Newer
Older
/**************************************************************************
** Copyright (c) 2010 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/synchronousprocess.h>

Tuomas Puranen
committed
#include <utils/environment.h>
#include <vcsbase/vcsbaseoutputwindow.h>
#include <vcsbase/vcsbaseplugin.h>
#include <QtCore/QTime>
#include <QtCore/QFileInfo>
#include <QtCore/QDir>
#include <QtCore/QSignalMapper>
#include <QtGui/QMainWindow> // for msg box parent

Friedemann Kleint
committed
static const char *const kGitDirectoryC = ".git";
static const char *const kBranchIndicatorC = "# On branch";
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
namespace Git {
namespace Internal {
BaseGitArgumentsWidget::BaseGitArgumentsWidget(GitSettings *settings,
Git::Internal::GitClient *client,
const QString &directory,
const QStringList &args) :
QWidget(0),
m_client(client),
m_workingDirectory(directory),
m_diffArgs(args),
m_settings(settings)
{
Q_ASSERT(settings);
Q_ASSERT(client);
}
} // namespace Internal
} // namespace Git
namespace {
class BaseGitDiffArgumentsWidget : public Git::Internal::BaseGitArgumentsWidget
{
public:
BaseGitDiffArgumentsWidget(Git::Internal::GitSettings *settings,
Git::Internal::GitClient *client,
const QString &directory,
const QStringList &args) :
BaseGitArgumentsWidget(settings, client, directory, args),
m_patience(0),
m_ignoreSpaces(0)
{
QHBoxLayout * layout = new QHBoxLayout(this);
layout->setContentsMargins(3, 0, 3, 0);
m_patience = new QToolButton;
m_patience->setToolTip(tr("Use the patience algorithmn for calculating the diff"));
m_patience->setText(tr("Patience"));
layout->addWidget(m_patience);
m_patience->setCheckable(true);
m_patience->setChecked(m_settings->diffPatience);
connect(m_patience, SIGNAL(toggled(bool)), this, SLOT(testForArgumentsChanged()));
m_ignoreSpaces = new QToolButton;
m_ignoreSpaces->setToolTip(tr("Ignore whitespaces only changes"));
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
m_ignoreSpaces->setText(tr("Ignore Whitespace"));
layout->addWidget(m_ignoreSpaces);
m_ignoreSpaces->setCheckable(true);
m_ignoreSpaces->setChecked(m_settings->ignoreSpaceChangesInDiff);
connect(m_ignoreSpaces, SIGNAL(toggled(bool)), this, SLOT(testForArgumentsChanged()));
}
QStringList arguments() const
{
QStringList args = m_diffArgs;
args.removeAll(QLatin1String("--patience"));
args.removeAll(QLatin1String("--ignore-space-change"));
if (m_patience->isChecked())
args.prepend(QLatin1String("--patience"));
if (m_ignoreSpaces->isChecked())
args.prepend(QLatin1String("--ignore-space-change"));
return args;
}
void testForArgumentsChanged() {
m_settings->diffPatience = m_patience->isChecked();
m_settings->ignoreSpaceChangesInDiff = m_ignoreSpaces->isChecked();
QStringList newArguments = arguments();
if (newArguments == m_diffArgs)
return;
m_diffArgs = newArguments;
redoCommand();
}
private:
QToolButton *m_patience;
QToolButton *m_ignoreSpaces;
};
class GitCommitDiffArgumentsWidget : public BaseGitDiffArgumentsWidget
{
public:
GitCommitDiffArgumentsWidget(Git::Internal::GitSettings *settings,
Git::Internal::GitClient *client, const QString &directory,
const QStringList &args, const QStringList &unstaged,
const QStringList &staged) :
BaseGitDiffArgumentsWidget(settings, client, directory, args),
m_unstagedFileNames(unstaged),
m_stagedFileNames(staged)
{ }
void redoCommand()
{
m_client->diff(m_workingDirectory, m_diffArgs, m_unstagedFileNames, m_stagedFileNames);
}
private:
const QStringList m_unstagedFileNames;
const QStringList m_stagedFileNames;
};
class GitFileDiffArgumentsWidget : public BaseGitDiffArgumentsWidget
{
public:
GitFileDiffArgumentsWidget(Git::Internal::GitSettings *settings,
Git::Internal::GitClient *client, const QString &directory,
const QStringList &args, const QString &file) :
BaseGitDiffArgumentsWidget(settings, client, directory, args),
m_fileName(file)
{ }
void redoCommand()
{
m_client->diff(m_workingDirectory, m_diffArgs, m_fileName);
}
private:
const QString m_fileName;
};
class GitBranchDiffArgumentsWidget : public BaseGitDiffArgumentsWidget
{
public:
GitBranchDiffArgumentsWidget(Git::Internal::GitSettings *settings,
Git::Internal::GitClient *client, const QString &directory,
const QStringList &args, const QString &branch) :
BaseGitDiffArgumentsWidget(settings, client, directory, args),
m_branchName(branch)
{ }
void redoCommand()
{
m_client->diffBranch(m_workingDirectory, m_diffArgs, m_branchName);
}
private:
const QString m_branchName;
};
class GitBlameArgumentsWidget : public Git::Internal::BaseGitArgumentsWidget
{
public:
GitBlameArgumentsWidget(Git::Internal::GitSettings *settings,
Git::Internal::GitClient *client, const QString &directory,
const QStringList &args, const QString &revision,
const QString &fileName) :
Git::Internal::BaseGitArgumentsWidget(settings, client, directory, args),
m_omitDate(0),
m_ignoreSpaces(0),
m_editor(0),
m_revision(revision),
m_fileName(fileName)
{
QHBoxLayout * layout = new QHBoxLayout(this);
layout->setContentsMargins(3, 0, 3, 0);
m_omitDate = new QToolButton;
m_omitDate->setToolTip(tr("Do not show the date a change was made in the output"));
m_omitDate->setText(tr("Omit Date"));
layout->addWidget(m_omitDate);
m_omitDate->setCheckable(true);
m_omitDate->setChecked(m_settings->omitAnnotationDate);
m_omitDate->setMinimumHeight(16);
m_omitDate->setMaximumHeight(16);
connect(m_omitDate, SIGNAL(toggled(bool)), this, SLOT(testForArgumentsChanged()));
m_ignoreSpaces = new QToolButton;
m_ignoreSpaces->setToolTip(tr("Ignore whitespace only changes"));
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
m_ignoreSpaces->setText(tr("Ignore Whitespace"));
layout->addWidget(m_ignoreSpaces);
m_ignoreSpaces->setCheckable(true);
m_ignoreSpaces->setChecked(m_settings->ignoreSpaceChangesInBlame);
m_ignoreSpaces->setMinimumHeight(16);
m_ignoreSpaces->setMaximumHeight(16);
connect(m_ignoreSpaces, SIGNAL(toggled(bool)), this, SLOT(testForArgumentsChanged()));
}
void setEditor(VCSBase::VCSBaseEditor *editor)
{
Q_ASSERT(editor);
m_editor = editor;
}
QStringList arguments() const
{
QStringList args = m_diffArgs;
args.removeAll(QLatin1String("-w"));
if (m_ignoreSpaces->isChecked())
args.prepend(QLatin1String("-w"));
return args;
}
void testForArgumentsChanged() {
m_settings->omitAnnotationDate = m_omitDate->isChecked();
m_settings->ignoreSpaceChangesInBlame = m_ignoreSpaces->isChecked();
m_diffArgs = arguments();
redoCommand(); // always redo for omit date
}
void redoCommand()
{
m_client->blame(m_workingDirectory, m_diffArgs, m_fileName,
m_revision, m_editor->lineNumberOfCurrentEditor());
}
private:
QToolButton *m_omitDate;
QToolButton *m_ignoreSpaces;
VCSBase::VCSBaseEditor *m_editor;
QString m_revision;
QString m_fileName;
};
} // namespace
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);
}
static inline VCSBase::VCSBaseOutputWindow *outputWindow()
{
return VCSBase::VCSBaseOutputWindow::instance();
}
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.");
}
// ---------------- 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";
QString GitClient::findRepositoryForDirectory(const QString &dir)
{
// Check for ".git/config"
const QString checkFile = QLatin1String(kGitDirectoryC) + QLatin1String("/config");
return VCSBase::VCSBasePlugin::findRepositoryForDirectory(dir, checkFile);
VCSBase::VCSBaseEditor *GitClient::findExistingVCSEditor(const char *registerDynamicProperty,
const QString &dynamicPropertyValue) const
{
VCSBase::VCSBaseEditor *rc = 0;
Core::IEditor *outputEditor = locateEditor(m_core, registerDynamicProperty, dynamicPropertyValue);
if (!outputEditor)
return 0;
// Exists already
outputEditor->createNew(m_msgWait);
rc = VCSBase::VCSBaseEditor::getVcsBaseEditor(outputEditor);
return rc;
}
/* 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,
QWidget *configWidget) const
Q_ASSERT(!findExistingVCSEditor(registerDynamicProperty, dynamicPropertyValue));
// Create new, set wait message, set up with source and codec
Core::IEditor *outputEditor = m_core->editorManager()->openEditorWithContents(id, &title, m_msgWait);
outputEditor->file()->setProperty(registerDynamicProperty, dynamicPropertyValue);
rc = VCSBase::VCSBaseEditor::getVcsBaseEditor(outputEditor);
connect(rc, SIGNAL(annotateRevisionRequested(QString,QString,int)),
this, SLOT(slotBlameRevisionRequested(QString,QString,int)));
QTC_ASSERT(rc, return 0);
rc->setSource(source);
if (setSourceCodec)
rc->setCodec(VCSBase::VCSBaseEditor::getCodec(source));
m_core->editorManager()->activateEditor(outputEditor, Core::EditorManager::ModeSwitch);
rc->setForceReadOnly(true);
if (configWidget)
rc->setConfigurationWidget(configWidget);

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);
QStringList userDiffArgs = diffArgs;
VCSBase::VCSBaseEditor *editor = findExistingVCSEditor("originalFileName", workingDirectory);
if (!editor) {
GitCommitDiffArgumentsWidget *argWidget =
new GitCommitDiffArgumentsWidget(&m_settings, this, workingDirectory, diffArgs,
unstagedFileNames, stagedFileNames);
userDiffArgs = argWidget->arguments();
editor = createVCSEditor(editorId, title,
workingDirectory, true, "originalFileName", workingDirectory, argWidget);
}
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?
QStringList cmdArgs;
cmdArgs << QLatin1String("diff") << QLatin1String(noColorOption);
if (unstagedFileNames.empty() && stagedFileNames.empty()) {
QStringList arguments(cmdArgs);
arguments << userDiffArgs;
outputWindow()->appendCommand(workingDirectory, binary, arguments);
command->addJob(arguments, m_settings.timeoutSeconds);
} else {
// Files diff.
if (!unstagedFileNames.empty()) {
QStringList arguments(cmdArgs);
arguments << userDiffArgs;

Friedemann Kleint
committed
arguments << QLatin1String("--") << unstagedFileNames;
outputWindow()->appendCommand(workingDirectory, binary, arguments);
command->addJob(arguments, m_settings.timeoutSeconds);
}
if (!stagedFileNames.empty()) {
QStringList arguments(cmdArgs);
arguments << userDiffArgs;

Friedemann Kleint
committed
arguments << QLatin1String("--cached") << diffArgs << QLatin1String("--") << stagedFileNames;
outputWindow()->appendCommand(workingDirectory, 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 << diffArgs;
const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, fileName);
QStringList userDiffArgs = diffArgs;
VCSBase::VCSBaseEditor *editor = findExistingVCSEditor("originalFileName", sourceFile);
if (!editor) {
GitFileDiffArgumentsWidget *argWidget =
new GitFileDiffArgumentsWidget(&m_settings, this, workingDirectory,
diffArgs, fileName);
userDiffArgs = argWidget->arguments();
editor = createVCSEditor(editorId, title, sourceFile, true, "originalFileName", sourceFile, argWidget);
}
QStringList cmdArgs;
cmdArgs << QLatin1String("diff") << QLatin1String(noColorOption)
<< userDiffArgs;
if (!fileName.isEmpty())
cmdArgs << QLatin1String("--") << fileName;
executeGit(workingDirectory, cmdArgs, editor);
void GitClient::diffBranch(const QString &workingDirectory,
const QStringList &diffArgs,
const QString &branchName)
{
if (Git::Constants::debug)
qDebug() << "diffBranch" << workingDirectory << 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());
QStringList userDiffArgs = diffArgs;
VCSBase::VCSBaseEditor *editor = findExistingVCSEditor("BranchName", branchName);
if (!editor) {
GitBranchDiffArgumentsWidget *argWidget =
new GitBranchDiffArgumentsWidget(&m_settings, this, workingDirectory,
diffArgs, branchName);
userDiffArgs = argWidget->arguments();
editor = createVCSEditor(editorId, title, sourceFile, true, "BranchName", branchName, argWidget);
}
QStringList cmdArgs;
cmdArgs << QLatin1String("diff") << QLatin1String(noColorOption)
<< diffArgs << branchName;
executeGit(workingDirectory, cmdArgs, editor);

Friedemann Kleint
committed
// @TODO: Use "--no-color" once it is supported
QStringList statusArgs(QLatin1String("status"));
statusArgs << QLatin1String("-u");
VCSBase::VCSBaseOutputWindow *outwin = outputWindow();
outwin->setRepository(workingDirectory);
GitCommand *command = executeGit(workingDirectory, statusArgs, 0, true);
connect(command, SIGNAL(finished(bool,int,QVariant)), outwin, SLOT(clearRepository()),
Qt::QueuedConnection);
static const char graphLogFormatC[] = "%h %an %s %ci";
// Create a graphical log.
void GitClient::graphLog(const QString &workingDirectory, const QString & branch)
{
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");
QString title;
if (branch.isEmpty()) {
title = tr("Git Log");
} else {
title = tr("Git Log %1").arg(branch);
arguments << branch;
}
const QString editorId = QLatin1String(Git::Constants::GIT_LOG_EDITOR_ID);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, QStringList());
VCSBase::VCSBaseEditor *editor = findExistingVCSEditor("logFileName", sourceFile);
if (!editor)
editor = createVCSEditor(editorId, title, sourceFile, false, "logFileName", sourceFile, 0);
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 = findExistingVCSEditor("logFileName", sourceFile);
if (!editor)
editor = createVCSEditor(editorId, title, sourceFile, false, "logFileName", sourceFile, 0);
editor->setFileLogAnnotateEnabled(enableAnnotationContextMenu);
executeGit(workingDirectory, arguments, editor);
// 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);
}
void GitClient::show(const QString &source, const QString &id)
{
if (Git::Constants::debug)
qDebug() << "show" << source << id;
if (!canShow(id)) {
outputWindow()->append(msgCannotShow(id));
return;
}

Friedemann Kleint
committed
QStringList arguments;
arguments << QLatin1String("show") << QLatin1String(noColorOption) << id;
const QString editorId = QLatin1String(Git::Constants::GIT_DIFF_EDITOR_ID);
VCSBase::VCSBaseEditor *editor = findExistingVCSEditor("show", id);
if (!editor)
editor = createVCSEditor(editorId, title, source, true, "show", id, 0);
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(), QStringList(), 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 << args;
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);
QStringList userBlameArgs = args;
VCSBase::VCSBaseEditor *editor = findExistingVCSEditor("blameFileName", id);
if (!editor) {
GitBlameArgumentsWidget *argWidget =
new GitBlameArgumentsWidget(&m_settings, this, workingDirectory, userBlameArgs,
revision, fileName);
editor = createVCSEditor(editorId, title, sourceFile, true, "blameFileName", id, argWidget);
argWidget->setEditor(editor);
userBlameArgs = argWidget->arguments();
}
QStringList arguments(QLatin1String("blame"));
arguments << QLatin1String("--root");
arguments.append(userBlameArgs);
arguments << QLatin1String("--") << fileName;
if (!revision.isEmpty())
arguments << revision;
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 = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
const QString output = commandOutputFromLocal8Bit(outputText);
outputWindow()->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 {
outputWindow()->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 = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
if (!rc) {
const QString errorMessage = tr("Unable to add %n file(s) to %1: %2", 0, files.size()).
arg(QDir::toNativeSeparators(workingDirectory),
commandOutputFromLocal8Bit(errorText));
outputWindow()->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 = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
if (!rc) {
const QString errorMessage = tr("Unable to remove %n file(s) from %1: %2", 0, files.size()).
arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
outputWindow()->appendError(errorMessage);
}
return rc;
}
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);
const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
if (!rc) {
const QString errorMessage = tr("Unable to move from %1 to %2: %3").
arg(from, to, commandOutputFromLocal8Bit(errorText));
outputWindow()->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 = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
const QString output = commandOutputFromLocal8Bit(outputText);
outputWindow()->append(output);
// Note that git exits with 1 even if the operation is successful
// Assume real failure if the output does not contain "foo.cpp modified"
// or "Unstaged changes after reset" (git 1.7.0).
if (!rc &&
(!output.contains(QLatin1String("modified"))
&& !output.contains(QLatin1String("Unstaged changes after reset")))) {
const QString stdErr = commandOutputFromLocal8Bit(errorText);
const QString msg = files.isEmpty() ?
tr("Unable to reset %1: %2").arg(QDir::toNativeSeparators(workingDirectory), stdErr) :
tr("Unable to reset %n file(s) in %1: %2", 0, files.size()).
arg(QDir::toNativeSeparators(workingDirectory), stdErr);
if (errorMessage) {
*errorMessage = msg;
} else {
outputWindow()->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 = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
// '[Re]Initialized...'
outputWindow()->append(commandOutputFromLocal8Bit(outputText));
outputWindow()->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 */,
bool revertStaging /* = true */)
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory << files;
if (revertStaging && revision.isEmpty())
revision = QLatin1String("HEAD");
if (files.isEmpty())
files = QStringList(QString(QLatin1Char('.')));
QByteArray outputText;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("checkout");
if (revertStaging)
arguments << revision;
arguments << QLatin1String("--") << files;
const bool rc = fullySynchronousGit(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 {
outputWindow()->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"
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
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 = fullySynchronousGit(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);
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;
}