Newer
Older
/**************************************************************************
** Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).
** Contact: Nokia Corporation (qt-info@nokia.com)
** 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.
** GNU Lesser General Public License Usage
** 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.
** If you are unsure which license is appropriate for your use, please
**************************************************************************/
#include "gitcommand.h"
#include "gitconstants.h"
#include "gitplugin.h"
#include "gitsubmiteditor.h"
#include "gitversioncontrol.h"
#include <coreplugin/actionmanager/actionmanager.h>
#include <coreplugin/icore.h>
#include <coreplugin/messagemanager.h>
#include <coreplugin/progressmanager/progressmanager.h>
#include <coreplugin/filemanager.h>
#include <coreplugin/iversioncontrol.h>
#include <utils/qtcassert.h>
#include <vcsbase/vcsbaseeditor.h>
#include <vcsbase/vcsbaseoutputwindow.h>
#include <projectexplorer/environment.h>
#include <QtCore/QTime>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtCore/QSignalMapper>
#include <QtGui/QMainWindow> // for msg box parent
#include <QtGui/QPushButton>

Friedemann Kleint
committed
static const char *const kGitDirectoryC = ".git";
static const char *const kBranchIndicatorC = "# On branch";
return Git::Internal::GitClient::tr(
"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())
if (ed->file()->property(property).toString() == entry)
// 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);
}
namespace Git {
namespace Internal {
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.");
}
// Format a command for the status window
static QString formatCommand(const QString &binary, const QStringList &args)
{
//: Executing: <executable> <arguments>
return GitClient::tr("Executing: %1 %2\n").arg(binary, args.join(QString(QLatin1Char(' '))));
// ---------------- GitClient
const char *GitClient::stashNamePrefix = "stash@{";

hjk
committed
GitClient::GitClient(GitPlugin* plugin)
: m_msgWait(tr("Waiting for data...")),
m_core(Core::ICore::instance()),
m_repositoryChangedSignalMapper(0),
m_cachedGitVersion(0),
m_hasCachedGitVersion(false)

Friedemann Kleint
committed
if (QSettings *s = m_core->settings()) {
m_settings.fromSettings(s);

Friedemann Kleint
committed
m_binaryPath = m_settings.gitBinaryPath();
}

Friedemann Kleint
committed
const char *GitClient::noColorOption = "--no-color";
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
QString GitClient::findRepositoryForFile(const QString &fileName)
{
const QString gitDirectory = QLatin1String(kGitDirectoryC);
const QFileInfo info(fileName);
QDir dir = info.absoluteDir();
do {
if (dir.entryList(QDir::AllDirs|QDir::Hidden).contains(gitDirectory))
return dir.absolutePath();
} while (dir.cdUp());
return QString();
}
QString GitClient::findRepositoryForDirectory(const QString &dir)
{
const QString gitDirectory = QLatin1String(kGitDirectoryC);
QDir directory(dir);
do {
if (directory.entryList(QDir::AllDirs|QDir::Hidden).contains(gitDirectory))
return directory.absolutePath();
} while (directory.cdUp());
return QString();
}
/* 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
*GitClient::createVCSEditor(const QString &id,
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);
} else {
// Create new, set wait message, set up with source and codec
outputEditor = m_core->editorManager()->openEditorWithContents(id, &title, m_msgWait);
outputEditor->file()->setProperty(registerDynamicProperty, dynamicPropertyValue);
connect(rc, SIGNAL(annotateRevisionRequested(QString,QString,int)),
this, SLOT(slotBlameRevisionRequested(QString,QString,int)));
m_core->editorManager()->activateEditor(outputEditor);

Friedemann Kleint
committed
void GitClient::diff(const QString &workingDirectory,
const QStringList &diffArgs,
const QStringList &unstagedFileNames,
const QStringList &stagedFileNames)
if (Git::Constants::debug)
qDebug() << "diff" << workingDirectory << unstagedFileNames << stagedFileNames;
const QString binary = QLatin1String(Constants::GIT_BINARY);
const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, workingDirectory, true, "originalFileName", workingDirectory);
editor->setDiffBaseDirectory(workingDirectory);
// 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?

Friedemann Kleint
committed
QStringList commonDiffArgs;
commonDiffArgs << QLatin1String("diff") << QLatin1String(noColorOption);
if (unstagedFileNames.empty() && stagedFileNames.empty()) {

Friedemann Kleint
committed
QStringList arguments(commonDiffArgs);
arguments << diffArgs;
VCSBase::VCSBaseOutputWindow::instance()->appendCommand(formatCommand(binary, arguments));
command->addJob(arguments, m_settings.timeoutSeconds);
} else {
// Files diff.
if (!unstagedFileNames.empty()) {

Friedemann Kleint
committed
QStringList arguments(commonDiffArgs);
arguments << QLatin1String("--") << unstagedFileNames;
VCSBase::VCSBaseOutputWindow::instance()->appendCommand(formatCommand(binary, arguments));
command->addJob(arguments, m_settings.timeoutSeconds);
}
if (!stagedFileNames.empty()) {

Friedemann Kleint
committed
QStringList arguments(commonDiffArgs);
arguments << QLatin1String("--cached") << diffArgs << QLatin1String("--") << stagedFileNames;
VCSBase::VCSBaseOutputWindow::instance()->appendCommand(formatCommand(binary, arguments));
command->addJob(arguments, m_settings.timeoutSeconds);
}
}
command->execute();

Friedemann Kleint
committed
void GitClient::diff(const QString &workingDirectory,
const QStringList &diffArgs,
const QString &fileName)
{
if (Git::Constants::debug)
qDebug() << "diff" << workingDirectory << fileName;
QStringList arguments;
arguments << QLatin1String("diff") << QLatin1String(noColorOption)
<< diffArgs;
arguments << QLatin1String("--") << fileName;
const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName);
VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, true, "originalFileName", sourceFile);
executeGit(workingDirectory, arguments, editor);
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);
}

Friedemann Kleint
committed
// @TODO: Use "--no-color" once it is supported
QStringList statusArgs(QLatin1String("status"));
statusArgs << QLatin1String("-u");
VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
outwin->setRepository(workingDirectory);
GitCommand *command = executeGit(workingDirectory, statusArgs, 0, true);
connect(command, SIGNAL(finished(bool,QVariant)), outwin, SLOT(clearRepository()),
Qt::QueuedConnection);
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);
}
void GitClient::log(const QString &workingDirectory, const QStringList &fileNames, bool enableAnnotationContextMenu)
qDebug() << "log" << workingDirectory << fileNames;

Friedemann Kleint
committed
QStringList arguments;
arguments << QLatin1String("log") << QLatin1String(noColorOption);
if (m_settings.logCount > 0)
arguments << QLatin1String("-n") << QString::number(m_settings.logCount);
if (!fileNames.isEmpty())
arguments.append(fileNames);
const QString msgArg = fileNames.empty() ? workingDirectory :
fileNames.join(QString(", "));
const QString title = tr("Git Log %1").arg(msgArg);
const QString editorId = QLatin1String(Git::Constants::GIT_LOG_EDITOR_ID);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileNames);
VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, false, "logFileName", sourceFile);
editor->setFileLogAnnotateEnabled(enableAnnotationContextMenu);
executeGit(workingDirectory, arguments, editor);
}
void GitClient::show(const QString &source, const QString &id)
{
if (Git::Constants::debug)
qDebug() << "show" << source << id;

Friedemann Kleint
committed
QStringList arguments;
arguments << QLatin1String("show") << QLatin1String(noColorOption) << id;
const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, source, true, "show", id);
const QFileInfo sourceFi(source);
const QString workDir = sourceFi.isDir() ? sourceFi.absoluteFilePath() : sourceFi.absolutePath();
executeGit(workDir, arguments, editor);
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 */)
qDebug() << "blame" << workingDirectory << fileName << lineNumber;
arguments << QLatin1String("--root");
if (m_plugin->settings().spaceIgnorantBlame)
arguments << QLatin1String("-w");
arguments << QLatin1String("--") << fileName;
if (!revision.isEmpty())
arguments << revision;
const QString editorId = QLatin1String(Git::Constants::GIT_BLAME_EDITOR_ID);
const QString id = VCSBase::VCSBaseEditor::getTitleId(workingDirectory, QStringList(fileName), revision);
const QString title = tr("Git Blame %1").arg(id);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName);
VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, true, "blameFileName", id);
executeGit(workingDirectory, arguments, editor, false, GitCommand::NoReport, lineNumber);
void GitClient::checkoutBranch(const QString &workingDirectory, const QString &branch)
{
QStringList arguments(QLatin1String("checkout"));
arguments << branch;
GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
connectRepositoryChanged(workingDirectory, cmd);
bool GitClient::synchronousCheckoutBranch(const QString &workingDirectory,
const QString &branch,
QString *errorMessage /* = 0 */)
{
QByteArray outputText;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("checkout") << branch;
const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
const QString output = commandOutputFromLocal8Bit(outputText);
VCSBase::VCSBaseOutputWindow::instance()->append(output);
if (!rc) {
const QString stdErr = commandOutputFromLocal8Bit(errorText);
//: Meaning of the arguments: %1: Branch, %2: Repository, %3: Error message
const QString msg = tr("Unable to checkout %1 of %2: %3").arg(branch, workingDirectory, stdErr);
if (errorMessage) {
*errorMessage = msg;
} else {
VCSBase::VCSBaseOutputWindow::instance()->appendError(msg);
}
return false;
}
return true;
}
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;
executeGit(workingDirectory, arguments, 0, true);
}
void GitClient::hardReset(const QString &workingDirectory, const QString &commit)
{
QStringList arguments;
arguments << QLatin1String("reset") << QLatin1String("--hard");
if (!commit.isEmpty())
arguments << commit;
GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
connectRepositoryChanged(workingDirectory, cmd);
}
void GitClient::addFile(const QString &workingDirectory, const QString &fileName)
{
QStringList arguments;
arguments << QLatin1String("add") << fileName;
executeGit(workingDirectory, arguments, 0, true);
// Warning: 'intendToAdd' works only from 1.6.1 onwards
bool GitClient::synchronousAdd(const QString &workingDirectory,
bool intendToAdd,
const QStringList &files)
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory << files;
QByteArray outputText;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("add");
if (intendToAdd)
arguments << QLatin1String("--intent-to-add");
arguments.append(files);
const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
if (!rc) {
const QString errorMessage = tr("Unable to add %n file(s) to %1: %2", 0, files.size()).
arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
bool GitClient::synchronousDelete(const QString &workingDirectory,
bool force,
const QStringList &files)
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory << files;
QByteArray outputText;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("rm");
if (force)
arguments << QLatin1String("--force");
arguments.append(files);
const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
if (!rc) {
const QString errorMessage = tr("Unable to remove %n file(s) from %1: %2", 0, files.size()).
arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
}
return rc;
}
bool GitClient::synchronousReset(const QString &workingDirectory,
const QStringList &files,
QString *errorMessage)
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory << files;
QByteArray outputText;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("reset");
if (files.isEmpty()) {
arguments << QLatin1String("--hard");
} else {
arguments << QLatin1String("HEAD") << QLatin1String("--") << files;
}
const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
const QString output = commandOutputFromLocal8Bit(outputText);
VCSBase::VCSBaseOutputWindow::instance()->append(output);
// Note that git exits with 1 even if the operation is successful
// Assume real failure if the output does not contain "foo.cpp modified"
if (!rc && !output.contains(QLatin1String("modified"))) {
const QString 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 {
VCSBase::VCSBaseOutputWindow::instance()->appendError(msg);
}
return false;
}
return true;
}
// 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"));
const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
// '[Re]Initialized...'
VCSBase::VCSBaseOutputWindow::instance()->append(commandOutputFromLocal8Bit(outputText));
VCSBase::VCSBaseOutputWindow::instance()->appendError(commandOutputFromLocal8Bit(errorText));
return rc;
}
/* 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 */)
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory << files;
if (revision.isEmpty())
revision = QLatin1String("HEAD");
if (files.isEmpty())
files = QStringList(QString(QLatin1Char('.')));
QByteArray outputText;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("checkout") << revision << QLatin1String("--") << files;
const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
if (!rc) {
const QString fileArg = files.join(QLatin1String(", "));
//: Meaning of the arguments: %1: revision, %2: files, %3: repository,
//: %4: Error message
const QString msg = tr("Unable to checkout %1 of %2 in %3: %4").
arg(revision, fileArg, workingDirectory, commandOutputFromLocal8Bit(errorText));
if (errorMessage) {
*errorMessage = msg;
} else {
VCSBase::VCSBaseOutputWindow::instance()->appendError(msg);
}
return false;
}
return true;
}
static inline QString msgParentRevisionFailed(const QString &workingDirectory,
const QString &revision,
const QString &why)
{
//: Failed to find parent revisions of a SHA1 for "annotate previous"
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
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);
}
const bool rc = synchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
if (!rc) {
*errorMessage = msgParentRevisionFailed(workingDirectory, revision, commandOutputFromLocal8Bit(errorText));
return false;
}
// Should result in one line of blank-delimited revisions, specifying current first
// unless it is top.
QString outputText = commandOutputFromLocal8Bit(outputTextData);
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
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;
}
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
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
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");
if (!synchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
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);
if (!synchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
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 {
VCSBase::VCSBaseOutputWindow::instance()->appendError(errorMessage);
}
}
return !failed;
}
// 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;
const bool rc = synchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
if (!rc) {
*errorMessage = tr("Unable to describe revision %1 in %2: %3").arg(revision, workingDirectory, commandOutputFromLocal8Bit(errorText));
return false;
}
*description = commandOutputFromLocal8Bit(outputTextData);
if (description->endsWith(QLatin1Char('\n')))
description->truncate(description->size() - 1);
return true;
}
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
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
856
857
858
859
860
861
// 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(),
tr("Stash description"), tr("Description:"), &message))
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))
VCSBase::VCSBaseOutputWindow::instance()->append(msgNoChangedFiles());
break;
case StatusFailed:
VCSBase::VCSBaseOutputWindow::instance()->append(errorMessage);
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*/)

