gitclient.cpp 72.3 KB
Newer Older
1
/**************************************************************************
con's avatar
con committed
2
3
4
**
** This file is part of Qt Creator
**
hjk's avatar
hjk committed
5
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
con's avatar
con committed
6
**
7
** Contact: Nokia Corporation (qt-info@nokia.com)
con's avatar
con committed
8
**
9
** Commercial Usage
10
**
11
12
13
14
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
15
**
16
** GNU Lesser General Public License Usage
17
**
18
19
20
21
22
23
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24
**
25
** If you are unsure which license is appropriate for your use, please
hjk's avatar
hjk committed
26
** contact the sales department at http://qt.nokia.com/contact.
con's avatar
con committed
27
**
28
**************************************************************************/
hjk's avatar
hjk committed
29

con's avatar
con committed
30
#include "gitclient.h"
31
#include "gitcommand.h"
32
#include "gitutils.h"
hjk's avatar
hjk committed
33

con's avatar
con committed
34
#include "commitdata.h"
hjk's avatar
hjk committed
35
36
#include "gitconstants.h"
#include "gitplugin.h"
37
#include "gitsubmiteditor.h"
38
#include "gitversioncontrol.h"
con's avatar
con committed
39

40
#include <coreplugin/actionmanager/actionmanager.h>
hjk's avatar
hjk committed
41
#include <coreplugin/coreconstants.h>
con's avatar
con committed
42
#include <coreplugin/editormanager/editormanager.h>
hjk's avatar
hjk committed
43
44
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
45
#include <coreplugin/progressmanager/progressmanager.h>
hjk's avatar
hjk committed
46
#include <coreplugin/uniqueidmanager.h>
47
#include <coreplugin/filemanager.h>
48
49
#include <coreplugin/iversioncontrol.h>

con's avatar
con committed
50
#include <texteditor/itexteditor.h>
hjk's avatar
hjk committed
51
#include <utils/qtcassert.h>
52
#include <utils/synchronousprocess.h>
hjk's avatar
hjk committed
53
#include <vcsbase/vcsbaseeditor.h>
54
#include <vcsbase/vcsbaseoutputwindow.h>
55
#include <vcsbase/vcsbaseplugin.h>
con's avatar
con committed
56

57
58
#include <projectexplorer/environment.h>

con's avatar
con committed
59
60
#include <QtCore/QRegExp>
#include <QtCore/QTemporaryFile>
61
#include <QtCore/QTime>
62
63
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
64
#include <QtCore/QSignalMapper>
con's avatar
con committed
65

66
#include <QtGui/QMainWindow> // for msg box parent
hjk's avatar
hjk committed
67
#include <QtGui/QMessageBox>
68
#include <QtGui/QPushButton>
con's avatar
con committed
69

70
71
static const char *const kGitDirectoryC = ".git";
static const char *const kBranchIndicatorC = "# On branch";
con's avatar
con committed
72
73
74

static inline QString msgServerFailure()
{
75
    return Git::Internal::GitClient::tr(
con's avatar
con committed
76
77
78
79
80
81
82
"Note that the git plugin for QtCreator is not able to interact with the server "
"so far. Thus, manual ssh-identification etc. will not work.");
}

inline Core::IEditor* locateEditor(const Core::ICore *core, const char *property, const QString &entry)
{
    foreach (Core::IEditor *ed, core->editorManager()->openedEditors())
83
        if (ed->file()->property(property).toString() == entry)
con's avatar
con committed
84
85
86
87
            return ed;
    return 0;
}

88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// Return converted command output, remove '\r' read on Windows
static inline QString commandOutputFromLocal8Bit(const QByteArray &a)
{
    QString output = QString::fromLocal8Bit(a);
    output.remove(QLatin1Char('\r'));
    return output;
}

// Return converted command output split into lines
static inline QStringList commandOutputLinesFromLocal8Bit(const QByteArray &a)
{
    QString output = commandOutputFromLocal8Bit(a);
    const QChar newLine = QLatin1Char('\n');
    if (output.endsWith(newLine))
        output.truncate(output.size() - 1);
    if (output.isEmpty())
        return QStringList();
    return output.split(newLine);
}

108
109
110
111
112
static inline VCSBase::VCSBaseOutputWindow *outputWindow()
{
    return VCSBase::VCSBaseOutputWindow::instance();
}

