Newer
Older
arguments << binary;
binary = wish;
}
VcsBase::VcsBaseOutputWindow *outwin = VcsBase::VcsBaseOutputWindow::instance();
const QString gitkOpts = settings()->stringValue(GitSettings::gitkOptionsKey);
if (!gitkOpts.isEmpty())
arguments.append(Utils::QtcProcess::splitArgs(gitkOpts));
if (!fileName.isEmpty())
arguments << QLatin1String("--") << fileName;
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()->stringValue(GitSettings::pathKey).isEmpty()) {
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;
QString gitDir = findGitDirForRepository(repoDirectory);
*errorMessage = tr("The repository \"%1\" is not initialized.").arg(repoDirectory);
return false;
}
// Run status. Note that it has exitcode 1 if there are no added files.
QString output;
const StatusResult status = gitStatus(repoDirectory, ShowAll, &output, errorMessage);
case StatusChanged:
break;
case StatusUnchanged:
*errorMessage = msgNoChangedFiles();
return false;
case StatusFailed:
return false;
// ## branch_name
// MM filename
// A new_unstaged_file
// R old -> new
// D deleted_file
// ?? untracked_file
if (status != StatusUnchanged) {
if (!commitData->parseFilesFromStatus(output)) {
*errorMessage = msgParseFilesFailed();
return false;
}
// Filter out untracked files that are not part of the project
QStringList untrackedFiles = commitData->filterFiles(UntrackedFile);
VcsBase::VcsBaseSubmitEditor::filterUntrackedFilesOfProject(repoDirectory, &untrackedFiles);
QList<CommitData::StateFilePair> filteredFiles;
QList<CommitData::StateFilePair>::const_iterator it = commitData->files.constBegin();
for ( ; it != commitData->files.constEnd(); ++it) {
if (it->first == UntrackedFile && !untrackedFiles.contains(it->second))
continue;
filteredFiles.append(*it);
}
commitData->files = filteredFiles;
if (commitData->files.isEmpty() && !amend) {
*errorMessage = msgNoChangedFiles();
return false;
}

Friedemann Kleint
committed
}
commitData->commitEncoding = readConfigValue(workingDirectory, QLatin1String("i18n.commitEncoding"));
// Get the commit template or the last commit message
if (amend) {
// Amend: get last commit data as "SHA1<tab>author<tab>email<tab>message".
QStringList args(QLatin1String("log"));
args << QLatin1String("--max-count=1") << QLatin1String("--pretty=format:%h\t%an\t%ae\t%B");
QTextCodec *codec = QTextCodec::codecForName(commitData->commitEncoding.toLocal8Bit());
const Utils::SynchronousProcessResponse sp = synchronousGit(repoDirectory, args, 0, codec);
if (sp.result != Utils::SynchronousProcessResponse::Finished) {
*errorMessage = tr("Cannot retrieve last commit data of repository \"%1\".").arg(repoDirectory);
QStringList values = sp.stdOut.split(QLatin1Char('\t'));
QTC_ASSERT(values.size() >= 4, return false);
commitData->amendSHA1 = values.takeFirst();
commitData->panelData.author = values.takeFirst();
commitData->panelData.email = values.takeFirst();
*commitTemplate = values.join(QLatin1String("\t"));
commitData->panelData.author = readConfigValue(workingDirectory, QLatin1String("user.name"));
commitData->panelData.email = readConfigValue(workingDirectory, QLatin1String("user.email"));
// Commit: Get the commit template
QDir gitDirectory(gitDir);
QString templateFilename = gitDirectory.absoluteFilePath(QLatin1String("MERGE_MSG"));
if (!QFile::exists(templateFilename))
templateFilename = gitDirectory.absoluteFilePath(QLatin1String("SQUASH_MSG"));
if (!QFile::exists(templateFilename))
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 QString renameSeparator = QLatin1String(" -> ");
const bool amend = !amendSHA1.isEmpty();
QStringList filesToAdd;
QStringList filesToRemove;
QStringList filesToReset;
int commitCount = 0;
for (int i = 0; i < model->rowCount(); ++i) {
const FileStates state = static_cast<FileStates>(model->extraData(i).toInt());
QString file = model->file(i);
const bool checked = model->checked(i);
if (checked)
++commitCount;
if (state == UntrackedFile && checked)
if ((state & StagedFile) && !checked) {
if (state & (ModifiedFile | AddedFile | DeletedFile)) {
filesToReset.append(file);
} else if (state & (RenamedFile | CopiedFile)) {
const QString newFile = file.mid(file.indexOf(renameSeparator) + renameSeparator.count());
filesToReset.append(newFile);
}
} else if (state & UnmergedFile && checked) {
QTC_ASSERT(false, continue); // There should not be unmerged files when commiting!
if (state == ModifiedFile && checked) {
filesToReset.removeAll(file);
filesToAdd.append(file);
} else if (state == AddedFile && checked) {
QTC_ASSERT(false, continue); // these should be untracked!
} else if (state == DeletedFile && checked) {
filesToReset.removeAll(file);
filesToRemove.append(file);
} else if (state == RenamedFile && checked) {
QTC_ASSERT(false, continue); // git mv directly stages.
} else if (state == CopiedFile && checked) {
QTC_ASSERT(false, continue); // only is noticed after adding a new file to the index
} else if (state == UnmergedFile && checked) {
QTC_ASSERT(false, continue); // There should not be unmerged files when commiting!
if (!filesToReset.isEmpty() && !synchronousReset(repositoryDirectory, filesToReset))
return false;
if (!filesToRemove.isEmpty() && !synchronousDelete(repositoryDirectory, true, filesToRemove))
return false;
if (!filesToAdd.isEmpty() && !synchronousAdd(repositoryDirectory, false, filesToAdd))
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;
if (data.bypassHooks)
args << QLatin1String("--no-verify");
const bool rc = fullySynchronousGit(repositoryDirectory, args, &outputText, &errorText);
outputWindow()->append(msgCommitted(amendSHA1, commitCount));
outputWindow()->appendError(tr("Cannot commit %n file(s): %1\n", 0, commitCount).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
GitClient::RevertResult GitClient::revertI(QStringList files,
bool *ptrToIsDirectory,
QString *errorMessage,
bool revertStaging)
{
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, StatusMode(NoUntracked | NoSubmodules), &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.
const QStringList allStagedFiles = data.filterFiles(StagedFile | ModifiedFile);
const QStringList allUnstagedFiles = data.filterFiles(ModifiedFile);
// 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(Core::ICore::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::executeAndHandleConflicts(const QString &workingDirectory,
const QStringList &arguments,
const QString &abortCommand)
// Disable UNIX terminals to suppress SSH prompting.
const unsigned flags = VcsBase::VcsBasePlugin::SshPasswordPrompt|VcsBase::VcsBasePlugin::ShowStdOutInLogWindow;
const Utils::SynchronousProcessResponse resp = synchronousGit(workingDirectory, arguments, flags);
ConflictHandler conflictHandler(0, workingDirectory, abortCommand);
// Notify about changed files or abort the rebase.
const bool ok = resp.result == Utils::SynchronousProcessResponse::Finished;
GitPlugin::instance()->gitVersionControl()->emitRepositoryChanged(workingDirectory);
} else {
conflictHandler.readStdOutString(resp.stdOut);
conflictHandler.readStdErr(resp.stdErr);
bool GitClient::synchronousPull(const QString &workingDirectory, bool rebase)
{
QString abortCommand;
if (rebase) {
arguments << QLatin1String("--rebase");
abortCommand = QLatin1String("rebase");
} else {
abortCommand = QLatin1String("merge");
}
bool ok = executeAndHandleConflicts(workingDirectory, arguments, abortCommand);
if (ok)
promptSubmoduleUpdate(workingDirectory);
return ok;
void GitClient::synchronousAbortCommand(const QString &workingDir, const QString &abortCommand)
{
// Abort to clean if something goes wrong
if (abortCommand.isEmpty()) {
// no abort command - checkout index to clean working copy.
synchronousCheckoutFiles(findRepositoryForDirectory(workingDir), QStringList(), QString(), 0, false);
return;
}
VcsBase::VcsBaseOutputWindow *outwin = VcsBase::VcsBaseOutputWindow::instance();
QStringList arguments;
arguments << abortCommand << 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));
}
void GitClient::handleMergeConflicts(const QString &workingDir, const QString &commit, const QString &abortCommand)
QString message = commit.isEmpty() ? tr("Conflicts detected")
: tr("Conflicts detected with commit %1").arg(commit);
QMessageBox mergeOrAbort(QMessageBox::Question, tr("Conflicts Detected"),
message, QMessageBox::Ignore | QMessageBox::Abort);
QPushButton *mergeToolButton = mergeOrAbort.addButton(tr("Run &Merge Tool"),
QMessageBox::ActionRole);
if (abortCommand == QLatin1String("rebase"))
mergeOrAbort.addButton(tr("&Skip"), QMessageBox::ActionRole);
switch (mergeOrAbort.exec()) {
synchronousAbortCommand(workingDir, abortCommand);
break;
case QMessageBox::Ignore:
break;
default: // Merge or Skip
if (mergeOrAbort.clickedButton() == mergeToolButton) {
merge(workingDir);
} else {
QStringList arguments = QStringList() << abortCommand << QLatin1String("--skip");
executeAndHandleConflicts(workingDir, arguments, abortCommand);
}
// 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 Core::Id editorId = Git::Constants::C_GIT_COMMAND_LOG_EDITOR;
const QString sourceFile = VcsBase::VcsBaseEditorWidget::getSource(workingDirectory, QStringList());
VcsBase::VcsBaseEditorWidget *editor = findExistingVCSEditor("svnLog", sourceFile);
editor = createVcsEditor(editorId, title, sourceFile, CodecNone, "svnLog", sourceFile, 0);
executeGit(workingDirectory, arguments, editor);
}
bool GitClient::synchronousPush(const QString &workingDirectory, const QStringList &pushArgs)
// Disable UNIX terminals to suppress SSH prompting.
const unsigned flags = VcsBase::VcsBasePlugin::SshPasswordPrompt|VcsBase::VcsBasePlugin::ShowStdOutInLogWindow
|VcsBase::VcsBasePlugin::ShowSuccessMessage;
QStringList arguments(QLatin1String("push"));
if (!pushArgs.isEmpty())
arguments += pushArgs;
const Utils::SynchronousProcessResponse resp =
synchronousGit(workingDirectory, arguments, flags);
return resp.result == Utils::SynchronousProcessResponse::Finished;
}
bool GitClient::synchronousMerge(const QString &workingDirectory, const QString &branch)
{
QString command = QLatin1String("merge");
QStringList arguments;
arguments << command << branch;
return executeAndHandleConflicts(workingDirectory, arguments, command);
bool GitClient::canRebase(const QString &workingDirectory) const
{
const QString gitDir = findGitDirForRepository(workingDirectory);
if (QFileInfo(gitDir + QLatin1String("/rebase-apply")).exists()
|| QFileInfo(gitDir + QLatin1String("/rebase-merge")).exists()) {
VcsBase::VcsBaseOutputWindow::instance()->appendError(
tr("Rebase, merge or am is in progress. Please finish "
"or abort it then try again"));
return false;
}
return true;
}
bool GitClient::synchronousRebase(const QString &workingDirectory, const QString &baseBranch,
const QString &topicBranch)
{
QString command = QLatin1String("rebase");
QStringList arguments;
arguments << command << baseBranch;
if (!topicBranch.isEmpty())
arguments << topicBranch;
return executeAndHandleConflicts(workingDirectory, arguments, command);
bool GitClient::synchronousRevert(const QString &workingDirectory, const QString &commit)
{
QStringList arguments;
QString command = QLatin1String("revert");
arguments << command << QLatin1String("--no-edit") << commit;
return executeAndHandleConflicts(workingDirectory, arguments, command);
}
bool GitClient::synchronousCherryPick(const QString &workingDirectory, const QString &commit)
{
QStringList arguments;
QString command = QLatin1String("cherry-pick");
arguments << command << commit;
return executeAndHandleConflicts(workingDirectory, arguments, command);
}
void GitClient::interactiveRebase(const QString &workingDirectory, const QString &commit)
{
QStringList arguments;
arguments << QLatin1String("rebase") << QLatin1String("-i") << commit;
outputWindow()->appendCommand(workingDirectory, settings()->stringValue(GitSettings::binaryPathKey), arguments);
VcsBase::Command *command = createCommand(workingDirectory, 0, true);
command->addJob(arguments, -1);
command->execute();
command->setCookie(workingDirectory);
RebaseManager *rebaseManager = new RebaseManager(command);
connect(command, SIGNAL(errorText(QString)), rebaseManager, SLOT(readStdErr(QString)));
connect(command, SIGNAL(finished(bool,int,QVariant)),
rebaseManager, SLOT(finished(bool,int,QVariant)));
}
QString GitClient::msgNoChangedFiles()
{
return tr("There are no modified files.");
}
void GitClient::stashPop(const QString &workingDirectory, const QString &stash)
{
QStringList arguments(QLatin1String("stash"));
arguments << QLatin1String("pop");
if (!stash.isEmpty())
arguments << stash;
VcsBase::Command *cmd = executeGit(workingDirectory, arguments, 0, true);
connectRepositoryChanged(workingDirectory, cmd);
}
void GitClient::stashPop(const QString &workingDirectory)
{
stashPop(workingDirectory, QString());
}
bool GitClient::synchronousStashRestore(const QString &workingDirectory,
const QString &stash,
const QString &branch /* = QString()*/,
QString *errorMessage)
{
QStringList arguments(QLatin1String("stash"));
arguments << QLatin1String(pop ? "pop" : "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"));
arguments << QLatin1String("clear");
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() ?
arg(nativeWorkingDir, stdErr) :
arg(stash, nativeWorkingDir, stdErr);
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("Cannot retrieve stash list of \"%1\": %2").
arg(QDir::toNativeSeparators(workingDirectory),
commandOutputFromLocal8Bit(errorText));
outputWindow()->append(msg);
return false;
}
Stash stash;
foreach (const QString &line, commandOutputLinesFromLocal8Bit(outputText))
if (stash.parseStashLine(line))
stashes->push_back(stash);
return true;
}
QString GitClient::readConfig(const QString &workingDirectory, const QStringList &configVar) const
{
QStringList arguments;
arguments << QLatin1String("config") << configVar;
QByteArray outputText;
QByteArray errorText;
if (!fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText, false))
return QString();
if (Utils::HostOsInfo::isWindowsHost())
return QString::fromUtf8(outputText).remove(QLatin1Char('\r'));
return commandOutputFromLocal8Bit(outputText);
QString GitClient::readConfigValue(const QString &workingDirectory, const QString &configVar) const
{
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") << QLatin1String(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 << QLatin1String(url) << workingDirectory.dirName();

Tuomas Puranen
committed
workingDirectory.cdUp();
const Utils::SynchronousProcessResponse resp =
synchronousGit(workingDirectory.path(), arguments, flags);
// TODO: Turn this into a VcsBaseClient and use resetCachedVcsInfo(...)
Core::ICore::vcsManager()->resetVersionControlForDirectory(workingDirectory.absolutePath());
return (resp.result == Utils::SynchronousProcessResponse::Finished);

Tuomas Puranen
committed
}
}
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
{
return m_settings;
}
void GitClient::connectRepositoryChanged(const QString & repository, VcsBase::Command *cmd)
{
// Bind command success termination with repository to changed signal
if (!m_repositoryChangedSignalMapper) {
m_repositoryChangedSignalMapper = new QSignalMapper(this);
connect(m_repositoryChangedSignalMapper, SIGNAL(mapped(QString)),
GitPlugin::instance()->gitVersionControl(), SIGNAL(repositoryChanged(QString)));
}
m_repositoryChangedSignalMapper->setMapping(cmd, repository);
connect(cmd, SIGNAL(success(QVariant)), m_repositoryChangedSignalMapper, SLOT(map()),
Qt::QueuedConnection);
}
// determine version as '(major << 16) + (minor << 8) + patch' or 0.
unsigned GitClient::gitVersion(QString *errorMessage) const
{
const QString newGitBinary = gitBinaryPath();
if (m_gitVersionForBinary != newGitBinary && !newGitBinary.isEmpty()) {
// Do not execute repeatedly if that fails (due to git
// not being installed) until settings are changed.
m_gitVersionForBinary = newGitBinary;
return m_cachedGitVersion;
// determine version as '(major << 16) + (minor << 8) + patch' or 0.
unsigned GitClient::synchronousGitVersion(QString *errorMessage) const
{
if (gitBinaryPath().isEmpty())
return 0;
// run git --version
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(QString(), QStringList(QLatin1String("--version")), &outputText, &errorText, false);
if (!rc) {
const QString msg = tr("Cannot determine git version: %1").arg(commandOutputFromLocal8Bit(errorText));
*errorMessage = msg;
return 0;
}
// cut 'git version 1.6.5.1.sha'
const QString output = commandOutputFromLocal8Bit(outputText);
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(0, 16);
const unsigned minor = versionPattern.cap(2).toUInt(0, 16);
const unsigned patch = versionPattern.cap(3).toUInt(0, 16);
return version(major, minor, patch);
GitClient::StashGuard::StashGuard(const QString &workingDirectory, const QString &keyword,
StashFlag flag) :
workingDir(workingDirectory),
flags(flag)
{
client = GitPlugin::instance()->gitClient();
QString errorMessage;
if (flags & NoPrompt)
executeStash(keyword, &errorMessage);
else
stashPrompt(keyword, &errorMessage);
if (stashResult == StashFailed)
VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
}
GitClient::StashGuard::~StashGuard()
{
if (pop && stashResult == Stashed) {
QString stashName;
if (client->stashNameFromMessage(workingDir, message, &stashName))
client->stashPop(workingDir, stashName);
}
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
void GitClient::StashGuard::stashPrompt(const QString &keyword, QString *errorMessage)
{
QString statusOutput;
switch (client->gitStatus(workingDir, StatusMode(NoUntracked | NoSubmodules),
&statusOutput, errorMessage)) {
case GitClient::StatusChanged:
break;
case GitClient::StatusUnchanged:
stashResult = StashUnchanged;
return;
case GitClient::StatusFailed:
stashResult = StashFailed;
return;
}
QPointer<QMessageBox> msgBox = new QMessageBox(QMessageBox::Question,
tr("Uncommitted Changes Found"),
tr("What would you like to do with local changes in:")
+ QLatin1String("\n\n\"") + workingDir + QLatin1Char('\"'),
QMessageBox::NoButton, Core::ICore::mainWindow());
msgBox->setDetailedText(statusOutput);
QPushButton *stashButton = msgBox->addButton(tr("Stash"), QMessageBox::AcceptRole);
stashButton->setToolTip(tr("Stash local changes and continue."));
QPushButton *discardButton = msgBox->addButton(tr("Discard"), QMessageBox::AcceptRole);
discardButton->setToolTip(tr("Discard (reset) local changes and continue."));
QPushButton *ignoreButton = 0;
if (flags & AllowUnstashed) {
ignoreButton = msgBox->addButton(QMessageBox::Ignore);
ignoreButton->setToolTip(tr("Continue with local changes in working directory."));
}
QPushButton *cancelButton = msgBox->addButton(QMessageBox::Cancel);
cancelButton->setToolTip(tr("Cancel current command."));
msgBox->exec();
if (msgBox.isNull())
return;
if (msgBox->clickedButton() == discardButton) {
if (!client->synchronousReset(workingDir, QStringList(), errorMessage))
stashResult = StashFailed;
else
stashResult = StashUnchanged;
} else if (msgBox->clickedButton() == ignoreButton) { // At your own risk, so.
stashResult = NotStashed;
} else if (msgBox->clickedButton() == cancelButton) {
stashResult = StashCanceled;
} else if (msgBox->clickedButton() == stashButton) {
executeStash(keyword, errorMessage);
}
}
void GitClient::StashGuard::executeStash(const QString &keyword, QString *errorMessage)
{
message = creatorStashMessage(keyword);
if (!client->executeSynchronousStash(workingDir, message, errorMessage))
stashResult = StashFailed;
else
stashResult = Stashed;
}
void GitClient::StashGuard::preventPop()
{
pop = false;
}
bool GitClient::StashGuard::stashingFailed() const
case StashCanceled:
case StashFailed:
return !(flags & AllowUnstashed);
default:
return false;
}
}
} // namespace Internal
} // namespace Git