Friedemann Kleint
committed
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory;
QByteArray outputText;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("stash");
if (!message.isEmpty())
arguments << QLatin1String("save") << message;

Friedemann Kleint
committed
const bool rc = synchronousGit(workingDirectory, arguments, &outputText, &errorText);
if (!rc) {
const QString msg = tr("Unable stash in %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
if (errorMessage) {
*errorMessage = msg;
} else {
VCSBase::VCSBaseOutputWindow::instance()->append(msg);
}

Friedemann Kleint
committed
return false;
}
return true;
}
// 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;
}
}
//: Look-up of a stash via its descriptive message failed.
const QString msg = tr("Unable to resolve stash message '%1' in %2").arg(message, workingDirectory);
if (errorMessage) {
*errorMessage = msg;
} else {
VCSBase::VCSBaseOutputWindow::instance()->append(msg);
}
return false;
}
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;
const bool rc = synchronousGit(workingDirectory, branchArgs, &outputText, &errorText);
if (!rc) {
*errorMessage = tr("Unable to run branch command: %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
*output = commandOutputFromLocal8Bit(outputText);
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;
QStringList args(QLatin1String("show"));

Friedemann Kleint
committed
args << QLatin1String(noColorOption) << id;
QByteArray outputText;
QByteArray errorText;
const bool rc = synchronousGit(workingDirectory, args, &outputText, &errorText);
if (!rc) {
*errorMessage = tr("Unable to run show: %1: %2").arg(workingDirectory, commandOutputFromLocal8Bit(errorText));
*output = commandOutputFromLocal8Bit(outputText);
// Factory function to create an asynchronous command
GitCommand *GitClient::createCommand(const QString &workingDirectory,
VCSBase::VCSBaseEditor* editor,
bool outputToWindow,
int editorLineNumber)
qDebug() << Q_FUNC_INFO << workingDirectory << editor;
VCSBase::VCSBaseOutputWindow *outputWindow = VCSBase::VCSBaseOutputWindow::instance();
GitCommand* command = new GitCommand(binary(), workingDirectory, processEnvironment(), QVariant(editorLineNumber));
if (editor)
connect(command, SIGNAL(finished(bool,QVariant)), editor, SLOT(commandFinishedGotoLine(bool,QVariant)));
if (editor) { // assume that the commands output is the important thing
connect(command, SIGNAL(outputData(QByteArray)), outputWindow, SLOT(appendDataSilently(QByteArray)));
} else {
connect(command, SIGNAL(outputData(QByteArray)), outputWindow, SLOT(appendData(QByteArray)));
}
connect(command, SIGNAL(outputData(QByteArray)), editor, SLOT(setPlainTextDataFiltered(QByteArray)));
connect(command, SIGNAL(errorText(QString)), outputWindow, SLOT(appendError(QString)));
return command;
}
// Execute a single command
GitCommand *GitClient::executeGit(const QString &workingDirectory,
const QStringList &arguments,
VCSBase::VCSBaseEditor* editor,
bool outputToWindow,
GitCommand::TerminationReportMode tm,
int editorLineNumber)
VCSBase::VCSBaseOutputWindow::instance()->appendCommand(formatCommand(QLatin1String(Constants::GIT_BINARY), arguments));
GitCommand *command = createCommand(workingDirectory, editor, outputToWindow, editorLineNumber);
command->addJob(arguments, m_settings.timeoutSeconds);
command->setTerminationReportMode(tm);
command->execute();
return command;
// 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;