113
114
115
namespace Git {
namespace Internal {

116
117
118
119
120
121
122
123
124
125
126
static inline QString msgRepositoryNotFound(const QString &dir)
{
    return GitClient::tr("Unable to determine the repository for %1.").arg(dir);
}

static inline QString msgParseFilesFailed()
{
    return  GitClient::tr("Unable to parse the file output.");
}

// ---------------- GitClient
127
128
129

const char *GitClient::stashNamePrefix = "stash@{";

130
131
GitClient::GitClient(GitPlugin* plugin)
  : m_msgWait(tr("Waiting for data...")),
con's avatar
con committed
132
    m_plugin(plugin),
133
    m_core(Core::ICore::instance()),
134
    m_repositoryChangedSignalMapper(0),
135
136
    m_cachedGitVersion(0),
    m_hasCachedGitVersion(false)
con's avatar
con committed
137
{
138
    if (QSettings *s = m_core->settings()) {
139
        m_settings.fromSettings(s);
140
141
        m_binaryPath = m_settings.gitBinaryPath();
    }
con's avatar
con committed
142
143
144
145
146
147
}

GitClient::~GitClient()
{
}

148
149
const char *GitClient::noColorOption = "--no-color";

con's avatar
con committed
150
151
QString GitClient::findRepositoryForDirectory(const QString &dir)
{
152
153
154
    // Check for ".git/config"
    const QString checkFile = QLatin1String(kGitDirectoryC) + QLatin1String("/config");
    return VCSBase::VCSBasePlugin::findRepositoryForDirectory(dir, checkFile);
con's avatar
con committed
155
156
157
158
159
160
161
}

/* Create an editor associated to VCS output of a source file/directory
 * (using the file's codec). Makes use of a dynamic property to find an
 * existing instance and to reuse it (in case, say, 'git diff foo' is
 * already open). */
VCSBase::VCSBaseEditor
162
    *GitClient::createVCSEditor(const QString &id,
con's avatar
con committed
163
164
165
166
167
168
169
170
171
172
173
174
175
176
                                QString title,
                                // Source file or directory
                                const QString &source,
                                bool setSourceCodec,
                                // Dynamic property and value to identify that editor
                                const char *registerDynamicProperty,
                                const QString &dynamicPropertyValue) const
{
    VCSBase::VCSBaseEditor *rc = 0;
    Core::IEditor* outputEditor = locateEditor(m_core, registerDynamicProperty, dynamicPropertyValue);
    if (outputEditor) {
         // Exists already
        outputEditor->createNew(m_msgWait);
        rc = VCSBase::VCSBaseEditor::getVcsBaseEditor(outputEditor);
hjk's avatar
hjk committed
177
        QTC_ASSERT(rc, return 0);
con's avatar
con committed
178
179
    } else {
        // Create new, set wait message, set up with source and codec
180
        outputEditor = m_core->editorManager()->openEditorWithContents(id, &title, m_msgWait);
181
        outputEditor->file()->setProperty(registerDynamicProperty, dynamicPropertyValue);
con's avatar
con committed
182
        rc = VCSBase::VCSBaseEditor::getVcsBaseEditor(outputEditor);
183
184
        connect(rc, SIGNAL(annotateRevisionRequested(QString,QString,int)),
                this, SLOT(slotBlameRevisionRequested(QString,QString,int)));
hjk's avatar
hjk committed
185
        QTC_ASSERT(rc, return 0);
con's avatar
con committed
186
187
        rc->setSource(source);
        if (setSourceCodec)
hjk's avatar
hjk committed
188
            rc->setCodec(VCSBase::VCSBaseEditor::getCodec(source));
con's avatar
con committed
189
    }
mae's avatar
mae committed
190
    m_core->editorManager()->activateEditor(outputEditor);
191
    rc->setForceReadOnly(true);
con's avatar
con committed
192
193
194
    return rc;
}

195
196
void GitClient::diff(const QString &workingDirectory,
                     const QStringList &diffArgs,
197
198
                     const QStringList &unstagedFileNames,
                     const QStringList &stagedFileNames)
con's avatar
con committed
199
200
{

201
202
203
204
    if (Git::Constants::debug)
        qDebug() << "diff" << workingDirectory << unstagedFileNames << stagedFileNames;

    const QString binary = QLatin1String(Constants::GIT_BINARY);
205
    const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
con's avatar
con committed
206
207
    const QString title = tr("Git Diff");

208
    VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, workingDirectory, true, "originalFileName", workingDirectory);
209
    editor->setDiffBaseDirectory(workingDirectory);
con's avatar
con committed
210

211
212
213
214
215
    // Create a batch of 2 commands to be run after each other in case
    // we have a mixture of staged/unstaged files as is the case
    // when using the submit dialog.
    GitCommand *command = createCommand(workingDirectory, editor);
    // Directory diff?
216
217
    QStringList commonDiffArgs;
    commonDiffArgs << QLatin1String("diff") << QLatin1String(noColorOption);
218
219
    if (m_settings.diffPatience)
        commonDiffArgs << QLatin1String("--patience");
220
    if (unstagedFileNames.empty() && stagedFileNames.empty()) {
221
222
       QStringList arguments(commonDiffArgs);
       arguments << diffArgs;
223
       outputWindow()->appendCommand(workingDirectory, binary, arguments);
Friedemann Kleint's avatar
Friedemann Kleint committed
224
       command->addJob(arguments, m_settings.timeoutSeconds);
225
226
227
    } else {
        // Files diff.
        if (!unstagedFileNames.empty()) {
228
229
           QStringList arguments(commonDiffArgs);
           arguments << QLatin1String("--") << unstagedFileNames;
230
           outputWindow()->appendCommand(workingDirectory, binary, arguments);
Friedemann Kleint's avatar
Friedemann Kleint committed
231
           command->addJob(arguments, m_settings.timeoutSeconds);
232
233
        }
        if (!stagedFileNames.empty()) {
234
235
           QStringList arguments(commonDiffArgs);
           arguments << QLatin1String("--cached") << diffArgs << QLatin1String("--") << stagedFileNames;
236
           outputWindow()->appendCommand(workingDirectory, binary, arguments);
Friedemann Kleint's avatar
Friedemann Kleint committed
237
           command->addJob(arguments, m_settings.timeoutSeconds);
238
239
240
        }
    }
    command->execute();
con's avatar
con committed
241
242
}

243
244
245
void GitClient::diff(const QString &workingDirectory,
                     const QStringList &diffArgs,
                     const QString &fileName)
con's avatar
con committed
246
247
248
249
{
    if (Git::Constants::debug)
        qDebug() << "diff" << workingDirectory << fileName;
    QStringList arguments;
250
251
    arguments << QLatin1String("diff") << QLatin1String(noColorOption)
              << diffArgs;
con's avatar
con committed
252
    if (!fileName.isEmpty())
253
        arguments << QLatin1String("--") << fileName;
254
    const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
con's avatar
con committed
255
    const QString title = tr("Git Diff %1").arg(fileName);
256
    const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName);
257
    VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, true, "originalFileName", sourceFile);
258
    executeGit(workingDirectory, arguments, editor);
con's avatar
con committed
259
260
}

