Newer
Older
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
break;
}
/* parse output for current branch: \code
* master
branch2
\endcode */
const QString branchPrefix = QLatin1String("* ");
foreach(const QString &line, commandOutputLinesFromLocal8Bit(outputTextData)) {
if (line.startsWith(branchPrefix)) {
*branch = line;
branch->remove(0, branchPrefix.size());
break;
}
}
if (branch->isEmpty()) {
errorMessage = msgCannotDetermineBranch(workingDirectory,
QString::fromLatin1("Internal error: Failed to parse output: %1").arg(commandOutputFromLocal8Bit(outputTextData)));
break;
}
} // branch
} while (false);
const bool failed = (revision && revision->isEmpty()) || (branch && branch->isEmpty());
if (failed && !errorMessage.isEmpty()) {
if (errorMessageIn) {
*errorMessageIn = errorMessage;
} else {
outputWindow()->appendError(errorMessage);
}
}
return !failed;
}
// Format an entry in a one-liner for selection list using git log.
bool GitClient::synchronousShortDescription(const QString &workingDirectory, const QString &revision,
const QString &format, QString *description,
QString *errorMessage)
{
QByteArray outputTextData;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("log") << QLatin1String(GitClient::noColorOption)
<< (QLatin1String("--pretty=format:") + format)
<< QLatin1String("--max-count=1") << revision;
const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText);
if (!rc) {
*errorMessage = tr("Cannot describe revision \"%1\" in \"%2\": %3").arg(revision, workingDirectory, commandOutputFromLocal8Bit(errorText));
return false;
}
*description = commandOutputFromLocal8Bit(outputTextData);
if (description->endsWith(QLatin1Char('\n')))
description->truncate(description->size() - 1);
return true;
}
// Create a default message to be used for describing stashes
static inline QString creatorStashMessage(const QString &keyword = QString())
{
QString rc = QCoreApplication::applicationName();
rc += QLatin1Char(' ');
if (!keyword.isEmpty()) {
rc += keyword;
rc += QLatin1Char(' ');
}
rc += QDateTime::currentDateTime().toString(Qt::ISODate);
return rc;
}
/* Do a stash and return the message as identifier. Note that stash names (stash{n})
* shift as they are pushed, so, enforce the use of messages to identify them. Flags:
* StashPromptDescription: Prompt the user for a description message.
* StashImmediateRestore: Immediately re-apply this stash (used for snapshots), user keeps on working
* StashIgnoreUnchanged: Be quiet about unchanged repositories (used for IVersionControl's snapshots). */
QString GitClient::synchronousStash(const QString &workingDirectory, const QString &messageKeyword,
unsigned flags, bool *unchanged)
{
if (unchanged)
*unchanged = false;
QString message;
bool success = false;
// Check for changes and stash
QString errorMessage;
switch (gitStatus(workingDirectory, false, 0, &errorMessage)) {
case StatusChanged: {
message = creatorStashMessage(messageKeyword);
do {
if ((flags & StashPromptDescription)) {
if (!inputText(Core::ICore::instance()->mainWindow(),
tr("Stash Description"), tr("Description:"), &message))
break;
}
if (!executeSynchronousStash(workingDirectory, message))
break;
if ((flags & StashImmediateRestore)
&& !synchronousStashRestore(workingDirectory, QLatin1String("stash@{0}")))
break;
success = true;
} while (false);
}
break;
case StatusUnchanged:
if (unchanged)
*unchanged = true;
if (!(flags & StashIgnoreUnchanged))
outputWindow()->append(msgNoChangedFiles());
outputWindow()->append(errorMessage);
break;
}
if (!success)
message.clear();
return message;
}
bool GitClient::executeSynchronousStash(const QString &workingDirectory,
const QString &message,
QString *errorMessage)

Friedemann Kleint
committed
{
QByteArray outputText;
QByteArray errorText;
QStringList arguments;
arguments << QLatin1String("stash");
if (!message.isEmpty())
arguments << QLatin1String("save") << message;
const bool rc = fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText);

