Newer
Older
QByteArray errorText;
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
QString GitClient::binary() const
}
// Determine a value for the HOME variable on Windows
// working around MSys git not finding it when run outside git bash
// (then looking for the SSH keys under "\program files\git").
QString GitClient::fakeWinHome(const QProcessEnvironment &e)
{
const QString homeDrive = e.value("HOMEDRIVE");
const QString homePath = e.value("HOMEPATH");
QTC_ASSERT(!homeDrive.isEmpty() && !homePath.isEmpty(), return QString())
return homeDrive + homePath;
}
QProcessEnvironment GitClient::processEnvironment() const
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
if (m_settings.adoptPath)
environment.insert(QLatin1String("PATH"), m_settings.path);
#ifdef Q_OS_WIN
if (m_settings.winSetHomeEnvironment) {
const QString home = fakeWinHome(environment);
if (Constants::debug)
qDebug("Setting home '%s'", qPrintable(home));
environment.insert(QLatin1String("HOME"), home);
}
#endif // Q_OS_WIN
// 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;
return VCSBase::VCSBasePlugin::runVCS(workingDirectory, binary(), gitArguments,
m_settings.timeoutSeconds * 1000,
flags, stdOutCodec);
}
bool GitClient::fullySynchronousGit(const QString &workingDirectory,
const QStringList &gitArguments,
QByteArray* outputText,
QByteArray* errorText,
bool logCommandToWindow)
qDebug() << "fullySynchronousGit" << workingDirectory << gitArguments;
if (logCommandToWindow)
outputWindow()->appendCommand(workingDirectory, m_binaryPath, gitArguments);
QProcess process;
process.setProcessEnvironment(processEnvironment());
process.start(binary(), gitArguments);
process.closeWriteChannel();
if (!process.waitForStarted()) {
if (errorText) {
const QString msg = QString::fromLatin1("Unable to execute '%1': %2:")
.arg(binary(), 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
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
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"))
return StatusUnchanged;
if (outputText.contains("nothing added to commit but untracked files present"))
return untracked ? StatusChanged : StatusUnchanged;
return StatusChanged;
}
// Quietly retrieve branch list of remote repository URL
//
// The branch HEAD is pointing to is always returned first.
QStringList GitClient::synchronousRepositoryBranches(const QString &repositoryURL)
{
QStringList arguments(QLatin1String("ls-remote"));
arguments << repositoryURL << QLatin1String("HEAD") << QLatin1String("refs/heads/*");
const unsigned flags =
VCSBase::VCSBasePlugin::SshPasswordPrompt|
VCSBase::VCSBasePlugin::SuppressStdErrInLogWindow|
VCSBase::VCSBasePlugin::SuppressFailMessageInLogWindow;
const Utils::SynchronousProcessResponse resp = synchronousGit(QString(), arguments, flags);
QStringList branches;
branches << "<detached HEAD>";
QString headSha;
if (resp.result == Utils::SynchronousProcessResponse::Finished) {
// split "82bfad2f51d34e98b18982211c82220b8db049b<tab>refs/heads/master"
foreach(const QString &line, resp.stdOut.split(QLatin1Char('\n'))) {
if (line.endsWith("\tHEAD")) {
Q_ASSERT(headSha.isNull());
headSha = line.left(line.indexOf(QChar('\t')));
continue;
}
const int slashPos = line.lastIndexOf(QLatin1Char('/'));
const QString branchName = line.mid(slashPos + 1);
if (slashPos != -1) {
if (line.startsWith(headSha))
branches[0] = branchName;
else
branches.push_back(branchName);
}
}
}
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();
QDir foundBinDir = gitBinDirectory;
const bool foundBinDirIsCmdDir = foundBinDir.dirName() == "cmd";
if (!tryLauchingGitK(env, workingDirectory, gitBinDirectory, foundBinDirIsCmdDir)) {
if (foundBinDirIsCmdDir) {
foundBinDir.cdUp();
tryLauchingGitK(env, workingDirectory, foundBinDir.path() + "/bin", false);
}
}
}
bool GitClient::tryLauchingGitK(const QProcessEnvironment &env,
const QString &workingDirectory,
const QString &gitBinDirectory,
bool silent)
{
#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;
VCSBase::VCSBaseOutputWindow *outwin = VCSBase::VCSBaseOutputWindow::instance();
if (!m_settings.gitkOptions.isEmpty())
arguments.append(Utils::QtcProcess::splitArgs(m_settings.gitkOptions));
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) {
const QString error = tr("Unable to launch %1.").arg(binary);
if (silent)
outwin->appendSilently(error);
else
outwin->appendError(error);
}
return success;
if (Git::Constants::debug)
qDebug() << Q_FUNC_INFO << workingDirectory;
// Find repo
const QString repoDirectory = GitClient::findRepositoryForDirectory(workingDirectory);
if (repoDirectory.isEmpty()) {
*errorMessage = msgRepositoryNotFound(workingDirectory);
commitData->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))
commitData->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;
const StatusResult status = gitStatus(repoDirectory, true, &output, errorMessage, &onBranch);
switch (status) {
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...
if (status != StatusUnchanged) {
if (!commitData->parseFilesFromStatus(output)) {
*errorMessage = msgParseFilesFailed();
return false;
}
// Filter out untracked files that are not part of the project
VCSBase::VCSBaseSubmitEditor::filterUntrackedFilesOfProject(repoDirectory, &commitData->untrackedFiles);
if (commitData->filesEmpty()) {
*errorMessage = msgNoChangedFiles();
return false;
}

Friedemann Kleint
committed
}
commitData->panelData.author = readConfigValue(workingDirectory, QLatin1String("user.name"));
commitData->panelData.email = readConfigValue(workingDirectory, QLatin1String("user.email"));
// Get the commit template or the last commit message
if (amend) {
// Amend: get last commit data as "SHA1@message". TODO: Figure out codec.
QStringList args(QLatin1String("log"));
const QString format = synchronousGitVersion(true) > 0x010701 ? "%h@%B" : "%h@%s%n%n%b";
args << QLatin1String("--max-count=1") << QLatin1String("--pretty=format:") + format;
const Utils::SynchronousProcessResponse sp = synchronousGit(repoDirectory, args);
if (sp.result != Utils::SynchronousProcessResponse::Finished) {
*errorMessage = tr("Unable to retrieve the last commit data of the repository %1.").arg(repoDirectory);
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
return false;
}
const int separatorPos = sp.stdOut.indexOf(QLatin1Char('@'));
QTC_ASSERT(separatorPos != -1, return false)
commitData->amendSHA1= sp.stdOut.left(separatorPos);
*commitTemplate = sp.stdOut.mid(separatorPos + 1);
} else {
// Commit: Get the commit template
QString templateFilename = readConfigValue(workingDirectory, QLatin1String("commit.template"));
if (!templateFilename.isEmpty()) {
// 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()));
}
// Log message for commits/amended commits to go to output window
static inline QString msgCommitted(const QString &amendSHA1, int fileCount)
{
if (amendSHA1.isEmpty())
return GitClient::tr("Committed %n file(s).\n", 0, fileCount);
if (fileCount)
return GitClient::tr("Amended %1 (%n file(s)).\n", 0, fileCount).arg(amendSHA1);
return GitClient::tr("Amended %1.").arg(amendSHA1);
}
bool GitClient::addAndCommit(const QString &repositoryDirectory,
const QStringList &checkedFiles,
const QStringList &origCommitFiles,
const QStringList &origDeletedFiles)
qDebug() << "GitClient::addAndCommit:" << repositoryDirectory << checkedFiles << origCommitFiles;
const QString renamedSeparator = QLatin1String(" -> ");
const bool amend = !amendSHA1.isEmpty();
// 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);
if (amend)
args << QLatin1String("--amend");
const QString &authorString = data.authorString();
if (!authorString.isEmpty())
args << QLatin1String("--author") << authorString;
const bool rc = fullySynchronousGit(repositoryDirectory, args, &outputText, &errorText);
if (rc) {
outputWindow()->append(msgCommitted(amendSHA1, 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)
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
{
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::synchronousFetch(const QString &workingDirectory)
{
QStringList arguments(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, arguments, flags);
return resp.result == Utils::SynchronousProcessResponse::Finished;
}
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;
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'));
}

Tuomas Puranen
committed
bool GitClient::cloneRepository(const QString &directory,const QByteArray &url)
{
QDir workingDirectory(directory);
const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt |
VCSBase::VCSBasePlugin::ShowStdOutInLogWindow|
VCSBase::VCSBasePlugin::ShowSuccessMessage;

Tuomas Puranen
committed
if (workingDirectory.exists()) {
if (!synchronousInit(workingDirectory.path()))
return false;
QStringList arguments(QLatin1String("remote"));
arguments << QLatin1String("add") << QLatin1String("origin") << url;
if (!fullySynchronousGit(workingDirectory.path(), arguments, 0, 0, true))
return false;

Tuomas Puranen
committed
arguments.clear();
arguments << QLatin1String("fetch");
const Utils::SynchronousProcessResponse resp =
synchronousGit(workingDirectory.path(), arguments, flags);

Tuomas Puranen
committed
if (resp.result != Utils::SynchronousProcessResponse::Finished)
return false;
arguments.clear();
arguments << QLatin1String("config")
<< QLatin1String("branch.master.remote")
<< QLatin1String("origin");
if (!fullySynchronousGit(workingDirectory.path(), arguments, 0, 0, true))

Tuomas Puranen
committed
return false;
arguments.clear();
arguments << QLatin1String("config")
<< QLatin1String("branch.master.merge")
<< QLatin1String("refs/heads/master");
if (!fullySynchronousGit(workingDirectory.path(), arguments, 0, 0, true))

Tuomas Puranen
committed
return false;
return true;
} else {
QStringList arguments(QLatin1String("clone"));
arguments << url << workingDirectory.dirName();
workingDirectory.cdUp();
const Utils::SynchronousProcessResponse resp =
synchronousGit(workingDirectory.path(), arguments, flags);

Tuomas Puranen
committed
return resp.result == Utils::SynchronousProcessResponse::Finished;
}
}
QString GitClient::vcsGetRepositoryURL(const QString &directory)
{
QStringList arguments(QLatin1String("config"));
QByteArray outputText;
arguments << QLatin1String("remote.origin.url");
if (fullySynchronousGit(directory, arguments, &outputText, 0, false)) {

Tuomas Puranen
committed
return commandOutputFromLocal8Bit(outputText);
}
return QString();
}
GitSettings GitClient::settings() const
{