261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
void GitClient::diffBranch(const QString &workingDirectory,
                           const QStringList &diffArgs,
                           const QString &branchName)
{
    if (Git::Constants::debug)
        qDebug() << "diffBranch" << workingDirectory << branchName;
    QStringList arguments;
    arguments << QLatin1String("diff") << QLatin1String(noColorOption)
              << diffArgs  << branchName;

    const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
    const QString title = tr("Git Diff Branch %1").arg(branchName);
    const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, QStringList());
    VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, true,
                                                     "BranchName", branchName);
    executeGit(workingDirectory, arguments, editor);
}

con's avatar
con committed
279
280
void GitClient::status(const QString &workingDirectory)
{
281
    // @TODO: Use "--no-color" once it is supported
282
283
    QStringList statusArgs(QLatin1String("status"));
    statusArgs << QLatin1String("-u");
284
    VCSBase::VCSBaseOutputWindow *outwin = outputWindow();
285
286
    outwin->setRepository(workingDirectory);
    GitCommand *command = executeGit(workingDirectory, statusArgs, 0, true);
287
    connect(command, SIGNAL(finished(bool,int,QVariant)), outwin, SLOT(clearRepository()),
288
            Qt::QueuedConnection);
con's avatar
con committed
289
290
}

291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
static const char graphLogFormatC[] = "%h %an %s %ci";

// Create a graphical log.
void GitClient::graphLog(const QString &workingDirectory)
{
    if (Git::Constants::debug)
        qDebug() << "log" << workingDirectory;

    QStringList arguments;
    arguments << QLatin1String("log") << QLatin1String(noColorOption);

    if (m_settings.logCount > 0)
         arguments << QLatin1String("-n") << QString::number(m_settings.logCount);
    arguments << (QLatin1String("--pretty=format:") +  QLatin1String(graphLogFormatC))
              << QLatin1String("--topo-order") <<  QLatin1String("--graph");

    const QString title = tr("Git Log");
    const QString editorId = QLatin1String(Git::Constants::GIT_LOG_EDITOR_ID);
    const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, QStringList());
    VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, false, "logFileName", sourceFile);
    executeGit(workingDirectory, arguments, editor);
}

314
void GitClient::log(const QString &workingDirectory, const QStringList &fileNames, bool enableAnnotationContextMenu)
con's avatar
con committed
315
316
{
    if (Git::Constants::debug)
317
        qDebug() << "log" << workingDirectory << fileNames;
con's avatar
con committed
318

319
320
    QStringList arguments;
    arguments << QLatin1String("log") << QLatin1String(noColorOption);
321
322
323
324

    if (m_settings.logCount > 0)
         arguments << QLatin1String("-n") << QString::number(m_settings.logCount);

325
326
    if (!fileNames.isEmpty())
        arguments.append(fileNames);
con's avatar
con committed
327

328
329
330
    const QString msgArg = fileNames.empty() ? workingDirectory :
                           fileNames.join(QString(", "));
    const QString title = tr("Git Log %1").arg(msgArg);
331
    const QString editorId = QLatin1String(Git::Constants::GIT_LOG_EDITOR_ID);
332
    const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileNames);
333
    VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, false, "logFileName", sourceFile);
334
    editor->setFileLogAnnotateEnabled(enableAnnotationContextMenu);
335
    executeGit(workingDirectory, arguments, editor);
con's avatar
con committed
336
337
}

338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
// Do not show "0000" or "^32ae4"
static inline bool canShow(const QString &sha)
{
    if (sha.startsWith(QLatin1Char('^')))
        return false;
    if (sha.count(QLatin1Char('0')) == sha.size())
        return false;
    return true;
}

static inline QString msgCannotShow(const QString &sha)
{
    return GitClient::tr("Cannot describe '%1'.").arg(sha);
}

con's avatar
con committed
353
354
355
356
void GitClient::show(const QString &source, const QString &id)
{
    if (Git::Constants::debug)
        qDebug() << "show" << source << id;
357
358
359
360
361
    if (!canShow(id)) {
        outputWindow()->append(msgCannotShow(id));
        return;
    }

362
363
    QStringList arguments;
    arguments << QLatin1String("show") << QLatin1String(noColorOption) << id;
con's avatar
con committed
364
365

    const QString title =  tr("Git Show %1").arg(id);
366
367
    const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
    VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, source, true, "show", id);
con's avatar
con committed
368
369
370

    const QFileInfo sourceFi(source);
    const QString workDir = sourceFi.isDir() ? sourceFi.absoluteFilePath() : sourceFi.absolutePath();
371
    executeGit(workDir, arguments, editor);
con's avatar
con committed
372
373
}

374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
void GitClient::slotBlameRevisionRequested(const QString &source, QString change, int lineNumber)
{
    // This might be invoked with a verbose revision description
    // "SHA1 author subject" from the annotation context menu. Strip the rest.
    const int blankPos = change.indexOf(QLatin1Char(' '));
    if (blankPos != -1)
        change.truncate(blankPos);
    const QFileInfo fi(source);
    blame(fi.absolutePath(), fi.fileName(), change, lineNumber);
}

void GitClient::blame(const QString &workingDirectory,
                      const QString &fileName,
                      const QString &revision /* = QString() */,
                      int lineNumber /* = -1 */)
con's avatar
con committed
389
390
{
    if (Git::Constants::debug)
391
        qDebug() << "blame" << workingDirectory << fileName << lineNumber;
con's avatar
con committed
392
    QStringList arguments(QLatin1String("blame"));
393
394
395
    arguments << QLatin1String("--root");
    if (m_plugin->settings().spaceIgnorantBlame)
        arguments << QLatin1String("-w");
396
    arguments << QLatin1String("--") << fileName;
397
398
    if (!revision.isEmpty())
        arguments << revision;
399
    const QString editorId = QLatin1String(Git::Constants::GIT_BLAME_EDITOR_ID);
400
401
    const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDirectory, QStringList(fileName), revision);
    const QString title = tr("Git Blame %1").arg(id);