Friedemann Kleint
committed
if (!rc) {
const QString msg = tr("Cannot stash in \"%1\": %2").
arg(QDir::toNativeSeparators(workingDirectory),
commandOutputFromLocal8Bit(errorText));
if (errorMessage) {
*errorMessage = msg;
} else {
outputWindow()->append(msg);

Friedemann Kleint
committed
return false;
}
return true;
}
// Resolve a stash name from message
bool GitClient::stashNameFromMessage(const QString &workingDirectory,
const QString &message, QString *name,
{
// All happy
if (message.startsWith(QLatin1String(stashNamePrefix))) {
*name = message;
return true;
}
// Retrieve list and find via message
QList<Stash> stashes;
if (!synchronousStashList(workingDirectory, &stashes, errorMessage))
return false;
foreach (const Stash &s, stashes) {
if (s.message == message) {
*name = s.name;
return true;
}
}
//: Look-up of a stash via its descriptive message failed.
const QString msg = tr("Cannot resolve stash message \"%1\" in \"%2\".").arg(message, workingDirectory);
if (errorMessage) {
*errorMessage = msg;
} else {
outputWindow()->append(msg);
}
return false;
}
bool GitClient::synchronousBranchCmd(const QString &workingDirectory, QStringList branchArgs,
QString *output, QString *errorMessage)
{
branchArgs.push_front(QLatin1String("branch"));
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(workingDirectory, branchArgs, &outputText, &errorText);
*errorMessage = tr("Cannot run \"git branch\" in \"%1\": %2").arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
*output = commandOutputFromLocal8Bit(outputText);
bool GitClient::synchronousRemoteCmd(const QString &workingDirectory, QStringList remoteArgs,
QString *output, QString *errorMessage)
{
remoteArgs.push_front(QLatin1String("remote"));
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(workingDirectory, remoteArgs, &outputText, &errorText);
if (!rc) {
*errorMessage = tr("Cannot run \"git remote\" in \"%1\": %2").arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
return false;
}
*output = commandOutputFromLocal8Bit(outputText);
return true;
}
bool GitClient::synchronousShow(const QString &workingDirectory, const QString &id,
QString *output, QString *errorMessage)
{
if (!canShow(id)) {
*errorMessage = msgCannotShow(id);
return false;
}
QStringList args(QLatin1String("show"));
args << QLatin1String(decorateOption) << QLatin1String(noColorOption) << id;
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
*errorMessage = tr("Cannot run \"git show\" in \"%1\": %2").arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
*output = commandOutputFromLocal8Bit(outputText);
// Retrieve list of files to be cleaned
bool GitClient::synchronousCleanList(const QString &workingDirectory,
QStringList *files, QString *errorMessage)
{
files->clear();
QStringList args;
args << QLatin1String("clean") << QLatin1String("--dry-run") << QLatin1String("-dxf");
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
*errorMessage = tr("Cannot 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)
{
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));
*errorMessage = tr("Cannot apply patch \"%1\" to \"%2\": %3").arg(file, workingDirectory, commandOutputFromLocal8Bit(errorText));
return false;
}
return true;
}
// Factory function to create an asynchronous command
VCSBase::Command *GitClient::createCommand(const QString &workingDirectory,
VCSBase::VCSBaseEditorWidget* editor,
bool useOutputToWindow,
int editorLineNumber)
VCSBase::Command *command = new VCSBase::Command(gitBinaryPath(), workingDirectory, processEnvironment());
command->setCookie(QVariant(editorLineNumber));
connect(command, SIGNAL(finished(bool,int,QVariant)), editor, SLOT(commandFinishedGotoLine(bool,int,QVariant)));
if (useOutputToWindow) {
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)));
if (editor)
connect(command, SIGNAL(outputData(QByteArray)), editor, SLOT(setPlainTextDataFiltered(QByteArray)));
if (outputWindow())
connect(command, SIGNAL(errorText(QString)), outputWindow(), SLOT(appendError(QString)));
return command;
}
// Execute a single command
VCSBase::Command *GitClient::executeGit(const QString &workingDirectory,
const QStringList &arguments,
VCSBase::VCSBaseEditorWidget* editor,
bool useOutputToWindow,
VCSBase::Command::TerminationReportMode tm,
int editorLineNumber,
bool unixTerminalDisabled)
outputWindow()->appendCommand(workingDirectory, settings()->stringValue(GitSettings::binaryPathKey), arguments);
VCSBase::Command *command = createCommand(workingDirectory, editor, useOutputToWindow, editorLineNumber);
command->addJob(arguments, settings()->intValue(GitSettings::timeoutKey));
command->setTerminationReportMode(tm);
command->setUnixTerminalDisabled(unixTerminalDisabled);
command->execute();
return command;
QProcessEnvironment GitClient::processEnvironment() const
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
if (settings()->boolValue(GitSettings::adoptPathKey))
environment.insert(QLatin1String("PATH"), settings()->stringValue(GitSettings::pathKey));
if (settings()->boolValue(GitSettings::winSetHomeEnvironmentKey))
environment.insert(QLatin1String("HOME"), QDir::toNativeSeparators(QDir::homePath()));
// 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)
return VCSBase::VCSBasePlugin::runVCS(workingDirectory, gitBinaryPath(), gitArguments,
settings()->intValue(GitSettings::timeoutKey) * 1000,
flags, stdOutCodec);
}
bool GitClient::fullySynchronousGit(const QString &workingDirectory,
const QStringList &gitArguments,
QByteArray* outputText,
QByteArray* errorText,
bool logCommandToWindow) const
{
return VCSBase::VCSBasePlugin::runFullySynchronous(workingDirectory, gitBinaryPath(), gitArguments,
processEnvironment(), outputText, errorText,
settings()->intValue(GitSettings::timeoutKey) * 1000,
logCommandToWindow);
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)

