diff --git a/src/libs/utils/synchronousprocess.cpp b/src/libs/utils/synchronousprocess.cpp index 667416a3406..597be2932be 100644 --- a/src/libs/utils/synchronousprocess.cpp +++ b/src/libs/utils/synchronousprocess.cpp @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include @@ -366,5 +368,108 @@ void SynchronousProcess::processStdErr(bool emitSignals) } } +// Path utilities + +enum OS_Type { OS_Mac, OS_Windows, OS_Unix }; + +#ifdef Q_OS_WIN +static const OS_Type pathOS = OS_Windows; +#else +# ifdef Q_OS_MAC +static const OS_Type pathOS = OS_Mac; +# else +static const OS_Type pathOS = OS_Unix; +# endif +#endif + +// Locate a binary in a directory, applying all kinds of +// extensions the operating system supports. +static QString checkBinary(const QDir &dir, const QString &binary) +{ + // naive UNIX approach + const QFileInfo info(dir.filePath(binary)); + if (info.isFile() && info.isExecutable()) + return info.absoluteFilePath(); + + // Does the OS have some weird extension concept or does the + // binary have a 3 letter extension? + if (pathOS == OS_Unix) + return QString(); + const int dotIndex = binary.lastIndexOf(QLatin1Char('.')); + if (dotIndex != -1 && dotIndex == binary.size() - 4) + return QString(); + + switch (pathOS) { + case OS_Unix: + break; + case OS_Windows: { + static const char *windowsExtensions[] = {".cmd", ".bat", ".exe", ".com" }; + // Check the Windows extensions using the order + const int windowsExtensionCount = sizeof(windowsExtensions)/sizeof(const char*); + for (int e = 0; e < windowsExtensionCount; e ++) { + const QFileInfo windowsBinary(dir.filePath(binary + QLatin1String(windowsExtensions[e]))); + if (windowsBinary.isFile() && windowsBinary.isExecutable()) + return windowsBinary.absoluteFilePath(); + } + } + break; + case OS_Mac: { + // Check for Mac app folders + const QFileInfo appFolder(dir.filePath(binary + QLatin1String(".app"))); + if (appFolder.isDir()) { + QString macBinaryPath = appFolder.absoluteFilePath(); + macBinaryPath += QLatin1String("/Contents/MacOS/"); + macBinaryPath += binary; + const QFileInfo macBinary(macBinaryPath); + if (macBinary.isFile() && macBinary.isExecutable()) + return macBinary.absoluteFilePath(); + } + } + break; + } + return QString(); +} + +QString SynchronousProcess::locateBinary(const QString &path, const QString &binary) +{ + // Absolute file? + const QFileInfo absInfo(binary); + if (absInfo.isAbsolute()) + return checkBinary(absInfo.dir(), absInfo.fileName()); + + // Windows finds binaries in the current directory + if (pathOS == OS_Windows) { + const QString currentDirBinary = checkBinary(QDir::current(), binary); + if (!currentDirBinary.isEmpty()) + return currentDirBinary; + } + + const QStringList paths = path.split(pathSeparator()); + if (paths.empty()) + return QString(); + const QStringList::const_iterator cend = paths.constEnd(); + for (QStringList::const_iterator it = paths.constBegin(); it != cend; ++it) { + const QDir dir(*it); + const QString rc = checkBinary(dir, binary); + if (!rc.isEmpty()) + return rc; + } + return QString(); +} + +QString SynchronousProcess::locateBinary(const QString &binary) +{ + const QByteArray path = qgetenv("PATH"); + return locateBinary(QString::fromLocal8Bit(path), binary); +} + +QChar SynchronousProcess::pathSeparator() +{ + if (pathOS == OS_Windows) + return QLatin1Char(';'); + return QLatin1Char(':'); +} + + } // namespace Utils } // namespace Core diff --git a/src/libs/utils/synchronousprocess.h b/src/libs/utils/synchronousprocess.h index e9218f43d80..80a04a1303e 100644 --- a/src/libs/utils/synchronousprocess.h +++ b/src/libs/utils/synchronousprocess.h @@ -114,6 +114,12 @@ public: SynchronousProcessResponse run(const QString &binary, const QStringList &args); + // Helpers to find binaries. Do not use it for other path variables + // and file types. + static QString locateBinary(const QString &binary); + static QString locateBinary(const QString &path, const QString &binary); + static QChar pathSeparator(); + signals: void stdOut(const QByteArray &data, bool firstTime); void stdErr(const QByteArray &data, bool firstTime); diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp index 87f06931200..a3ad3ba84ff 100644 --- a/src/plugins/git/gitclient.cpp +++ b/src/plugins/git/gitclient.cpp @@ -102,8 +102,10 @@ GitClient::GitClient(GitPlugin* plugin) m_plugin(plugin), m_core(Core::ICore::instance()) { - if (QSettings *s = m_core->settings()) + if (QSettings *s = m_core->settings()) { m_settings.fromSettings(s); + m_binaryPath = m_settings.gitBinaryPath(); + } } GitClient::~GitClient() @@ -478,7 +480,7 @@ GitCommand *GitClient::createCommand(const QString &workingDirectory, if (m_settings.adoptPath) environment.set(QLatin1String("PATH"), m_settings.path); - GitCommand* command = new GitCommand(workingDirectory, environment); + GitCommand* command = new GitCommand(m_binaryPath, workingDirectory, environment); if (outputToWindow) { if (!editor) { // assume that the commands output is the important thing connect(command, SIGNAL(outputData(QByteArray)), this, SLOT(appendDataAndPopup(QByteArray))); @@ -527,10 +529,9 @@ bool GitClient::synchronousGit(const QString &workingDirectory, { if (Git::Constants::debug) qDebug() << "synchronousGit" << workingDirectory << arguments; - const QString binary = QLatin1String(Constants::GIT_BINARY); if (logCommandToWindow) - m_plugin->outputWindow()->append(formatCommand(binary, arguments)); + m_plugin->outputWindow()->append(formatCommand(m_binaryPath, arguments)); QProcess process; process.setWorkingDirectory(workingDirectory); @@ -540,7 +541,7 @@ bool GitClient::synchronousGit(const QString &workingDirectory, environment.set(QLatin1String("PATH"), m_settings.path); process.setEnvironment(environment.toStringList()); - process.start(binary, arguments); + process.start(m_binaryPath, arguments); if (!process.waitForFinished()) { if (errorText) *errorText = "Error: Git timed out"; @@ -1000,6 +1001,6 @@ void GitClient::setSettings(const GitSettings &s) m_settings = s; if (QSettings *s = m_core->settings()) m_settings.toSettings(s); + m_binaryPath = m_settings.gitBinaryPath(); } } - diff --git a/src/plugins/git/gitclient.h b/src/plugins/git/gitclient.h index c920ffc1b32..edd7e05c9e3 100644 --- a/src/plugins/git/gitclient.h +++ b/src/plugins/git/gitclient.h @@ -176,6 +176,7 @@ private: GitPlugin *m_plugin; Core::ICore *m_core; GitSettings m_settings; + QString m_binaryPath; }; diff --git a/src/plugins/git/gitcommand.cpp b/src/plugins/git/gitcommand.cpp index 8362926cecc..54bfcea2ab4 100644 --- a/src/plugins/git/gitcommand.cpp +++ b/src/plugins/git/gitcommand.cpp @@ -61,8 +61,10 @@ GitCommand::Job::Job(const QStringList &a, int t) : { } -GitCommand::GitCommand(const QString &workingDirectory, +GitCommand::GitCommand(const QString &binaryPath, + const QString &workingDirectory, ProjectExplorer::Environment &environment) : + m_binaryPath(binaryPath), m_workingDirectory(workingDirectory), m_environment(environmentToList(environment)) { @@ -109,7 +111,7 @@ void GitCommand::run() if (Git::Constants::debug) qDebug() << "GitCommand::run" << j << '/' << count << m_jobs.at(j).arguments; - process.start(QLatin1String(Constants::GIT_BINARY), m_jobs.at(j).arguments); + process.start(m_binaryPath, m_jobs.at(j).arguments); if (!process.waitForFinished(m_jobs.at(j).timeout * 1000)) { ok = false; error += QLatin1String("Error: Git timed out"); diff --git a/src/plugins/git/gitcommand.h b/src/plugins/git/gitcommand.h index 32b76bf3485..1d661706151 100644 --- a/src/plugins/git/gitcommand.h +++ b/src/plugins/git/gitcommand.h @@ -43,9 +43,11 @@ namespace Internal { class GitCommand : public QObject { + Q_DISABLE_COPY(GitCommand) Q_OBJECT public: - explicit GitCommand(const QString &workingDirectory, + explicit GitCommand(const QString &binaryPath, + const QString &workingDirectory, ProjectExplorer::Environment &environment); @@ -67,8 +69,7 @@ private: int timeout; }; - QStringList environment() const; - + const QString m_binaryPath; const QString m_workingDirectory; const QStringList m_environment; diff --git a/src/plugins/git/gitsettings.cpp b/src/plugins/git/gitsettings.cpp index 02a1acc1d9e..c379bb3fba5 100644 --- a/src/plugins/git/gitsettings.cpp +++ b/src/plugins/git/gitsettings.cpp @@ -32,9 +32,13 @@ ***************************************************************************/ #include "gitsettings.h" +#include "gitconstants.h" + +#include #include #include +#include static const char *groupC = "Git"; static const char *sysEnvKeyC = "SysEnv"; @@ -79,5 +83,30 @@ bool GitSettings::equals(const GitSettings &s) const return adoptPath == s.adoptPath && path == s.path && logCount == s.logCount && timeout == s.timeout; } +QString GitSettings::gitBinaryPath(bool *ok, QString *errorMessage) const +{ + // Locate binary in path if one is specified, otherwise default + // to pathless binary + if (ok) + *ok = true; + if (errorMessage) + errorMessage->clear(); + const QString binary = QLatin1String(Constants::GIT_BINARY); + // Easy, git is assumed to be elsewhere accessible + if (!adoptPath) + return binary; + // Search in path? + const QString pathBinary = Core::Utils::SynchronousProcess::locateBinary(path, binary); + if (pathBinary.isEmpty()) { + if (ok) + *ok = false; + if (errorMessage) + *errorMessage = QCoreApplication::translate("GitSettings", + "The binary '%1' could not be located in the path '%2'").arg(binary, path); + return binary; + } + return pathBinary; +} + } } diff --git a/src/plugins/git/gitsettings.h b/src/plugins/git/gitsettings.h index c2463eb326d..05dc250c578 100644 --- a/src/plugins/git/gitsettings.h +++ b/src/plugins/git/gitsettings.h @@ -51,6 +51,8 @@ struct GitSettings void fromSettings(QSettings *); void toSettings(QSettings *) const; + QString gitBinaryPath(bool *ok = 0, QString *errorMessage = 0) const; + bool equals(const GitSettings &s) const; bool adoptPath; diff --git a/src/plugins/git/settingspage.cpp b/src/plugins/git/settingspage.cpp index 121c7cd889b..4fdd672eac4 100644 --- a/src/plugins/git/settingspage.cpp +++ b/src/plugins/git/settingspage.cpp @@ -36,8 +36,10 @@ #include "gitplugin.h" #include +#include -using namespace Git::Internal; +namespace Git { +namespace Internal { SettingsPageWidget::SettingsPageWidget(QWidget *parent) : QWidget(parent) @@ -101,6 +103,17 @@ void SettingsPage::apply() { if (!m_widget) return; + const GitSettings newSettings = m_widget->settings(); + // Warn if git cannot be found in path if the widget is on top + if (m_widget->isVisible()) { + bool gitFoundOk; + QString errorMessage; + newSettings.gitBinaryPath(&gitFoundOk, &errorMessage); + if (!gitFoundOk) + QMessageBox::warning(m_widget, tr("Git Settings"), errorMessage); + } - GitPlugin::instance()->setSettings(m_widget->settings()); + GitPlugin::instance()->setSettings(newSettings); +} +} }