402
    const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName);
con's avatar
con committed
403

404
    VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, true, "blameFileName", id);
405
    executeGit(workingDirectory, arguments, editor, false, GitCommand::NoReport, lineNumber);
con's avatar
con committed
406
407
}

408
409
410
411
void GitClient::checkoutBranch(const QString &workingDirectory, const QString &branch)
{
    QStringList arguments(QLatin1String("checkout"));
    arguments <<  branch;
412
413
    GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
    connectRepositoryChanged(workingDirectory, cmd);
414
415
}

416
417
418
419
420
421
422
423
bool GitClient::synchronousCheckoutBranch(const QString &workingDirectory,
                                    const QString &branch,
                                    QString *errorMessage /* = 0 */)
{
    QByteArray outputText;
    QByteArray errorText;
    QStringList arguments;
    arguments << QLatin1String("checkout") << branch;
424
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
425
    const QString output = commandOutputFromLocal8Bit(outputText);
426
    outputWindow()->append(output);
427
428
    if (!rc) {
        const QString stdErr = commandOutputFromLocal8Bit(errorText);
Friedemann Kleint's avatar
Friedemann Kleint committed
429
        //: Meaning of the arguments: %1: Branch, %2: Repository, %3: Error message
430
431
432
433
        const QString msg = tr("Unable to checkout %1 of %2: %3").arg(branch, workingDirectory, stdErr);
        if (errorMessage) {
            *errorMessage = msg;
        } else {
434
            outputWindow()->appendError(msg);
435
436
437
438
439
440
        }
        return false;
    }
    return true;
}

con's avatar
con committed
441
442
443
444
445
446
447
448
449
450
451
void GitClient::checkout(const QString &workingDirectory, const QString &fileName)
{
    // Passing an empty argument as the file name is very dangereous, since this makes
    // git checkout apply to all files. Almost looks like a bug in git.
    if (fileName.isEmpty())
        return;

    QStringList arguments;
    arguments << QLatin1String("checkout") << QLatin1String("HEAD") << QLatin1String("--")
            << fileName;

452
    executeGit(workingDirectory, arguments, 0, true);
con's avatar
con committed
453
454
455
456
457
458
459
460
461
}

void GitClient::hardReset(const QString &workingDirectory, const QString &commit)
{
    QStringList arguments;
    arguments << QLatin1String("reset") << QLatin1String("--hard");
    if (!commit.isEmpty())
        arguments << commit;

462
463
    GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
    connectRepositoryChanged(workingDirectory, cmd);
con's avatar
con committed
464
465
466
467
468
469
470
}

void GitClient::addFile(const QString &workingDirectory, const QString &fileName)
{
    QStringList arguments;
    arguments << QLatin1String("add") << fileName;

471
    executeGit(workingDirectory, arguments, 0, true);
con's avatar
con committed
472
473
}

474
475
476
477
// Warning: 'intendToAdd' works only from 1.6.1 onwards
bool GitClient::synchronousAdd(const QString &workingDirectory,
                               bool intendToAdd,
                               const QStringList &files)
con's avatar
con committed
478
{
479
480
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory << files;
con's avatar
con committed
481
482
483
    QByteArray outputText;
    QByteArray errorText;
    QStringList arguments;
484
485
486
487
    arguments << QLatin1String("add");
    if (intendToAdd)
        arguments << QLatin1String("--intent-to-add");
    arguments.append(files);
488
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
con's avatar
con committed
489
490
    if (!rc) {
        const QString errorMessage = tr("Unable to add %n file(s) to %1: %2", 0, files.size()).
491
                                     arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
492
        outputWindow()->appendError(errorMessage);
con's avatar
con committed
493
494
495
496
    }
    return rc;
}

497
498
499
500
501
502
503
504
505
506
507
508
509
bool GitClient::synchronousDelete(const QString &workingDirectory,
                                  bool force,
                                  const QStringList &files)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory << files;
    QByteArray outputText;
    QByteArray errorText;
    QStringList arguments;
    arguments << QLatin1String("rm");
    if (force)
        arguments << QLatin1String("--force");
    arguments.append(files);
510
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
511
512
513
    if (!rc) {
        const QString errorMessage = tr("Unable to remove %n file(s) from %1: %2", 0, files.size()).
                                     arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
514
        outputWindow()->appendError(errorMessage);
515
516
517
518
    }
    return rc;
}

dt's avatar
dt committed
519
520
521
522
523
524
525
526
527
528
529
530
bool GitClient::synchronousMove(const QString &workingDirectory,
                                const QString &from,
                                const QString &to)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory << from << to;
    QByteArray outputText;
    QByteArray errorText;
    QStringList arguments;
    arguments << QLatin1String("mv");
    arguments << (from);
    arguments << (to);
531
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
dt's avatar
dt committed
532
533
534
535
536
537
538
539
    if (!rc) {
        const QString errorMessage = tr("Unable to move from %1 to %2: %3").
                                     arg(from, to, commandOutputFromLocal8Bit(errorText));
        outputWindow()->appendError(errorMessage);
    }
    return rc;
}

540
541
542
bool GitClient::synchronousReset(const QString &workingDirectory,
                                 const QStringList &files,
                                 QString *errorMessage)
543
544
545
546
547
548
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory << files;
    QByteArray outputText;
    QByteArray errorText;
    QStringList arguments;
549
550
551
552
553
554
    arguments << QLatin1String("reset");
    if (files.isEmpty()) {
        arguments << QLatin1String("--hard");
    } else {
        arguments << QLatin1String("HEAD") << QLatin1String("--") << files;
    }
