Skip to content
Snippets Groups Projects
gitclient.cpp 95.7 KiB
Newer Older
    }

    // Check for changes
    QString output;
    switch (gitStatus(repoDirectory, false, &output, errorMessage)) {
    case StatusChanged:
        break;
    case StatusUnchanged:
        return RevertUnchanged;
    case StatusFailed:
        return RevertFailed;
    }
    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 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))
    QStringList filesToRevert = unstagedFiles;
    if (revertStaging)
        filesToRevert += stagedFiles;
    if (!synchronousCheckoutFiles(repoDirectory, filesToRevert, QString(), errorMessage, revertStaging))
void GitClient::revert(const QStringList &files, bool revertStaging)
    switch (revertI(files, &isDirectory, &errorMessage, revertStaging)) {
        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);
        outputWindow()->append(errorMessage);
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)
con's avatar
con committed
{
    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();
Friedemann Kleint's avatar
Friedemann Kleint committed
    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));
con's avatar
con committed
}

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::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)
con's avatar
con committed
{
    // 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"));
    arguments << QLatin1String("-r") << QLatin1String(noColorOption);
    executeGit(workingDirectory, arguments, 0, true);
}

void GitClient::stashList(const QString &workingDirectory)
{
    QStringList arguments(QLatin1String("stash"));
    arguments << QLatin1String("list") << QLatin1String(noColorOption);
    executeGit(workingDirectory, arguments, 0, true);
con's avatar
con committed
}

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;
}

con's avatar
con committed
QString GitClient::readConfig(const QString &workingDirectory, const QStringList &configVar)
{
    QStringList arguments;
    arguments << QLatin1String("config") << configVar;

    QByteArray outputText;
    if (fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText, false))
        return commandOutputFromLocal8Bit(outputText);
con's avatar
con committed
    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'));
}

bool GitClient::cloneRepository(const QString &directory,const QByteArray &url)
{
    QDir workingDirectory(directory);
Tobias Hunger's avatar
Tobias Hunger committed
    const unsigned flags = VCSBase::VCSBasePlugin::SshPasswordPrompt |
            VCSBase::VCSBasePlugin::ShowStdOutInLogWindow|
            VCSBase::VCSBasePlugin::ShowSuccessMessage;

    if (workingDirectory.exists()) {
        if (!synchronousInit(workingDirectory.path()))
            return false;

        QStringList arguments(QLatin1String("remote"));
        arguments << QLatin1String("add") << QLatin1String("origin") << url;
Tobias Hunger's avatar
Tobias Hunger committed
        if (!fullySynchronousGit(workingDirectory.path(), arguments, 0, 0, true))
            return false;

        arguments.clear();
        arguments << QLatin1String("fetch");
Tobias Hunger's avatar
Tobias Hunger committed
        const Utils::SynchronousProcessResponse resp =
                synchronousGit(workingDirectory.path(), arguments, flags);
        if (resp.result != Utils::SynchronousProcessResponse::Finished)
            return false;

        arguments.clear();
Tobias Hunger's avatar
Tobias Hunger committed
        arguments << QLatin1String("config")
                  << QLatin1String("branch.master.remote")
                  <<  QLatin1String("origin");
        if (!fullySynchronousGit(workingDirectory.path(), arguments, 0, 0, true))
Tobias Hunger's avatar
Tobias Hunger committed
        arguments << QLatin1String("config")
                  << QLatin1String("branch.master.merge")
                  << QLatin1String("refs/heads/master");
        if (!fullySynchronousGit(workingDirectory.path(), arguments, 0, 0, true))
            return false;

        return true;
    } else {
        QStringList arguments(QLatin1String("clone"));
        arguments << url << workingDirectory.dirName();
        workingDirectory.cdUp();
Tobias Hunger's avatar
Tobias Hunger committed
        const Utils::SynchronousProcessResponse resp =
                synchronousGit(workingDirectory.path(), arguments, flags);
        return resp.result == Utils::SynchronousProcessResponse::Finished;
    }
}

QString GitClient::vcsGetRepositoryURL(const QString &directory)
{
    QStringList arguments(QLatin1String("config"));
    QByteArray outputText;

    arguments << QLatin1String("remote.origin.url");

Tobias Hunger's avatar
Tobias Hunger committed
    if (fullySynchronousGit(directory, arguments, &outputText, 0, false)) {
        return commandOutputFromLocal8Bit(outputText);
    }
    return QString();
}

GitSettings GitClient::settings() const
{
    return m_settings;
}

void GitClient::setSettings(const GitSettings &s)
{
    if (s != m_settings) {
        m_settings = s;
        if (QSettings *coreSettings = m_core->settings())
            m_settings.toSettings(coreSettings);
        m_hasCachedGitVersion = false;
        m_plugin->gitVersionControl()->emitConfigurationChanged();

void GitClient::connectRepositoryChanged(const QString & repository, GitCommand *cmd)
{
    // Bind command success termination with repository to changed signal
    if (!m_repositoryChangedSignalMapper) {
        m_repositoryChangedSignalMapper = new QSignalMapper(this);
        connect(m_repositoryChangedSignalMapper, SIGNAL(mapped(QString)),
                m_plugin->gitVersionControl(), SIGNAL(repositoryChanged(QString)));
    }
    m_repositoryChangedSignalMapper->setMapping(cmd, repository);
    connect(cmd, SIGNAL(success()), m_repositoryChangedSignalMapper, SLOT(map()),
            Qt::QueuedConnection);
}
// determine version as '(major << 16) + (minor << 8) + patch' or 0.
unsigned GitClient::gitVersion(bool silent, QString *errorMessage  /* = 0 */)
    if (!m_hasCachedGitVersion) {
        // Do not execute repeatedly if that fails (due to git
        // not being installed) until settings are changed.
        m_cachedGitVersion = synchronousGitVersion(silent, errorMessage);
        m_hasCachedGitVersion = true;
    }
QString GitClient::gitVersionString(bool silent, QString *errorMessage)
    if (const unsigned version = gitVersion(silent, errorMessage)) {
        QString rc;
        QTextStream(&rc) << (version >> 16) << '.'
                << (0xFF & (version >> 8)) << '.'
                << (version & 0xFF);
        return rc;
    }
    return QString();
}

// determine version as '(major << 16) + (minor << 8) + patch' or 0.
unsigned GitClient::synchronousGitVersion(bool silent, QString *errorMessage /* = 0 */)
{
    // run git --version
    QByteArray outputText;
    QByteArray errorText;
    const bool rc = fullySynchronousGit(QString(), QStringList("--version"), &outputText, &errorText);
    if (!rc) {
        const QString msg = tr("Unable to determine git version: %1").arg(commandOutputFromLocal8Bit(errorText));
        if (errorMessage) {
            *errorMessage = msg;
        } else {
                outputWindow()->append(msg);
                outputWindow()->appendError(msg);
        }
        return 0;
    }
    // cut 'git version 1.6.5.1.sha'
    const QString output = commandOutputFromLocal8Bit(outputText);
    const QRegExp versionPattern(QLatin1String("^[^\\d]+([\\d])\\.([\\d])\\.([\\d]).*$"));
    QTC_ASSERT(versionPattern.isValid(), return 0);
    QTC_ASSERT(versionPattern.exactMatch(output), return 0);
    const unsigned major = versionPattern.cap(1).toUInt();
    const unsigned minor = versionPattern.cap(2).toUInt();
    const unsigned patch = versionPattern.cap(3).toUInt();
    return version(major, minor, patch);
Friedemann Kleint's avatar
Friedemann Kleint committed

#include "gitclient.moc"