Newer
Older
const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
*errorMessage = tr("Unable to run 'git clean' in %1: %2").arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
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;
}
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;
const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
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;
}
// 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,int,QVariant)), editor, SLOT(commandFinishedGotoLine(bool,int,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,
bool unixTerminalDisabled)
outputWindow()->appendCommand(workingDirectory, QLatin1String(Constants::GIT_BINARY), arguments);
GitCommand *command = createCommand(workingDirectory, editor, outputToWindow, editorLineNumber);
command->addJob(arguments, m_settings.timeoutSeconds);
command->setTerminationReportMode(tm);
command->setUnixTerminalDisabled(unixTerminalDisabled);
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;
#else
return QStringList(m_binaryPath);
#endif
}
QProcessEnvironment GitClient::processEnvironment() const
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
if (m_settings.adoptPath)
environment.insert(QLatin1String("PATH"), m_settings.path);
// Set up SSH and C locale (required by git using perl).
VCSBase::VCSBasePlugin::setProcessEnvironment(&environment, false);
return environment;
}
// 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,
QByteArray* outputText,
QByteArray* errorText,
bool logCommandToWindow)
qDebug() << "fullySynchronousGit" << workingDirectory << gitArguments;
if (logCommandToWindow)
outputWindow()->appendCommand(workingDirectory, m_binaryPath, gitArguments);
QProcess process;
process.setProcessEnvironment(processEnvironment());
QStringList args = binary(); // "cmd /c git" on Windows
const QString executable = args.front();
args.pop_front();
args.append(gitArguments);
process.start(executable, args);
process.closeWriteChannel();
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;
}
if (!Utils::SynchronousProcess::readDataFromProcess(process, m_settings.timeoutSeconds * 1000,
outputText, errorText, true)) {
errorText->append(GitCommand::msgTimeout(m_settings.timeoutSeconds).toLocal8Bit());
Utils::SynchronousProcess::stopProcess(process);
qDebug() << "synchronousGit ex=" << process.exitStatus() << process.exitCode();
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;

Friedemann Kleint
committed
static inline int
askWithDetailedText(QWidget *parent,
const QString &title, const QString &msg,
const QString &inf,
QMessageBox::StandardButton defaultButton,
QMessageBox::StandardButtons buttons = QMessageBox::Yes|QMessageBox::No)
{
QMessageBox msgBox(QMessageBox::Question, title, msg, buttons, parent);
msgBox.setDetailedText(inf);
msgBox.setDefaultButton(defaultButton);
return msgBox.exec();
}
// Convenience that pops up an msg box.
GitClient::StashResult GitClient::ensureStash(const QString &workingDirectory)
{
QString errorMessage;
const StashResult sr = ensureStash(workingDirectory, &errorMessage);
if (sr == StashFailed)
outputWindow()->appendError(errorMessage);

Friedemann Kleint
committed
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
return sr;
}
// Ensure that changed files are stashed before a pull or similar
GitClient::StashResult GitClient::ensureStash(const QString &workingDirectory, QString *errorMessage)
{
QString statusOutput;
switch (gitStatus(workingDirectory, false, &statusOutput, errorMessage)) {
case StatusChanged:
break;
case StatusUnchanged:
return StashUnchanged;
case StatusFailed:
return StashFailed;
}
const int answer = askWithDetailedText(m_core->mainWindow(), tr("Changes"),
tr("You have modified files. Would you like to stash your changes?"),
statusOutput, QMessageBox::Yes, QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel);
switch (answer) {
case QMessageBox::Cancel:
return StashCanceled;
case QMessageBox::Yes:
if (!executeSynchronousStash(workingDirectory, creatorStashMessage(QLatin1String("push")), errorMessage))

Friedemann Kleint
committed
return StashFailed;
break;
case QMessageBox::No: // At your own risk, so.
return NotStashed;
}
return Stashed;
}
// Trim a git status file spec: "modified: foo .cpp" -> "modified: foo .cpp"
static inline QString trimFileSpecification(QString fileSpec)
{
const int colonIndex = fileSpec.indexOf(QLatin1Char(':'));
if (colonIndex != -1) {
// Collapse the sequence of spaces
const int filePos = colonIndex + 2;
int nonBlankPos = filePos;
for ( ; fileSpec.at(nonBlankPos).isSpace(); nonBlankPos++) ;
if (nonBlankPos > filePos)
fileSpec.remove(filePos, nonBlankPos - filePos);
}
return fileSpec;
}
GitClient::StatusResult GitClient::gitStatus(const QString &workingDirectory,
bool untracked,
QString *output,

Robert Loehning
committed
QString *errorMessage,
bool *onBranch)
{
// Run 'status'. Note that git returns exitcode 1 if there are no added files.
QByteArray outputText;
QByteArray errorText;

Friedemann Kleint
committed
// @TODO: Use "--no-color" once it is supported
QStringList statusArgs(QLatin1String("status"));
if (untracked)
statusArgs << QLatin1String("-u");
const bool statusRc = fullySynchronousGit(workingDirectory, statusArgs, &outputText, &errorText);

Friedemann Kleint
committed
GitCommand::removeColorCodes(&outputText);
if (output)
*output = commandOutputFromLocal8Bit(outputText);

Robert Loehning
committed
const bool branchKnown = outputText.contains(kBranchIndicatorC);
if (onBranch)
*onBranch = branchKnown;
// Is it something really fatal?

Robert Loehning
committed
if (!statusRc && !branchKnown && !outputText.contains("# Not currently on any branch.")) {
if (errorMessage) {
const QString error = commandOutputFromLocal8Bit(errorText);
*errorMessage = tr("Unable to obtain the status: %1").arg(error);
}
return StatusFailed;
}
// Unchanged (output text depending on whether -u was passed)
if (outputText.contains("nothing to commit")
|| outputText.contains("nothing added to commit but untracked files present"))
return StatusUnchanged;
return StatusChanged;
}
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
// Quietly retrieve branch list of remote repository URL
QStringList GitClient::synchronousRepositoryBranches(const QString &repositoryURL)
{
QStringList arguments(QLatin1String("ls-remote"));
arguments << QLatin1String("--heads") << repositoryURL;
const unsigned flags =
VCSBase::VCSBasePlugin::SshPasswordPrompt|
VCSBase::VCSBasePlugin::SuppressStdErrInLogWindow|
VCSBase::VCSBasePlugin::SuppressFailMessageInLogWindow;
const Utils::SynchronousProcessResponse resp = synchronousGit(QString(), arguments, flags);
QStringList branches;
if (resp.result == Utils::SynchronousProcessResponse::Finished) {
// split "82bfad2f51d34e98b18982211c82220b8db049b<tab>refs/heads/master"
foreach(const QString &line, resp.stdOut.split(QLatin1Char('\n'))) {
const int slashPos = line.lastIndexOf(QLatin1Char('/'));
if (slashPos != -1)
branches.push_back(line.mid(slashPos + 1));
}
}
return branches;
}
void GitClient::launchGitK(const QString &workingDirectory)
{
VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
// Locate git in (potentially) custom path. m_binaryPath can be absolute,
// which will be handled correctly.
QTC_ASSERT(!m_binaryPath.isEmpty(), return);
const QString gitBinary = QLatin1String(Constants::GIT_BINARY);
const QProcessEnvironment env = processEnvironment();
const QString path = env.value(QLatin1String("PATH"));
const QString fullGitBinary = Utils::SynchronousProcess::locateBinary(path, m_binaryPath);
if (fullGitBinary.isEmpty()) {
outwin->appendError(tr("Cannot locate %1.").arg(gitBinary));
return;
}
const QString gitBinDirectory = QFileInfo(fullGitBinary).absolutePath();
#ifdef Q_OS_WIN
// Launch 'wish' shell from git binary directory with the gitk located there
const QString binary = gitBinDirectory + QLatin1String("/wish");
QStringList arguments(gitBinDirectory + QLatin1String("/gitk"));
#else
// Simple: Run gitk from binary path
const QString binary = gitBinDirectory + QLatin1String("/gitk");
QStringList arguments;
if (!m_settings.gitkOptions.isEmpty())
arguments.append(m_settings.gitkOptions.split(QLatin1Char(' ')));
outwin->appendCommand(workingDirectory, binary, arguments);
// This should always use QProcess::startDetached (as not to kill
// the child), but that does not have an environment parameter.
bool success = false;
if (m_settings.adoptPath) {
QProcess *process = new QProcess(this);
process->setWorkingDirectory(workingDirectory);
process->setProcessEnvironment(env);
process->start(binary, arguments);
success = process->waitForStarted();
if (success) {
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
} else {
delete process;
}
} else {
success = QProcess::startDetached(binary, arguments, workingDirectory);
if (!success)
outwin->appendError(tr("Unable to launch %1.").arg(binary));
bool GitClient::getCommitData(const QString &workingDirectory,
QString *commitTemplate,
CommitData *d,
QString *errorMessage)
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory;
d->clear();
// Find repo
const QString repoDirectory = GitClient::findRepositoryForDirectory(workingDirectory);
if (repoDirectory.isEmpty()) {
*errorMessage = msgRepositoryNotFound(workingDirectory);
return false;
}
d->panelInfo.repository = repoDirectory;
QDir gitDir(repoDirectory);
if (!gitDir.cd(QLatin1String(kGitDirectoryC))) {
*errorMessage = tr("The repository %1 is not initialized yet.").arg(repoDirectory);
return false;
}
// Read description
const QString descriptionFile = gitDir.absoluteFilePath(QLatin1String("description"));
if (QFileInfo(descriptionFile).isFile()) {
QFile file(descriptionFile);
if (file.open(QIODevice::ReadOnly|QIODevice::Text))
d->panelInfo.description = commandOutputFromLocal8Bit(file.readAll()).trimmed();
}
// Run status. Note that it has exitcode 1 if there are no added files.

Robert Loehning
committed
bool onBranch;
QString output;

Robert Loehning
committed
switch (gitStatus(repoDirectory, true, &output, errorMessage, &onBranch)) {
case StatusChanged:

Robert Loehning
committed
if (!onBranch) {
*errorMessage = tr("You did not checkout a branch.");
return false;
}
break;
case StatusUnchanged:
*errorMessage = msgNoChangedFiles();
return false;
case StatusFailed:
return false;
}
// Output looks like:
// # On branch [branchname]
// # Changes to be committed:
// # (use "git reset HEAD <file>..." to unstage)
// #
// # modified: somefile.cpp
// # new File: somenew.h
// #
// # Changed but not updated:
// # (use "git add <file>..." to update what will be committed)
// #
// # modified: someother.cpp
// #
// # Untracked files:
// # (use "git add <file>..." to include in what will be committed)
// #
// # list of files...

Friedemann Kleint
committed
if (!d->parseFilesFromStatus(output)) {
*errorMessage = msgParseFilesFailed();

Friedemann Kleint
committed
// Filter out untracked files that are not part of the project
VCSBase::VCSBaseSubmitEditor::filterUntrackedFilesOfProject(repoDirectory, &d->untrackedFiles);

Friedemann Kleint
committed
if (d->filesEmpty()) {
*errorMessage = msgNoChangedFiles();
return false;
}
d->panelData.author = readConfigValue(workingDirectory, QLatin1String("user.name"));
d->panelData.email = readConfigValue(workingDirectory, QLatin1String("user.email"));
// Get the commit template
QString templateFilename = readConfigValue(workingDirectory, QLatin1String("commit.template"));
// Make relative to repository
const QFileInfo templateFileInfo(templateFilename);
if (templateFileInfo.isRelative())
templateFilename = repoDirectory + QLatin1Char('/') + templateFilename;
QFile templateFile(templateFilename);
if (templateFile.open(QIODevice::ReadOnly|QIODevice::Text)) {
*commitTemplate = QString::fromLocal8Bit(templateFile.readAll());
} else {
qWarning("Unable to read commit template %s: %s",
qPrintable(templateFilename),
qPrintable(templateFile.errorString()));
}
}
return true;
}
bool GitClient::addAndCommit(const QString &repositoryDirectory,
const GitSubmitEditorPanelData &data,
const QString &messageFile,
const QStringList &checkedFiles,
const QStringList &origCommitFiles,
const QStringList &origDeletedFiles)
qDebug() << "GitClient::addAndCommit:" << repositoryDirectory << checkedFiles << origCommitFiles;
const QString renamedSeparator = QLatin1String(" -> ");
// Do we need to reset any files that had been added before
// (did the user uncheck any previously added files)
// Split up renamed files ('foo.cpp -> foo2.cpp').
QStringList resetFiles = origCommitFiles.toSet().subtract(checkedFiles.toSet()).toList();
for (QStringList::iterator it = resetFiles.begin(); it != resetFiles.end(); ++it) {
const int renamedPos = it->indexOf(renamedSeparator);
if (renamedPos != -1) {
const QString newFile = it->mid(renamedPos + renamedSeparator.size());
it->truncate(renamedPos);
it = resetFiles.insert(++it, newFile);
}
}
if (!resetFiles.isEmpty())
if (!synchronousReset(repositoryDirectory, resetFiles))
// Re-add all to make sure we have the latest changes, but only add those that aren't marked
// for deletion. Purge out renamed files ('foo.cpp -> foo2.cpp').
QStringList addFiles = checkedFiles.toSet().subtract(origDeletedFiles.toSet()).toList();
for (QStringList::iterator it = addFiles.begin(); it != addFiles.end(); ) {
if (it->contains(renamedSeparator)) {
it = addFiles.erase(it);
} else {
++it;
}
}
if (!addFiles.isEmpty())
if (!synchronousAdd(repositoryDirectory, false, addFiles))
return false;
// Do the final commit
QStringList args;
args << QLatin1String("commit")
<< QLatin1String("-F") << QDir::toNativeSeparators(messageFile);
const QString &authorString = data.authorString();
if (!authorString.isEmpty())
args << QLatin1String("--author") << authorString;
const bool rc = fullySynchronousGit(repositoryDirectory, args, &outputText, &errorText);
if (rc) {
outputWindow()->append(tr("Committed %n file(s).\n", 0, checkedFiles.size()));
} else {
outputWindow()->appendError(tr("Unable to commit %n file(s): %1\n", 0, checkedFiles.size()).arg(commandOutputFromLocal8Bit(errorText)));
/* Revert: This function can be called with a file list (to revert single
* files) or a single directory (revert all). Qt Creator currently has only
* 'revert single' in its VCS menus, but the code is prepared to deal with
* reverting a directory pending a sophisticated selection dialog in the
* VCSBase plugin. */
GitClient::RevertResult GitClient::revertI(QStringList files,
bool *ptrToIsDirectory,
QString *errorMessage,
bool revertStaging)
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
{
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << files;
if (files.empty())
return RevertCanceled;
// Figure out the working directory
const QFileInfo firstFile(files.front());
const bool isDirectory = firstFile.isDir();
if (ptrToIsDirectory)
*ptrToIsDirectory = isDirectory;
const QString workingDirectory = isDirectory ? firstFile.absoluteFilePath() : firstFile.absolutePath();
const QString repoDirectory = GitClient::findRepositoryForDirectory(workingDirectory);
if (repoDirectory.isEmpty()) {
*errorMessage = msgRepositoryNotFound(workingDirectory);
return RevertFailed;
}
// Check for changes
QString output;
switch (gitStatus(repoDirectory, false, &output, errorMessage)) {
case StatusChanged:
break;
case StatusUnchanged:
return RevertUnchanged;
case StatusFailed:
return RevertFailed;
}

Friedemann Kleint
committed
if (!data.parseFilesFromStatus(output)) {
*errorMessage = msgParseFilesFailed();
return RevertFailed;
}
// If we are looking at files, make them relative to the repository
// directory to match them in the status output list.
if (!isDirectory) {
const QDir repoDir(repoDirectory);
const QStringList::iterator cend = files.end();
for (QStringList::iterator it = files.begin(); it != cend; ++it)
*it = repoDir.relativeFilePath(*it);
}
// From the status output, determine all modified [un]staged files.

Friedemann Kleint
committed
const QString modifiedState = QLatin1String("modified");
const QStringList allStagedFiles = data.stagedFileNames(modifiedState);
const QStringList allUnstagedFiles = data.unstagedFileNames(modifiedState);
// Unless a directory was passed, filter all modified files for the
// argument file list.
QStringList stagedFiles = allStagedFiles;
QStringList unstagedFiles = allUnstagedFiles;
if (!isDirectory) {
const QSet<QString> filesSet = files.toSet();
stagedFiles = allStagedFiles.toSet().intersect(filesSet).toList();
unstagedFiles = allUnstagedFiles.toSet().intersect(filesSet).toList();
}
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << data.stagedFiles << data.unstagedFiles << allStagedFiles << allUnstagedFiles << stagedFiles << unstagedFiles;
if ((!revertStaging || stagedFiles.empty()) && unstagedFiles.empty())
return RevertUnchanged;
// Ask to revert (to do: Handle lists with a selection dialog)
const QMessageBox::StandardButton answer
= QMessageBox::question(m_core->mainWindow(),
tr("Revert"),
tr("The file has been changed. Do you want to revert it?"),
QMessageBox::Yes|QMessageBox::No,
QMessageBox::No);
if (answer == QMessageBox::No)
return RevertCanceled;
// Unstage the staged files
if (revertStaging && !stagedFiles.empty() && !synchronousReset(repoDirectory, stagedFiles, errorMessage))
return RevertFailed;
QStringList filesToRevert = unstagedFiles;
if (revertStaging)
filesToRevert += stagedFiles;
// Finally revert!
if (!synchronousCheckoutFiles(repoDirectory, filesToRevert, QString(), errorMessage, revertStaging))
return RevertFailed;
return RevertOk;
}
void GitClient::revert(const QStringList &files, bool revertStaging)
{
bool isDirectory;
QString errorMessage;
switch (revertI(files, &isDirectory, &errorMessage, revertStaging)) {
case RevertOk:
m_plugin->gitVersionControl()->emitFilesChanged(files);
case RevertCanceled:
break;
case RevertUnchanged: {
const QString msg = (isDirectory || files.size() > 1) ? msgNoChangedFiles() : tr("The file is not modified.");
outputWindow()->append(msg);
}
break;
case RevertFailed:
outputWindow()->append(errorMessage);
break;
}
}
bool GitClient::synchronousPull(const QString &workingDirectory)
return synchronousPull(workingDirectory, m_settings.pullRebase);
bool GitClient::synchronousPull(const QString &workingDirectory, bool rebase)
{
QStringList arguments(QLatin1String("pull"));
if (rebase)
arguments << QLatin1String("--rebase");
// Disable UNIX terminals to suppress SSH prompting.
const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
|VCSBase::VCSBasePlugin::ShowSuccessMessage;
const Utils::SynchronousProcessResponse resp = synchronousGit(workingDirectory, arguments, flags);
// Notify about changed files or abort the rebase.
const bool ok = resp.result == Utils::SynchronousProcessResponse::Finished;
if (ok) {
GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
} else {
if (rebase)
syncAbortPullRebase(workingDirectory);
void GitClient::syncAbortPullRebase(const QString &workingDir)
{
// Abort rebase to clean if something goes wrong
VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
outwin->appendError(tr("The command 'git pull --rebase' failed, aborting rebase."));
QStringList arguments;
arguments << QLatin1String("rebase") << QLatin1String("--abort");
QByteArray stdOut;
QByteArray stdErr;
const bool rc = fullySynchronousGit(workingDir, arguments, &stdOut, &stdErr, true);
outwin->append(commandOutputFromLocal8Bit(stdOut));
if (!rc)
outwin->appendError(commandOutputFromLocal8Bit(stdErr));
// Subversion: git svn
void GitClient::synchronousSubversionFetch(const QString &workingDirectory)
{
QStringList args;
args << QLatin1String("svn") << QLatin1String("fetch");
// Disable UNIX terminals to suppress SSH prompting.
const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
|VCSBase::VCSBasePlugin::ShowSuccessMessage;
const Utils::SynchronousProcessResponse resp = synchronousGit(workingDirectory, args, flags);
// Notify about changes.
if (resp.result == Utils::SynchronousProcessResponse::Finished)
GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
}
void GitClient::subversionLog(const QString &workingDirectory)
{
if (Git::Constants::debug)
qDebug() << "subversionLog" << workingDirectory;
QStringList arguments;
arguments << QLatin1String("svn") << QLatin1String("log");
if (m_settings.logCount > 0)
arguments << (QLatin1String("--limit=") + QString::number(m_settings.logCount));
// Create a command editor, no highlighting or interaction.
const QString title = tr("Git SVN Log");
const QString editorId = QLatin1String(Git::Constants::C_GIT_COMMAND_LOG_EDITOR);
const QString sourceFile = VCSBase::VCSBaseEditor::getSource(workingDirectory, QStringList());
VCSBase::VCSBaseEditor *editor = createVCSEditor(editorId, title, sourceFile, false, "svnLog", sourceFile);
executeGit(workingDirectory, arguments, editor);
}
bool GitClient::synchronousPush(const QString &workingDirectory)
// Disable UNIX terminals to suppress SSH prompting.
const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt|VCSBase::VCSBasePlugin::ShowStdOutInLogWindow
|VCSBase::VCSBasePlugin::ShowSuccessMessage;
const Utils::SynchronousProcessResponse resp =
synchronousGit(workingDirectory, QStringList(QLatin1String("push")), flags);
return resp.result == Utils::SynchronousProcessResponse::Finished;
}
QString GitClient::msgNoChangedFiles()
{
return tr("There are no modified files.");
}
void GitClient::stashPop(const QString &workingDirectory)
{
QStringList arguments(QLatin1String("stash"));
arguments << QLatin1String("pop");
GitCommand *cmd = executeGit(workingDirectory, arguments, 0, true);
connectRepositoryChanged(workingDirectory, cmd);
}
bool GitClient::synchronousStashRestore(const QString &workingDirectory,
const QString &stash,
const QString &branch /* = QString()*/,
QString *errorMessage)
{
QStringList arguments(QLatin1String("stash"));
if (branch.isEmpty()) {
arguments << QLatin1String("apply") << stash;
} else {
arguments << QLatin1String("branch") << branch << stash;
}
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
if (!rc) {
const QString stdErr = commandOutputFromLocal8Bit(errorText);
const QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory);
const QString msg = branch.isEmpty() ?
tr("Unable to restore stash %1: %2").
arg(nativeWorkingDir, stdErr) :
tr("Unable to restore stash %1 to branch %2: %3").
arg(nativeWorkingDir, branch, stdErr);
if (errorMessage) {
*errorMessage = msg;
} else {
outputWindow()->append(msg);
}
return false;
}
QString output = commandOutputFromLocal8Bit(outputText);
if (!output.isEmpty())
outputWindow()->append(output);
GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
return true;
}
bool GitClient::synchronousStashRemove(const QString &workingDirectory,
const QString &stash /* = QString() */,
QString *errorMessage /* = 0 */)
{
QStringList arguments(QLatin1String("stash"));
if (stash.isEmpty()) {
arguments << QLatin1String("clear");
} else {
arguments << QLatin1String("drop") << stash;
}
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
if (!rc) {
const QString stdErr = commandOutputFromLocal8Bit(errorText);
const QString nativeWorkingDir = QDir::toNativeSeparators(workingDirectory);
const QString msg = stash.isEmpty() ?
tr("Unable to remove stashes of %1: %2").
arg(nativeWorkingDir, stdErr) :
tr("Unable to remove stash %1 of %2: %3").
arg(stash, nativeWorkingDir, stdErr);
if (errorMessage) {
*errorMessage = msg;
} else {
outputWindow()->append(msg);
}
return false;
}
QString output = commandOutputFromLocal8Bit(outputText);
if (!output.isEmpty())
outputWindow()->append(output);
void GitClient::branchList(const QString &workingDirectory)
{
QStringList arguments(QLatin1String("branch"));

Friedemann Kleint
committed
arguments << QLatin1String("-r") << QLatin1String(noColorOption);
executeGit(workingDirectory, arguments, 0, true);
}
void GitClient::stashList(const QString &workingDirectory)
{
QStringList arguments(QLatin1String("stash"));

Friedemann Kleint
committed
arguments << QLatin1String("list") << QLatin1String(noColorOption);
executeGit(workingDirectory, arguments, 0, true);
bool GitClient::synchronousStashList(const QString &workingDirectory,
QList<Stash> *stashes,
QString *errorMessage /* = 0 */)
{
stashes->clear();
QStringList arguments(QLatin1String("stash"));
arguments << QLatin1String("list") << QLatin1String(noColorOption);
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);
const QString msg = tr("Unable retrieve stash list of %1: %2").
arg(QDir::toNativeSeparators(workingDirectory),
commandOutputFromLocal8Bit(errorText));
if (errorMessage) {
*errorMessage = msg;
} else {
outputWindow()->append(msg);
}
return false;
}
Stash stash;
foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputText))
if (stash.parseStashLine(line))
stashes->push_back(stash);
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << *stashes;
return true;
}
QString GitClient::readConfig(const QString &workingDirectory, const QStringList &configVar)
{
QStringList arguments;
arguments << QLatin1String("config") << configVar;
QByteArray outputText;
QByteArray errorText;
if (fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText, false))
return commandOutputFromLocal8Bit(outputText);
return QString();
}
// Read a single-line config value, return trimmed
QString GitClient::readConfigValue(const QString &workingDirectory, const QString &configVar)
{
return readConfig(workingDirectory, QStringList(configVar)).remove(QLatin1Char('\n'));
}
GitSettings GitClient::settings() const
{
return m_settings;
}
void GitClient::setSettings(const GitSettings &s)
{
if (s != m_settings) {
m_settings = s;
if (QSettings *coreSettings = m_core->settings())
m_settings.toSettings(coreSettings);

Friedemann Kleint
committed
m_binaryPath = m_settings.gitBinaryPath();
m_cachedGitVersion = 0u;
m_hasCachedGitVersion = false;
}
}
void GitClient::connectRepositoryChanged(const QString & repository, GitCommand *cmd)
{
// Bind command success termination with repository to changed signal
if (!m_repositoryChangedSignalMapper) {
m_repositoryChangedSignalMapper = new QSignalMapper(this);
connect(m_repositoryChangedSignalMapper, SIGNAL(mapped(QString)),
m_plugin->gitVersionControl(), SIGNAL(repositoryChanged(QString)));
}
m_repositoryChangedSignalMapper->setMapping(cmd, repository);
connect(cmd, SIGNAL(success()), m_repositoryChangedSignalMapper, SLOT(map()),
Qt::QueuedConnection);
}
// determine version as '(major << 16) + (minor << 8) + patch' or 0.
unsigned GitClient::gitVersion(bool silent, QString *errorMessage /* = 0 */)
{
if (!m_hasCachedGitVersion) {
// Do not execute repeatedly if that fails (due to git
// not being installed) until settings are changed.
m_cachedGitVersion = synchronousGitVersion(silent, errorMessage);
m_hasCachedGitVersion = true;
}
return m_cachedGitVersion;
QString GitClient::gitVersionString(bool silent, QString *errorMessage)
{
if (const unsigned version = gitVersion(silent, errorMessage)) {
QString rc;
QTextStream(&rc) << (version >> 16) << '.'
<< (0xFF & (version >> 8)) << '.'
<< (version & 0xFF);
return rc;
}
return QString();
}
// determine version as '(major << 16) + (minor << 8) + patch' or 0.
unsigned GitClient::synchronousGitVersion(bool silent, QString *errorMessage /* = 0 */)
{
// run git --version
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(QString(), QStringList("--version"), &outputText, &errorText);
if (!rc) {
const QString msg = tr("Unable to determine git version: %1").arg(commandOutputFromLocal8Bit(errorText));
if (errorMessage) {
*errorMessage = msg;
} else {
outputWindow()->append(msg);
outputWindow()->appendError(msg);
}
return 0;
}
// cut 'git version 1.6.5.1.sha'
const QString output = commandOutputFromLocal8Bit(outputText);
const QRegExp versionPattern(QLatin1String("^[^\\d]+([\\d])\\.([\\d])\\.([\\d]).*$"));
QTC_ASSERT(versionPattern.isValid(), return 0);
QTC_ASSERT(versionPattern.exactMatch(output), return 0);
const unsigned major = versionPattern.cap(1).toUInt();
const unsigned minor = versionPattern.cap(2).toUInt();
const unsigned patch = versionPattern.cap(3).toUInt();
return version(major, minor, patch);
} // namespace Internal
} // namespace Git