555
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
556
    const QString output = commandOutputFromLocal8Bit(outputText);
557
    outputWindow()->append(output);
558
559
560
    // Note that git exits with 1 even if the operation is successful
    // Assume real failure if the output does not contain "foo.cpp modified"
    if (!rc && !output.contains(QLatin1String("modified"))) {
561
562
563
564
565
566
567
        const QString stdErr = commandOutputFromLocal8Bit(errorText);
        const QString msg = files.isEmpty() ?
                            tr("Unable to reset %1: %2").arg(workingDirectory, stdErr) :
                            tr("Unable to reset %n file(s) in %1: %2", 0, files.size()).arg(workingDirectory, stdErr);
        if (errorMessage) {
            *errorMessage = msg;
        } else {
568
            outputWindow()->appendError(msg);
569
        }
570
571
572
573
574
        return false;
    }
    return true;
}

575
576
577
578
579
580
581
582
// Initialize repository
bool GitClient::synchronousInit(const QString &workingDirectory)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory;
    QByteArray outputText;
    QByteArray errorText;
    const QStringList arguments(QLatin1String("init"));
583
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
584
    // '[Re]Initialized...'
585
    outputWindow()->append(commandOutputFromLocal8Bit(outputText));
586
    if (!rc)
587
        outputWindow()->appendError(commandOutputFromLocal8Bit(errorText));
588
589
590
    return rc;
}

591
592
593
594
595
596
597
598
/* Checkout, supports:
 * git checkout -- <files>
 * git checkout revision -- <files>
 * git checkout revision -- . */
bool GitClient::synchronousCheckoutFiles(const QString &workingDirectory,
                                         QStringList files /* = QStringList() */,
                                         QString revision /* = QString() */,
                                         QString *errorMessage /* = 0 */)
599
600
601
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory << files;
602
603
604
605
    if (revision.isEmpty())
        revision = QLatin1String("HEAD");
    if (files.isEmpty())
        files = QStringList(QString(QLatin1Char('.')));
606
607
608
    QByteArray outputText;
    QByteArray errorText;
    QStringList arguments;
609
    arguments << QLatin1String("checkout") << revision << QLatin1String("--") << files;
610
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
611
    if (!rc) {
612
        const QString fileArg = files.join(QLatin1String(", "));
Friedemann Kleint's avatar
Friedemann Kleint committed
613
614
        //: Meaning of the arguments: %1: revision, %2: files, %3: repository,
        //: %4: Error message
615
616
617
618
619
        const QString msg = tr("Unable to checkout %1 of %2 in %3: %4").
                            arg(revision, fileArg, workingDirectory, commandOutputFromLocal8Bit(errorText));
        if (errorMessage) {
            *errorMessage = msg;
        } else {
620
            outputWindow()->appendError(msg);
621
        }
622
623
624
625
626
        return false;
    }
    return true;
}

627
628
629
630
static inline QString msgParentRevisionFailed(const QString &workingDirectory,
                                              const QString &revision,
                                              const QString &why)
{
Friedemann Kleint's avatar
Friedemann Kleint committed
631
    //: Failed to find parent revisions of a SHA1 for "annotate previous"
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
    return GitClient::tr("Unable to find parent revisions of %1 in %2: %3").arg(revision, workingDirectory, why);
}

static inline QString msgInvalidRevision()
{
    return GitClient::tr("Invalid revision");
}

// Split a line of "<commit> <parent1> ..." to obtain parents from "rev-list" or "log".
static inline bool splitCommitParents(const QString &line,
                                      QString *commit = 0,
                                      QStringList *parents = 0)
{
    if (commit)
        commit->clear();
    if (parents)
        parents->clear();
    QStringList tokens = line.trimmed().split(QLatin1Char(' '));
    if (tokens.size() < 2)
        return false;
    if (commit)
        *commit = tokens.front();
    tokens.pop_front();
    if (parents)
        *parents = tokens;
    return true;
}

// Find out the immediate parent revisions of a revision of the repository.
// Might be several in case of merges.
bool GitClient::synchronousParentRevisions(const QString &workingDirectory,
                                           const QStringList &files /* = QStringList() */,
                                           const QString &revision,
                                           QStringList *parents,
                                           QString *errorMessage)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory << revision;
    QByteArray outputTextData;
    QByteArray errorText;
    QStringList arguments;
    arguments << QLatin1String("rev-list") << QLatin1String(GitClient::noColorOption)
              << QLatin1String("--parents") << QLatin1String("--max-count=1") << revision;
    if (!files.isEmpty()) {
        arguments.append(QLatin1String("--"));
        arguments.append(files);
    }
679
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
680
    if (!rc) {
681
        *errorMessage = msgParentRevisionFailed(workingDirectory, revision, commandOutputFromLocal8Bit(errorText));
682
683
684
685
        return false;
    }
    // Should result in one line of blank-delimited revisions, specifying current first
    // unless it is top.
686
    QString outputText = commandOutputFromLocal8Bit(outputTextData);
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
    outputText.remove(QLatin1Char('\n'));
    if (!splitCommitParents(outputText, 0, parents)) {
        *errorMessage = msgParentRevisionFailed(workingDirectory, revision, msgInvalidRevision());
        return false;
    }
    if (Git::Constants::debug)
        qDebug() << workingDirectory << files << revision << "->" << *parents;
    return true;
}

// Short SHA1, author, subject
static const char defaultShortLogFormatC[] = "%h (%an \"%s\")";

bool GitClient::synchronousShortDescription(const QString &workingDirectory, const QString &revision,
                                    QString *description, QString *errorMessage)
{
    // Short SHA 1, author, subject
    return synchronousShortDescription(workingDirectory, revision,
                               QLatin1String(defaultShortLogFormatC),
                               description, errorMessage);
}

