Newer
Older
// Short SHA 1, author, subject
return synchronousShortDescription(workingDirectory, revision,
QLatin1String(defaultShortLogFormatC),
description, errorMessage);
}
// Convenience working on a list of revisions
bool GitClient::synchronousShortDescriptions(const QString &workingDirectory, const QStringList &revisions,
QStringList *descriptions, QString *errorMessage)
{
descriptions->clear();
foreach (const QString &revision, revisions) {
QString description;
if (!synchronousShortDescription(workingDirectory, revision, &description, errorMessage)) {
descriptions->clear();
return false;
}
descriptions->push_back(description);
}
return true;
}
static inline QString msgCannotDetermineBranch(const QString &workingDirectory, const QString &why)
{
return GitClient::tr("Cannot retrieve branch of \"%1\": %2").arg(QDir::toNativeSeparators(workingDirectory), why);
}
// Retrieve head revision/branch
bool GitClient::synchronousTopRevision(const QString &workingDirectory, QString *revision,
QString *branch, QString *errorMessageIn)
{
QByteArray outputTextData;
QByteArray errorText;
QStringList arguments;
QString errorMessage;
do {
// get revision
if (revision) {
revision->clear();
arguments << QLatin1String("log") << QLatin1String(noColorOption)
<< QLatin1String("--max-count=1") << QLatin1String("--pretty=format:%H");
if (!fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
errorMessage = tr("Cannot retrieve top revision of \"%1\": %2").arg(QDir::toNativeSeparators(workingDirectory), commandOutputFromLocal8Bit(errorText));
break;
}
*revision = commandOutputFromLocal8Bit(outputTextData);
revision->remove(QLatin1Char('\n'));
} // revision desired
// get branch
if (branch) {
branch->clear();
arguments.clear();
arguments << QLatin1String("branch") << QLatin1String(noColorOption);
if (!fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
errorMessage = msgCannotDetermineBranch(workingDirectory, commandOutputFromLocal8Bit(errorText));
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, QString *errorMessage, bool *onBranch)
{
// Run 'status'. Note that git returns exitcode 1 if there are no added files.
QByteArray outputText;
QByteArray errorText;
QStringList statusArgs(QLatin1String("status"));
if (untracked)
statusArgs << QLatin1String("-u");
statusArgs << QLatin1String("-s") << QLatin1String("-b");
const bool statusRc = fullySynchronousGit(workingDirectory, statusArgs, &outputText, &errorText);
if (output)
*output = commandOutputFromLocal8Bit(outputText);
static const char * NO_BRANCH = "## HEAD (no branch)\n";
const bool branchKnown = !outputText.startsWith(NO_BRANCH);

Robert Loehning
committed
if (onBranch)
*onBranch = branchKnown;
// Is it something really fatal?
if (!statusRc && !branchKnown) {
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.count('\n') == 1)
return 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 QString pattern = QLatin1String("\trefs/heads/");
const int pos = line.lastIndexOf(pattern);
if (pos != -1) {
const QString branchName = line.mid(pos + pattern.count());
if (line.startsWith(headSha))
branches[0] = branchName;
else
branches.push_back(branchName);
}
}
}
return branches;
}
void GitClient::launchGitK(const QString &workingDirectory)
{
const QFileInfo binaryInfo(gitBinaryPath());
QDir foundBinDir(binaryInfo.dir());
const bool foundBinDirIsCmdDir = foundBinDir.dirName() == "cmd";
QProcessEnvironment env = processEnvironment();
if (tryLauchingGitK(env, workingDirectory, foundBinDir.path(), 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;
if (!gitDir.cd(QLatin1String(GIT_DIRECTORY))) {
*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;
// ## branch_name
// MM filename
// A new_unstaged_file
// R old -> new
// ?? missing_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(CommitData::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 == CommitData::UntrackedFile && !untrackedFiles.contains(it->second))
continue;
filteredFiles.append(*it);
}
commitData->files = filteredFiles;
if (commitData->files.isEmpty()) {
*errorMessage = msgNoChangedFiles();
return false;
}

Friedemann Kleint
committed
}
commitData->panelData.author = readConfigValue(workingDirectory, QLatin1String("user.name"));
commitData->panelData.email = readConfigValue(workingDirectory, QLatin1String("user.email"));
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@message".
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;
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);
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 = gitDir.absoluteFilePath(QLatin1String("MERGE_MSG"));
if (!QFileInfo(templateFilename).isFile())
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,
VCSBase::SubmitFileModel *model)
const QString renameSeparator = QLatin1String(" -> ");
const bool amend = !amendSHA1.isEmpty();
1744
1745
1746
1747
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
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
QStringList filesToAdd;
QStringList filesToRemove;
QStringList filesToReset;
int commitCount = 0;
for (int i = 0; i < model->rowCount(); ++i) {
const CommitData::FileState state = static_cast<CommitData::FileState>(model->data(i).toInt());
QString file = model->file(i);
const bool checked = model->checked(i);
if (checked)
++commitCount;
if (state == CommitData::UntrackedFile && checked)
filesToAdd.append(file);
if (state == CommitData::ModifiedStagedFile && !checked) {
filesToReset.append(file);
} else if (state == CommitData::AddedStagedFile && !checked) {
filesToReset.append(file);
} else if (state == CommitData::DeletedStagedFile && !checked) {
filesToReset.append(file);
} else if (state == CommitData::RenamedStagedFile && !checked) {
const int pos = file.indexOf(QLatin1String(" -> "));
const QString newFile = file.mid(pos + 4);
filesToReset.append(newFile);
} else if (state == CommitData::CopiedStagedFile && !checked) {
const QString newFile = file.mid(file.indexOf(renameSeparator) + renameSeparator.count());
filesToReset.append(newFile);
} else if (state == CommitData::UpdatedStagedFile && !checked) {
QTC_ASSERT(false, continue); // There should not be updated files when commiting!
}
if (state == CommitData::ModifiedFile && checked) {
filesToReset.removeAll(file);
filesToAdd.append(file);
} else if (state == CommitData::AddedFile && checked) {
QTC_ASSERT(false, continue); // these should be untracked!
} else if (state == CommitData::DeletedFile && checked) {
filesToReset.removeAll(file);
filesToRemove.append(file);
} else if (state == CommitData::RenamedFile && checked) {
QTC_ASSERT(false, continue); // git mv directly stages.
} else if (state == CommitData::CopiedFile && checked) {
QTC_ASSERT(false, continue); // only is noticed after adding a new file to the index
} else if (state == CommitData::UpdatedFile && checked) {
QTC_ASSERT(false, continue); // There should not be updated 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;
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
* VCSBase plugin. */
GitClient::RevertResult GitClient::revertI(QStringList files,
bool *ptrToIsDirectory,
QString *errorMessage,
bool revertStaging)
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
{
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.
const QStringList allStagedFiles = data.filterFiles(CommitData::ModifiedStagedFile);
const QStringList allUnstagedFiles = data.filterFiles(CommitData::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(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)