Skip to content
Snippets Groups Projects
gitclient.cpp 133 KiB
Newer Older
Orgad Shaneh's avatar
Orgad Shaneh committed
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. Finish "
                       "or abort it and then try again."));
Orgad Shaneh's avatar
Orgad Shaneh committed
        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, bool fixup)
Orgad Shaneh's avatar
Orgad Shaneh committed
{
    QStringList arguments;
    arguments << QLatin1String("rebase") << QLatin1String("-i");
    if (fixup)
        arguments << QLatin1String("--autosquash");
    arguments << commit + QLatin1Char('^');
Orgad Shaneh's avatar
Orgad Shaneh committed
    outputWindow()->appendCommand(workingDirectory, settings()->stringValue(GitSettings::binaryPathKey), arguments);
    if (fixup)
        m_disableEditor = true;
Orgad Shaneh's avatar
Orgad Shaneh committed
    VcsBase::Command *command = createCommand(workingDirectory, 0, true);
    command->addJob(arguments, -1);
    command->execute();
    command->setCookie(workingDirectory);
    new ConflictHandler(command, workingDirectory, QLatin1String("rebase"));
    if (fixup)
        m_disableEditor = false;
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;
hjk's avatar
hjk committed
    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"));
Tobias Hunger's avatar
Tobias Hunger committed
    if (branch.isEmpty())
        arguments << QLatin1String(pop ? "pop" : "apply") << stash;
Tobias Hunger's avatar
Tobias Hunger committed
    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() ?
Tobias Hunger's avatar
Tobias Hunger committed
                            tr("Cannot restore stash \"%1\": %2").
                            arg(nativeWorkingDir, stdErr) :
Tobias Hunger's avatar
Tobias Hunger committed
                            tr("Cannot restore stash \"%1\" to branch \"%2\": %3").
                            arg(nativeWorkingDir, branch, stdErr);
Tobias Hunger's avatar
Tobias Hunger committed
        if (errorMessage)
            *errorMessage = msg;
Tobias Hunger's avatar
Tobias Hunger committed
        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");
        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() ?
Tobias Hunger's avatar
Tobias Hunger committed
                            tr("Cannot remove stashes of \"%1\": %2").
                            arg(nativeWorkingDir, stdErr) :
Tobias Hunger's avatar
Tobias Hunger committed
                            tr("Cannot remove stash \"%1\" of \"%2\": %3").
                            arg(stash, nativeWorkingDir, stdErr);
Tobias Hunger's avatar
Tobias Hunger committed
        if (errorMessage)
            *errorMessage = msg;
Tobias Hunger's avatar
Tobias Hunger committed
        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);
Tobias Hunger's avatar
Tobias Hunger committed
        const QString msg = tr("Cannot retrieve stash list of \"%1\": %2").
                            arg(QDir::toNativeSeparators(workingDirectory),
                                commandOutputFromLocal8Bit(errorText));
        if (errorMessage)
            *errorMessage = msg;
            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
con's avatar
con committed
{
    QStringList arguments;
    arguments << QLatin1String("config") << configVar;

    QByteArray outputText;
    if (!fullySynchronousGit(workingDirectory, arguments, &outputText, &errorText, false))
        return QString();
    if (Utils::HostOsInfo::isWindowsHost())
        return QString::fromUtf8(outputText).remove(QLatin1Char('\r'));
    return commandOutputFromLocal8Bit(outputText);
con's avatar
con committed
}

// Read a single-line config value, return trimmed
QString GitClient::readConfigValue(const QString &workingDirectory, const QString &configVar) const
con's avatar
con committed
{
    return readConfig(workingDirectory, QStringList(configVar)).remove(QLatin1Char('\n'));
}

bool GitClient::cloneRepository(const QString &directory,const QByteArray &url)
{
    QDir workingDirectory(directory);
hjk's avatar
hjk 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") << QLatin1String(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 << QLatin1String(url) << workingDirectory.dirName();
Tobias Hunger's avatar
Tobias Hunger committed
        const Utils::SynchronousProcessResponse resp =
                synchronousGit(workingDirectory.path(), arguments, flags);
hjk's avatar
hjk committed
        // TODO: Turn this into a VcsBaseClient and use resetCachedVcsInfo(...)
hjk's avatar
hjk committed
        Core::ICore::vcsManager()->resetVersionControlForDirectory(workingDirectory.absolutePath());
        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
hjk's avatar
hjk committed
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()),
// determine version as '(major << 16) + (minor << 8) + patch' or 0.
Orgad Shaneh's avatar
Orgad Shaneh committed
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.
Orgad Shaneh's avatar
Orgad Shaneh committed
        m_cachedGitVersion = synchronousGitVersion(errorMessage);
        m_gitVersionForBinary = newGitBinary;

// determine version as '(major << 16) + (minor << 8) + patch' or 0.
Orgad Shaneh's avatar
Orgad Shaneh committed
unsigned GitClient::synchronousGitVersion(QString *errorMessage) const
    if (gitBinaryPath().isEmpty())
        return 0;

    // run git --version
    QByteArray outputText;
    QByteArray errorText;
Orgad Shaneh's avatar
Orgad Shaneh committed
    const bool rc = fullySynchronousGit(QString(), QStringList(QLatin1String("--version")), &outputText, &errorText, false);