// Convenience working on a list of revisions
bool GitClient::synchronousShortDescriptions(const QString &workingDirectory, const QStringList &revisions,
                                            QStringList *descriptions, QString *errorMessage)
{
    descriptions->clear();
    foreach (const QString &revision, revisions) {
        QString description;
        if (!synchronousShortDescription(workingDirectory, revision, &description, errorMessage)) {
            descriptions->clear();
            return false;
        }
        descriptions->push_back(description);
    }
    return true;
}

725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
static inline QString msgCannotDetermineBranch(const QString &workingDirectory, const QString &why)
{
    return GitClient::tr("Unable to retrieve branch of %1: %2").arg(workingDirectory, why);
}

// Retrieve head revision/branch
bool GitClient::synchronousTopRevision(const QString &workingDirectory,
                                       QString *revision /* = 0 */,
                                       QString *branch /* = 0 */,
                                       QString *errorMessageIn /* = 0 */)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory;
    QByteArray outputTextData;
    QByteArray errorText;
    QStringList arguments;
    QString errorMessage;
    do {
        // get revision
        if (revision) {
            revision->clear();
            arguments << QLatin1String("log") << QLatin1String(noColorOption)
                    <<  QLatin1String("--max-count=1") << QLatin1String("--pretty=format:%H");
748
            if (!fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
749
750
751
752
753
754
755
756
757
758
759
                errorMessage =  tr("Unable to retrieve top revision of %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
                break;
            }
            *revision = commandOutputFromLocal8Bit(outputTextData);
            revision->remove(QLatin1Char('\n'));
        } // revision desired
        // get branch
        if (branch) {
            branch->clear();
            arguments.clear();
            arguments << QLatin1String("branch") << QLatin1String(noColorOption);
760
            if (!fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
                errorMessage = msgCannotDetermineBranch(workingDirectory, commandOutputFromLocal8Bit(errorText));
                break;
            }
            /* parse output for current branch: \code
* master
  branch2
\endcode */
            const QString branchPrefix = QLatin1String("* ");
            foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputTextData)) {
                if (line.startsWith(branchPrefix)) {
                    *branch = line;
                    branch->remove(0, branchPrefix.size());
                    break;
                }
            }
            if (branch->isEmpty()) {
                errorMessage = msgCannotDetermineBranch(workingDirectory,
                                                        QString::fromLatin1("Internal error: Failed to parse output: %1").arg(commandOutputFromLocal8Bit(outputTextData)));
                break;
            }
        } // branch
    } while (false);
    const bool failed = (revision && revision->isEmpty()) || (branch && branch->isEmpty());
    if (failed && !errorMessage.isEmpty()) {
        if (errorMessageIn) {
            *errorMessageIn = errorMessage;
        } else {
788
            outputWindow()->appendError(errorMessage);
789
790
791
792
793
        }
    }
    return !failed;
}

794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
// Format an entry in a one-liner for selection list using git log.
bool GitClient::synchronousShortDescription(const QString &workingDirectory,
                                    const QString &revision,
                                    const QString &format,
                                    QString *description,
                                    QString *errorMessage)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory << revision;
    QByteArray outputTextData;
    QByteArray errorText;
    QStringList arguments;
    arguments << QLatin1String("log") << QLatin1String(GitClient::noColorOption)
              << (QLatin1String("--pretty=format:") + format)
              << QLatin1String("--max-count=1") << revision;
809
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
810
    if (!rc) {
811
        *errorMessage = tr("Unable to describe revision %1 in %2: %3").arg(revision, workingDirectory, commandOutputFromLocal8Bit(errorText));
812
813
        return false;
    }
814
    *description = commandOutputFromLocal8Bit(outputTextData);
815
816
817
818
819
    if (description->endsWith(QLatin1Char('\n')))
        description->truncate(description->size() - 1);
    return true;
}

820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
// Create a default message to be used for describing stashes
static inline QString creatorStashMessage(const QString &keyword = QString())
{
    QString rc = QCoreApplication::applicationName();
    rc += QLatin1Char(' ');
    if (!keyword.isEmpty()) {
        rc += keyword;
        rc += QLatin1Char(' ');
    }
    rc += QDateTime::currentDateTime().toString(Qt::ISODate);
    return rc;
}

/* Do a stash and return the message as identifier. Note that stash names (stash{n})
 * shift as they are pushed, so, enforce the use of messages to identify them. Flags:
 * StashPromptDescription: Prompt the user for a description message.
 * StashImmediateRestore: Immediately re-apply this stash (used for snapshots), user keeps on working
 * StashIgnoreUnchanged: Be quiet about unchanged repositories (used for IVersionControl's snapshots). */

QString GitClient::synchronousStash(const QString &workingDirectory,
                                    const QString &messageKeyword /*  = QString() */,
                                    unsigned flags,
                                    bool *unchanged /* =0 */)
{
    if (unchanged)
        *unchanged = false;
    QString message;
    bool success = false;
    // Check for changes and stash
    QString errorMessage;
    switch (gitStatus(workingDirectory, false, 0, &errorMessage)) {
    case  StatusChanged: {
            message = creatorStashMessage(messageKeyword);
            do {
                if ((flags & StashPromptDescription)) {
                    if (!inputText(Core::ICore::instance()->mainWindow(),
856
                         tr("Stash Description"), tr("Description:"), &message))
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
                        break;
                }
                if (!executeSynchronousStash(workingDirectory, message))
                    break;
                if ((flags & StashImmediateRestore)
                    && !synchronousStashRestore(workingDirectory, QLatin1String("stash@{0}")))
                    break;
                success = true;
            } while (false);
        }
        break;
    case StatusUnchanged:
        if (unchanged)
            *unchanged = true;
        if (!(flags & StashIgnoreUnchanged))
872
            outputWindow()->append(msgNoChangedFiles());
873
874
        break;
    case StatusFailed:
875
        outputWindow()->append(errorMessage);
876
877
878
879
880
881
882
883
884
885
886
887
        break;
    }
    if (!success)
        message.clear();
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << '\n' << workingDirectory << messageKeyword << "returns" << message;
    return message;
}