Friedemann Kleint
committed
{
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
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"),

Friedemann Kleint
committed
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);
VCSBase::Command::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("Cannot obtain 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)
{
const QString gitBinDirectory = gitBinaryPath();
QDir foundBinDir(gitBinDirectory);
const bool foundBinDirIsCmdDir = foundBinDir.dirName() == "cmd";
QProcessEnvironment env = processEnvironment();
if (tryLauchingGitK(env, workingDirectory, gitBinDirectory, foundBinDirIsCmdDir))
return;
if (!foundBinDirIsCmdDir)
return;
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();
const QString gitkOpts = settings()->stringValue(GitSettings::gitkOptionsKey);
if (!gitkOpts.isEmpty())
arguments.append(Utils::QtcProcess::splitArgs(gitkOpts));
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 (settings()->boolValue(GitSettings::adoptPathKey)) {
QProcess *process = new QProcess(this);
process->setWorkingDirectory(workingDirectory);
process->setProcessEnvironment(env);
process->start(binary, arguments);
success = process->waitForStarted();
connect(process, SIGNAL(finished(int)), process, SLOT(deleteLater()));
delete process;
} else {
success = QProcess::startDetached(binary, arguments, workingDirectory);
const QString error = tr("Cannot launch \"%1\".").arg(binary);
if (silent)
outwin->appendSilently(error);
else
outwin->appendError(error);
}
return success;
QString GitClient::gitBinaryPath(bool *ok, QString *errorMessage) const
{
return settings()->gitBinaryPath(ok, errorMessage);
}
// 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.").arg(repoDirectory);
return false;
}
// Read description
const QString descriptionFile = gitDir.absoluteFilePath(QLatin1String("description"));
if (QFileInfo(descriptionFile).isFile()) {
Utils::FileReader reader;
if (!reader.fetch(descriptionFile, QIODevice::Text, errorMessage))
return false;
commitData->panelInfo.description = commandOutputFromLocal8Bit(reader.data()).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
// # modified: submodule (modified content)
// # modified: submodule2 (new commit)
// #
// # 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("Cannot retrieve last commit data of repository \"%1\".").arg(repoDirectory);
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;
Utils::FileReader reader;
if (!reader.fetch(templateFilename, QIODevice::Text, errorMessage))
return false;
*commitTemplate = QString::fromLocal8Bit(reader.data());
// 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)
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(); ) {
it = addFiles.erase(it);
if (!addFiles.isEmpty() && !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);
outputWindow()->append(msgCommitted(amendSHA1, checkedFiles.size()));
outputWindow()->appendError(tr("Cannot 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)
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
{
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 ((!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:
GitPlugin::instance()->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, const QString &remote)
{
QStringList arguments(QLatin1String("fetch"));
if (!remote.isEmpty())
arguments << remote;
// 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, settings()->boolValue(GitSettings::pullRebaseKey));
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)
{
QStringList arguments;
arguments << QLatin1String("svn") << QLatin1String("log");
int logCount = settings()->intValue(GitSettings::logCountKey);
if (logCount > 0)
arguments << (QLatin1String("--limit=") + QString::number(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::VCSBaseEditorWidget::getSource(workingDirectory, QStringList());
VCSBase::VCSBaseEditorWidget *editor = findExistingVCSEditor("svnLog", sourceFile);
if (!editor)
editor = createVCSEditor(editorId, title, sourceFile, false, "svnLog", sourceFile, 0);
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");
VCSBase::Command *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"));
arguments << QLatin1String("apply") << stash;
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() ?
arg(nativeWorkingDir, stdErr) :
tr("Cannot restore stash \"%1\" to branch \"%2\": %3").
arg(nativeWorkingDir, branch, stdErr);
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;