Tobias Hunger's avatar
Tobias Hunger committed
        const QString msg = tr("Cannot determine git version: %1").arg(commandOutputFromLocal8Bit(errorText));
Orgad Shaneh's avatar
Orgad Shaneh committed
        if (errorMessage)
Orgad Shaneh's avatar
Orgad Shaneh committed
        else
            outputWindow()->append(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::StashInfo::StashInfo() :
    m_client(GitPlugin::instance()->gitClient())
bool GitClient::StashInfo::init(const QString &workingDirectory, const QString &keyword,
                                StashFlag flag)
{
    m_workingDir = workingDirectory;
    m_flags = flag;
Orgad Shaneh's avatar
Orgad Shaneh committed
    QString errorMessage;
    QString statusOutput;
    switch (m_client->gitStatus(m_workingDir, StatusMode(NoUntracked | NoSubmodules),
                              &statusOutput, &errorMessage)) {
    case GitClient::StatusChanged:
        if (m_flags & NoPrompt)
            executeStash(keyword, &errorMessage);
        else
            stashPrompt(keyword, statusOutput, &errorMessage);
        break;
    case GitClient::StatusUnchanged:
        m_stashResult = StashUnchanged;
        break;
    case GitClient::StatusFailed:
        m_stashResult = StashFailed;
        break;
    }
    if (m_stashResult == StashFailed)
Orgad Shaneh's avatar
Orgad Shaneh committed
        VcsBase::VcsBaseOutputWindow::instance()->appendError(errorMessage);
    return !stashingFailed();
void GitClient::StashInfo::stashPrompt(const QString &keyword, const QString &statusOutput,
                                       QString *errorMessage)
Orgad Shaneh's avatar
Orgad Shaneh committed
    QMessageBox msgBox(QMessageBox::Question, tr("Uncommitted Changes Found"),
                       tr("What would you like to do with local changes in:")
                       + QLatin1String("\n\n\"") + m_workingDir + QLatin1Char('\"'),
Orgad Shaneh's avatar
Orgad Shaneh committed
                       QMessageBox::NoButton, Core::ICore::mainWindow());
Orgad Shaneh's avatar
Orgad Shaneh committed
    msgBox.setDetailedText(statusOutput);
Orgad Shaneh's avatar
Orgad Shaneh committed
    QPushButton *stashButton = msgBox.addButton(tr("Stash"), QMessageBox::AcceptRole);
    stashButton->setToolTip(tr("Stash local changes and continue."));

Orgad Shaneh's avatar
Orgad Shaneh committed
    QPushButton *discardButton = msgBox.addButton(tr("Discard"), QMessageBox::AcceptRole);
    discardButton->setToolTip(tr("Discard (reset) local changes and continue."));

    QPushButton *ignoreButton = 0;
    if (m_flags & AllowUnstashed) {
Orgad Shaneh's avatar
Orgad Shaneh committed
        ignoreButton = msgBox.addButton(QMessageBox::Ignore);
        ignoreButton->setToolTip(tr("Continue with local changes in working directory."));
    }

Orgad Shaneh's avatar
Orgad Shaneh committed
    QPushButton *cancelButton = msgBox.addButton(QMessageBox::Cancel);
    cancelButton->setToolTip(tr("Cancel current command."));

Orgad Shaneh's avatar
Orgad Shaneh committed
    msgBox.exec();
Orgad Shaneh's avatar
Orgad Shaneh committed
    if (msgBox.clickedButton() == discardButton) {
        if (!m_client->synchronousReset(m_workingDir, QStringList(), errorMessage))
            m_stashResult = StashFailed;
            m_stashResult = StashUnchanged;
Orgad Shaneh's avatar
Orgad Shaneh committed
    } else if (msgBox.clickedButton() == ignoreButton) { // At your own risk, so.
        m_stashResult = NotStashed;
Orgad Shaneh's avatar
Orgad Shaneh committed
    } else if (msgBox.clickedButton() == cancelButton) {
        m_stashResult = StashCanceled;
Orgad Shaneh's avatar
Orgad Shaneh committed
    } else if (msgBox.clickedButton() == stashButton) {
        executeStash(keyword, errorMessage);
    }
}

void GitClient::StashInfo::executeStash(const QString &keyword, QString *errorMessage)
    m_message = creatorStashMessage(keyword);
    if (!m_client->executeSynchronousStash(m_workingDir, m_message, errorMessage))
        m_stashResult = StashFailed;
        m_stashResult = Stashed;
bool GitClient::StashInfo::stashingFailed() const
    switch (m_stashResult) {
    case StashCanceled:
    case StashFailed:
Orgad Shaneh's avatar
Orgad Shaneh committed
        return true;
    case NotStashed:
        return !(m_flags & AllowUnstashed);
Orgad Shaneh's avatar
Orgad Shaneh committed
    default:
        return false;
    }
}

void GitClient::StashInfo::end()
{
    if (m_stashResult == Stashed) {
        QString stashName;
        if (m_client->stashNameFromMessage(m_workingDir, m_message, &stashName))
            m_client->stashPop(m_workingDir, stashName);
    }
    m_stashResult = NotStashed;
}
} // namespace Internal
} // namespace Git
Friedemann Kleint's avatar
Friedemann Kleint committed

#include "gitclient.moc"