bool GitClient::executeSynchronousStash(const QString &workingDirectory,
                                 const QString &message,
                                 QString *errorMessage /* = 0*/)
888
889
890
891
892
893
894
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory;
    QByteArray outputText;
    QByteArray errorText;
    QStringList arguments;
    arguments << QLatin1String("stash");
895
896
    if (!message.isEmpty())
        arguments << QLatin1String("save") << message;
897
    const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
898
    if (!rc) {
899
900
901
902
        const QString msg = tr("Unable stash in %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
        if (errorMessage) {
            *errorMessage = msg;
        } else {
903
            outputWindow()->append(msg);
904
        }
905
906
907
908
909
        return false;
    }
    return true;
}

910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
// Resolve a stash name from message
bool GitClient::stashNameFromMessage(const QString &workingDirectory,
                                     const QString &message, QString *name,
                                     QString *errorMessage /* = 0 */)
{
    // All happy
    if (message.startsWith(QLatin1String(stashNamePrefix))) {
        *name = message;
        return true;
    }
    // Retrieve list and find via message
    QList<Stash> stashes;
    if (!synchronousStashList(workingDirectory, &stashes, errorMessage))
        return false;
    foreach (const Stash &s, stashes) {
        if (s.message == message) {
            *name = s.name;
            return true;
        }
    }
Friedemann Kleint's avatar
Friedemann Kleint committed
930
    //: Look-up of a stash via its descriptive message failed.
931
932
933
934
    const QString msg = tr("Unable to resolve stash message '%1' in %2").arg(message, workingDirectory);
    if (errorMessage) {
        *errorMessage = msg;
    } else {
935
        outputWindow()->append(msg);
936
937
938
939
    }
    return  false;
}

940
941
942
943
944
945
946
947
bool GitClient::synchronousBranchCmd(const QString &workingDirectory, QStringList branchArgs,
                                     QString *output, QString *errorMessage)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory << branchArgs;
    branchArgs.push_front(QLatin1String("branch"));
    QByteArray outputText;
    QByteArray errorText;
948
    const bool rc = fullySynchronousGit(workingDirectory, branchArgs, &outputText, &errorText);
949
    if (!rc) {
Friedemann Kleint's avatar
Friedemann Kleint committed
950
        *errorMessage = tr("Unable to run a 'git branch' command in %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
951
952
        return false;
    }
953
    *output = commandOutputFromLocal8Bit(outputText);
954
955
956
957
958
959
960
961
    return true;
}

bool GitClient::synchronousShow(const QString &workingDirectory, const QString &id,
                                 QString *output, QString *errorMessage)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory << id;
962
963
964
965
    if (!canShow(id)) {
        *errorMessage = msgCannotShow(id);
        return false;
    }
966
    QStringList args(QLatin1String("show"));
967
    args << QLatin1String(noColorOption) << id;
968
969
    QByteArray outputText;
    QByteArray errorText;
970
    const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
971
    if (!rc) {
Friedemann Kleint's avatar
Friedemann Kleint committed
972
        *errorMessage = tr("Unable to run 'git show' in %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
973
974
        return false;
    }
975
    *output = commandOutputFromLocal8Bit(outputText);
976
977
978
    return true;
}

979
980
981
982
983
984
985
986
987
988
989
// Retrieve list of files to be cleaned
bool GitClient::synchronousCleanList(const QString &workingDirectory,
                                     QStringList *files, QString *errorMessage)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory;
    files->clear();
    QStringList args;
    args << QLatin1String("clean") << QLatin1String("--dry-run") << QLatin1String("-dxf");
    QByteArray outputText;
    QByteArray errorText;
990
    const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
991
    if (!rc) {
Friedemann Kleint's avatar
Friedemann Kleint committed
992
        *errorMessage = tr("Unable to run 'git clean' in %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
993
994
995
996
997
998
999
1000
1001
1002
        return false;
    }
    // Filter files that git would remove
    const QString prefix = QLatin1String("Would remove ");
    foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputText))
        if (line.startsWith(prefix))
            files->push_back(line.mid(prefix.size()));
    return true;
}

1003
1004
1005
1006
1007
1008
1009
1010
1011
bool GitClient::synchronousApplyPatch(const QString &workingDirectory,
                                      const QString &file, QString *errorMessage)
{
    if (Git::Constants::debug)
        qDebug() << Q_FUNC_INFO << workingDirectory;
    QStringList args;
    args << QLatin1String("apply") << QLatin1String("--whitespace=fix") << file;
    QByteArray outputText;
    QByteArray errorText;
1012
    const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
    if (rc) {
        if (!errorText.isEmpty())
            *errorMessage = tr("There were warnings while applying %1 to %2:\n%3").arg(file, workingDirectory, commandOutputFromLocal8Bit(errorText));
    } else {
        *errorMessage = tr("Unable apply patch %1 to %2: %3").arg(file, workingDirectory, commandOutputFromLocal8Bit(errorText));
        return false;
    }
    return true;
}

1023
1024
1025
// Factory function to create an asynchronous command
GitCommand *GitClient::createCommand(const QString &workingDirectory,
                             VCSBase::VCSBaseEditor* editor,
1026
1027
                             bool outputToWindow,
                             int editorLineNumber)
con's avatar
con committed
1028
1029
{
    if (Git::Constants::debug)
1030
        qDebug() << Q_FUNC_INFO << workingDirectory << editor;
1031

1032
    VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
1033
1034
    GitCommand* command = new GitCommand(binary(), workingDirectory, processEnvironment(), QVariant(editorLineNumber));
    if (editor)
1035
        connect(command, SIGNAL(finished(bool,int,QVariant)), editor, SLOT(commandFinishedGotoLine(bool,int,QVariant)));
con's avatar
con committed
1036
    if (outputToWindow) {
1037
1038
        if (editor) { // assume that the commands output is the important thing
            connect(command, SIGNAL(outputData(QByteArray)), outputWindow, SLOT(appendDataSilently(QByteArray)));
1039
1040
1041
        } else {
            connect(command, SIGNAL(outputData(QByteArray)), outputWindow, SLOT(appendData(QByteArray)));
        }
con's avatar
con committed
1042
    } else {
hjk's avatar
hjk committed
1043
        QTC_ASSERT(editor, /**/);
1044
        connect(command, SIGNAL(outputData(QByteArray)), editor, SLOT(setPlainTextDataFiltered(QByteArray)));
con's avatar
con committed
1045
1046
1047
    }

    if (outputWindow)
1048
        connect(command, SIGNAL(errorText(QString)), outputWindow, SLOT(appendError(QString)));
1049
1050
    return command;
}
con's avatar
con committed
1051

1052
// Execute a single command
1053
1054
1055
1056
1057
GitCommand *GitClient::executeGit(const QString &workingDirectory,
                                  const QStringList &arguments,
                                  VCSBase::VCSBaseEditor* editor,
                                  bool outputToWindow,
                                  GitCommand::TerminationReportMode tm,
1058
1059
                                  int editorLineNumber,
                                  bool unixTerminalDisabled)
1060
{
1061
    outputWindow()->appendCommand(workingDirectory, QLatin1String(Constants::GIT_BINARY), arguments);
1062
    GitCommand *command = createCommand(workingDirectory, editor, outputToWindow, editorLineNumber);
Friedemann Kleint's avatar
Friedemann Kleint committed
1063
    command->addJob(arguments, m_settings.timeoutSeconds);
1064
    command->setTerminationReportMode(tm);
1065
    command->setUnixTerminalDisabled(unixTerminalDisabled);
1066
    command->execute();
1067
    return command;
con's avatar
con committed
1068
1069
}

1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
// Return fixed arguments required to run
QStringList GitClient::binary() const
{
#ifdef Q_OS_WIN
        QStringList args;
        args << QLatin1String("cmd.exe") << QLatin1String("/c") << m_binaryPath;
        return args;
#else
        return QStringList(m_binaryPath);
#endif
}

1082
QProcessEnvironment GitClient::processEnvironment() const
1083
{
1084
    QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
1085
    if (m_settings.adoptPath)
1086
1087
1088
1089
        environment.insert(QLatin1String("PATH"), m_settings.path);
    // Set up SSH and C locale (required by git using perl).
    VCSBase::VCSBasePlugin::setProcessEnvironment(&environment);
    return environment;
1090
1091
}

1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
// Synchronous git execution using Utils::SynchronousProcess, with
// log windows updating.
Utils::SynchronousProcessResponse
        GitClient::synchronousGit(const QString &workingDirectory,
                                  const QStringList &gitArguments,
                                  unsigned flags,
                                  QTextCodec *stdOutCodec)
{
    if (Git::Constants::debug)
        qDebug() << "synchronousGit" << workingDirectory << gitArguments;
    QStringList args = binary(); // "cmd /c git" on Windows
    const QString executable = args.front();
    args.pop_front();
    args.append(gitArguments);
    return VCSBase::VCSBasePlugin::runVCS(workingDirectory, executable, args,
                                          m_settings.timeoutSeconds * 1000,
                                          flags, stdOutCodec);
}

bool GitClient::fullySynchronousGit(const QString &workingDirectory,
1112
                               const QStringList &gitArguments,
1113
1114
1115
                               QByteArray* outputText,
                               QByteArray* errorText,
                               bool logCommandToWindow)
con's avatar
con committed
1116
1117
{
    if (Git::Constants::debug)
1118
        qDebug() << "fullySynchronousGit" << workingDirectory << gitArguments;
con's avatar
con committed
1119

1120
    if (logCommandToWindow)
1121
        outputWindow()->appendCommand(workingDirectory, m_binaryPath, gitArguments);
1122
1123

    QProcess process;
con's avatar
con committed
1124
    process.setWorkingDirectory(workingDirectory);
1125
    process.setProcessEnvironment(processEnvironment());
con's avatar
con committed
1126

1127
    QStringList args = binary(); // "cmd /c git" on Windows
1128
1129
    const QString executable = args.front();
    args.pop_front();
1130
1131
    args.append(gitArguments);
    process.start(executable, args);
1132
    process.closeWriteChannel();
1133
1134
1135
1136
1137
1138
1139
1140
    if (!process.waitForStarted()) {
        if (errorText) {
            const QString msg = QString::fromLatin1("Unable to execute '%1': %2:")
                                .arg(binary().join(QString(QLatin1Char(' '))), process.errorString());
            *errorText = msg.toLocal8Bit();
        }
        return false;
    }
1141

1142
    if (!Utils::SynchronousProcess::readDataFromProcess(process, m_settings.timeoutSeconds * 1000,
1143
                                                        outputText, errorText, true)) {
1144
        errorText->append(GitCommand::msgTimeout(m_settings.timeoutSeconds).toLocal8Bit());
1145
        Utils::SynchronousProcess::stopProcess(process);
con's avatar
con committed
1146
1147
1148
1149
        return false;
    }

    if (Git::Constants::debug)
1150
1151
        qDebug() << "synchronousGit ex=" << process.exitStatus() << process.exitCode();
    return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;