Newer
Older
QString *errorMessage)
{
QMap<QString,QString> result;
QString output;
QString error;
QStringList args(QLatin1String("-v"));
if (!synchronousRemoteCmd(workingDirectory, args, &output, &error)) {
if (errorMessage)
*errorMessage = error;
else
outputWindow()->append(error);
return result;
}
QStringList remotes = output.split(QLatin1String("\n"));
foreach (const QString &remote, remotes) {
if (!remote.endsWith(QLatin1String(" (push)")))
continue;
QStringList tokens = remote.split(QRegExp(QLatin1String("\\s")),
QString::SkipEmptyParts);
if (tokens.count() != 3)
continue;
result.insert(tokens.at(0), tokens.at(1));
}
return result;
}
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
QStringList GitClient::synchronousSubmoduleStatus(const QString &workingDirectory,
QString *errorMessage)
{
QByteArray outputTextData;
QByteArray errorText;
QStringList arguments;
// get submodule status
arguments << QLatin1String("submodule") << QLatin1String("status");
if (!fullySynchronousGit(workingDirectory, arguments, &outputTextData, &errorText)) {
QString error = tr("Cannot retrieve submodule status of \"%1\": %2")
.arg(QDir::toNativeSeparators(workingDirectory),
commandOutputFromLocal8Bit(errorText));
if (errorMessage)
*errorMessage = error;
else
outputWindow()->append(error);
return QStringList();
}
return commandOutputLinesFromLocal8Bit(outputTextData);
}
SubmoduleDataMap GitClient::submoduleList(const QString &workingDirectory)
SubmoduleDataMap result;
QString gitmodulesFileName = workingDirectory + QLatin1String("/.gitmodules");
if (!QFile::exists(gitmodulesFileName))
return result;
static QMap<QString, SubmoduleDataMap> cachedSubmoduleData;
if (cachedSubmoduleData.contains(workingDirectory))
return cachedSubmoduleData.value(workingDirectory);
QStringList args(QLatin1String("-l"));
QStringList allConfigs = readConfig(workingDirectory, args).split(QLatin1Char('\n'));
const QString submoduleLineStart = QLatin1String("submodule.");
foreach (const QString &configLine, allConfigs) {
if (!configLine.startsWith(submoduleLineStart))
continue;
int nameStart = submoduleLineStart.size();
int nameEnd = configLine.indexOf(QLatin1Char('.'), nameStart);
QString submoduleName = configLine.mid(nameStart, nameEnd - nameStart);
SubmoduleData submoduleData;
if (result.contains(submoduleName))
submoduleData = result[submoduleName];
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
if (configLine.mid(nameEnd, 5) == QLatin1String(".url="))
submoduleData.url = configLine.mid(nameEnd + 5);
else if (configLine.mid(nameEnd, 8) == QLatin1String(".ignore="))
submoduleData.ignore = configLine.mid(nameEnd + 8);
else
continue;
result.insert(submoduleName, submoduleData);
}
// if config found submodules
if (!result.isEmpty()) {
QSettings gitmodulesFile(gitmodulesFileName, QSettings::IniFormat);
foreach (const QString &submoduleName, result.keys()) {
gitmodulesFile.beginGroup(QLatin1String("submodule \"") +
submoduleName + QLatin1Char('"'));
result[submoduleName].dir = gitmodulesFile.value(QLatin1String("path")).toString();
QString ignore = gitmodulesFile.value(QLatin1String("ignore")).toString();
if (!ignore.isEmpty() && result[submoduleName].ignore.isEmpty())
result[submoduleName].ignore = ignore;
gitmodulesFile.endGroup();
}
cachedSubmoduleData.insert(workingDirectory, result);
return result;
}
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 = msgCannotRun(QLatin1String("git show"), workingDirectory, commandOutputFromLocal8Bit(errorText));
*output = commandOutputFromLocal8Bit(outputText);
// Retrieve list of files to be cleaned
bool GitClient::cleanList(const QString &workingDirectory, const QString &flag, QStringList *files, QString *errorMessage)
{
QStringList args;
args << QLatin1String("clean") << QLatin1String("--dry-run") << flag;
QByteArray outputText;
QByteArray errorText;
const bool rc = fullySynchronousGit(workingDirectory, args, &outputText, &errorText);
*errorMessage = msgCannotRun(QLatin1String("git clean"), 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::synchronousCleanList(const QString &workingDirectory, QStringList *files,
QStringList *ignoredFiles, QString *errorMessage)
{
bool res = cleanList(workingDirectory, QLatin1String("-df"), files, errorMessage);
res &= cleanList(workingDirectory, QLatin1String("-dXf"), ignoredFiles, errorMessage);
SubmoduleDataMap submodules = submoduleList(workingDirectory);
foreach (const SubmoduleData &submodule, submodules) {
if (submodule.ignore != QLatin1String("all")
&& submodule.ignore != QLatin1String("dirty")) {
res &= synchronousCleanList(workingDirectory + QLatin1Char('/') + submodule.dir,
files, ignoredFiles, errorMessage);
}
}
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());
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)), this, SLOT(appendOutputDataSilently(QByteArray)));
connect(command, SIGNAL(outputData(QByteArray)), this, SLOT(appendOutputData(QByteArray)));
if (editor)
connect(command, SIGNAL(outputData(QByteArray)), editor, SLOT(setPlainTextDataFiltered(QByteArray)));
connect(command, SIGNAL(errorText(QString)), outputWindow(), SLOT(appendError(QString)));
return command;
}
// Execute a single command
VcsBase::Command *GitClient::executeGit(const QString &workingDirectory,
int editorLineNumber)
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(VcsBase::Command::NoReport);
command->setUnixTerminalDisabled(false);
command->setExpectChanges(expectChanges);
command->execute();
return command;
QProcessEnvironment GitClient::processEnvironment() const
QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
QString gitPath = settings()->stringValue(GitSettings::pathKey);
if (!gitPath.isEmpty()) {
gitPath += Utils::HostOsInfo::pathListSeparator();
gitPath += environment.value(QLatin1String("PATH"));
environment.insert(QLatin1String("PATH"), gitPath);
}
if (Utils::HostOsInfo::isWindowsHost()
&& settings()->boolValue(GitSettings::winSetHomeEnvironmentKey)) {
environment.insert(QLatin1String("HOME"), QDir::toNativeSeparators(QDir::homePath()));
environment.insert(QLatin1String("GIT_EDITOR"), m_disableEditor ? QLatin1String("true") : m_gitQtcEditor);
// Set up SSH and C locale (required by git using perl).
VcsBasePlugin::setProcessEnvironment(&environment, false);
return environment;
}
bool GitClient::beginStashScope(const QString &workingDirectory, const QString &keyword, StashFlag flag)
{
const QString repoDirectory = findRepositoryForDirectory(workingDirectory);
QTC_ASSERT(!repoDirectory.isEmpty(), return false);
StashInfo &stashInfo = m_stashInfo[repoDirectory];
return stashInfo.init(repoDirectory, keyword, flag);
}
GitClient::StashInfo &GitClient::stashInfo(const QString &workingDirectory)
{
const QString repoDirectory = findRepositoryForDirectory(workingDirectory);
QTC_CHECK(m_stashInfo.contains(repoDirectory));
return m_stashInfo[repoDirectory];
}
void GitClient::endStashScope(const QString &workingDirectory)
{
const QString repoDirectory = findRepositoryForDirectory(workingDirectory);
QTC_ASSERT(m_stashInfo.contains(repoDirectory), return);
m_stashInfo[repoDirectory].end();
bool GitClient::isValidRevision(const QString &revision) const
{
if (revision.length() < 1)
return false;
for (int i = 0; i < revision.length(); ++i)
if (revision.at(i) != QLatin1Char('0'))
return true;
return false;
}
// Synchronous git execution using Utils::SynchronousProcess, with
// log windows updating.
Utils::SynchronousProcessResponse GitClient::synchronousGit(const QString &workingDirectory,
const QStringList &gitArguments,
unsigned flags,
QTextCodec *outputCodec)
return VcsBasePlugin::runVcs(workingDirectory, gitBinaryPath(), gitArguments,
settings()->intValue(GitSettings::timeoutKey) * 1000,
processEnvironment(),
flags, outputCodec);
}
bool GitClient::fullySynchronousGit(const QString &workingDirectory,
const QStringList &gitArguments,
QByteArray* outputText,
QByteArray* errorText,
unsigned flags) const
return VcsBasePlugin::runFullySynchronous(workingDirectory, gitBinaryPath(), gitArguments,
processEnvironment(), outputText, errorText,
settings()->intValue(GitSettings::timeoutKey) * 1000,
flags);
void GitClient::updateSubmodulesIfNeeded(const QString &workingDirectory, bool prompt)
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
if (!m_updatedSubmodules.isEmpty() || submoduleList(workingDirectory).isEmpty())
return;
QStringList submoduleStatus = synchronousSubmoduleStatus(workingDirectory);
if (submoduleStatus.isEmpty())
return;
bool updateNeeded = false;
foreach (const QString &status, submoduleStatus) {
if (status.startsWith(QLatin1Char('+'))) {
updateNeeded = true;
break;
}
}
if (!updateNeeded)
return;
if (prompt && QMessageBox::question(Core::ICore::mainWindow(), tr("Submodules Found"),
tr("Would you like to update submodules?"),
QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
return;
}
foreach (const QString &statusLine, submoduleStatus) {
// stash only for lines starting with +
// because only they would be updated
if (!statusLine.startsWith(QLatin1Char('+')))
continue;
// get submodule name
const int nameStart = statusLine.indexOf(QLatin1Char(' '), 2) + 1;
const int nameLength = statusLine.indexOf(QLatin1Char(' '), nameStart) - nameStart;
const QString submoduleDir = workingDirectory + QLatin1Char('/')
+ statusLine.mid(nameStart, nameLength);
if (beginStashScope(submoduleDir, QLatin1String("SubmoduleUpdate"))) {
m_updatedSubmodules.append(submoduleDir);
} else {
finishSubmoduleUpdate();
return;
}
}
QStringList arguments;
arguments << QLatin1String("submodule") << QLatin1String("update");
VcsBase::Command *cmd = executeGit(workingDirectory, arguments, 0, true, true);
connect(cmd, SIGNAL(finished(bool,int,QVariant)), this, SLOT(finishSubmoduleUpdate()));
void GitClient::finishSubmoduleUpdate()
foreach (const QString &submoduleDir, m_updatedSubmodules)
endStashScope(submoduleDir);
m_updatedSubmodules.clear();
void GitClient::fetchFinished(const QVariant &cookie)
{
GitPlugin::instance()->updateBranches(cookie.toString());
}
// 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, StatusMode mode,
QString *output, QString *errorMessage)
{
// Run 'status'. Note that git returns exitcode 1 if there are no added files.
QByteArray outputText;
QByteArray errorText;
QStringList statusArgs = statusArguments();
if (mode & NoUntracked)
statusArgs << QLatin1String("--untracked-files=no");
statusArgs << QLatin1String("--untracked-files=all");
if (mode & NoSubmodules)
statusArgs << QLatin1String("--ignore-submodules=all");
statusArgs << QLatin1String("-s") << QLatin1String("-b");
const bool statusRc = fullySynchronousGit(workingDirectory, statusArgs,
&outputText, &errorText, false);
if (output)
*output = commandOutputFromLocal8Bit(outputText);
static const char * NO_BRANCH = "## HEAD (no branch)\n";
const bool branchKnown = !outputText.startsWith(NO_BRANCH);
// 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)
QList<QByteArray> lines = outputText.split('\n');
foreach (const QByteArray &line, lines)
if (!line.isEmpty() && !line.startsWith('#'))
return StatusChanged;
return StatusUnchanged;
}
GitClient::CommandInProgress GitClient::checkCommandInProgressInGitDir(const QString &gitDir)
if (QFile::exists(gitDir + QLatin1String("/MERGE_HEAD")))
return Merge;
else if (QFile::exists(gitDir + QLatin1String("/rebase-apply/rebasing")))
return Rebase;
else if (QFile::exists(gitDir + QLatin1String("/rebase-merge")))
return RebaseMerge;
else if (QFile::exists(gitDir + QLatin1String("/REVERT_HEAD")))
return Revert;
else if (QFile::exists(gitDir + QLatin1String("/CHERRY_PICK_HEAD")))
return CherryPick;
else
return NoCommand;
}
GitClient::CommandInProgress GitClient::checkCommandInProgress(const QString &workingDirectory)
{
return checkCommandInProgressInGitDir(findGitDirForRepository(workingDirectory));
}
void GitClient::continueCommandIfNeeded(const QString &workingDirectory)
{
CommandInProgress command = checkCommandInProgress(workingDirectory);
switch (command) {
continuePreviousGitCommand(workingDirectory, tr("Continue Rebase"),
tr("Rebase is in progress. What do you want to do?"),
tr("Continue"), QLatin1String("rebase"),
command != RebaseMerge);
continuePreviousGitCommand(workingDirectory, tr("Continue Revert"),
tr("You need to commit changes to finish revert.\nCommit now?"),
tr("Commit"), QLatin1String("revert"));
continuePreviousGitCommand(workingDirectory, tr("Continue Cherry-Picking"),
tr("You need to commit changes to finish cherry-picking.\nCommit now?"),
tr("Commit"), QLatin1String("cherry-pick"));
}
}
void GitClient::continuePreviousGitCommand(const QString &workingDirectory,
const QString &msgBoxTitle, QString msgBoxText,
const QString &buttonName, const QString &gitCommand,
bool requireChanges)
{
bool isRebase = gitCommand == QLatin1String("rebase");
bool hasChanges;
if (!requireChanges) {
hasChanges = true;
} else {
hasChanges = gitStatus(workingDirectory, StatusMode(NoUntracked | NoSubmodules))
== GitClient::StatusChanged;
if (!hasChanges)
msgBoxText.prepend(tr("No changes found. "));
QMessageBox msgBox(QMessageBox::Question, msgBoxTitle, msgBoxText,
QMessageBox::NoButton, Core::ICore::mainWindow());
if (hasChanges || isRebase)
msgBox.addButton(hasChanges ? buttonName : tr("Skip"), QMessageBox::AcceptRole);
msgBox.addButton(QMessageBox::Abort);
msgBox.addButton(QMessageBox::Ignore);
switch (msgBox.exec()) {
case QMessageBox::Ignore:
break;
case QMessageBox::Abort:
synchronousAbortCommand(workingDirectory, gitCommand);
break;
default: // Continue/Skip
if (isRebase)
rebase(workingDirectory, QLatin1String(hasChanges ? "--continue" : "--skip"));
else
GitPlugin::instance()->startCommit();
}
}
// 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 = VcsBasePlugin::SshPasswordPrompt
| VcsBasePlugin::SuppressStdErrInLogWindow
| VcsBasePlugin::SuppressFailMessageInLogWindow;
const Utils::SynchronousProcessResponse resp = synchronousGit(QString(), arguments, flags);
QStringList branches;
branches << tr("<Detached HEAD>");
// split "82bfad2f51d34e98b18982211c82220b8db049b<tab>refs/heads/master"
foreach (const QString &line, resp.stdOut.split(QLatin1Char('\n'))) {
if (line.endsWith(QLatin1String("\tHEAD"))) {
QTC_CHECK(headSha.isNull());
headSha = line.left(line.indexOf(QLatin1Char('\t')));
continue;
}
const QString pattern = QLatin1String("\trefs/heads/");
const int pos = line.lastIndexOf(pattern);
bool headFound = false;
if (pos != -1) {
const QString branchName = line.mid(pos + pattern.count());
if (!headFound && line.startsWith(headSha)) {
headFound = true;
}
}
return branches;
}
void GitClient::launchGitK(const QString &workingDirectory, const QString &fileName)
const QFileInfo binaryInfo(gitBinaryPath());
QDir foundBinDir(binaryInfo.dir());
const bool foundBinDirIsCmdDir = foundBinDir.dirName() == QLatin1String("cmd");
QProcessEnvironment env = processEnvironment();
if (tryLauchingGitK(env, workingDirectory, fileName, foundBinDir.path(), foundBinDirIsCmdDir))
return;
if (!foundBinDirIsCmdDir)
return;
foundBinDir.cdUp();
tryLauchingGitK(env, workingDirectory, fileName, foundBinDir.path() + QLatin1String("/bin"), false);
void GitClient::launchRepositoryBrowser(const QString &workingDirectory)
{
const QString repBrowserBinary = settings()->stringValue(GitSettings::repositoryBrowserCmd);
if (!repBrowserBinary.isEmpty())
QProcess::startDetached(repBrowserBinary, QStringList(workingDirectory), workingDirectory);
}
bool GitClient::tryLauchingGitK(const QProcessEnvironment &env,
const QString &workingDirectory,
const QString &gitBinDirectory,
bool silent)
{
QString binary = gitBinDirectory + QLatin1String("/gitk");
QStringList arguments;
if (Utils::HostOsInfo::isWindowsHost()) {
// If git/bin is in path, use 'wish' shell to run. Otherwise (git/cmd), directly run gitk
QString wish = gitBinDirectory + QLatin1String("/wish");
if (QFileInfo(wish + QLatin1String(".exe")).exists()) {
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;
bool GitClient::launchGitGui(const QString &workingDirectory) {
bool success;
QString gitBinary = gitBinaryPath(&success);
if (success)
success = QProcess::startDetached(gitBinary, QStringList(QLatin1String("gui")),
workingDirectory);
if (!success)
outputWindow()->appendError(tr("Cannot launch git gui"));
return success;
}
QString GitClient::gitBinaryPath(bool *ok, QString *errorMessage) const
{
return settings()->gitBinaryPath(ok, errorMessage);
}
bool GitClient::getCommitData(const QString &workingDirectory,
QString *commitTemplate,
// 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;
if (commitData.commitType == FixupCommit) {
QStringList arguments;
arguments << QLatin1String("HEAD") << QLatin1String("--not")
<< QLatin1String("--remotes") << QLatin1String("-n1");
synchronousLog(repoDirectory, arguments, &output, errorMessage);
if (output.isEmpty()) {
*errorMessage = msgNoCommits(false);
return false;
}
}
const StatusResult status = gitStatus(repoDirectory, ShowAll, &output, errorMessage);
case StatusChanged:
break;
case StatusUnchanged:
if (commitData.commitType == AmendCommit) // amend might be run just for the commit message
*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);
}
if (commitData.files.isEmpty() && commitData.commitType != AmendCommit) {
*errorMessage = msgNoChangedFiles();
return false;
}

Friedemann Kleint
committed
}
commitData.commitEncoding = readConfigValue(workingDirectory, QLatin1String("i18n.commitEncoding"));
// Get the commit template or the last commit message
// 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"));
break;
}
case SimpleCommit: {
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)) {
Utils::FileName templateName = Utils::FileName::fromUserInput(
readConfigValue(workingDirectory, QLatin1String("commit.template")));
templateFilename = templateName.toString();
}
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(" -> ");
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;
args << QLatin1String("commit");
if (commitType == FixupCommit) {
args << QLatin1String("--fixup") << amendSHA1;
} else {
args << QLatin1String("-F") << QDir::toNativeSeparators(messageFile);
if (commitType == AmendCommit)
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);
const QString stdErr = commandOutputFromLocal8Bit(errorText);
if (rc) {
outputWindow()->append(msgCommitted(amendSHA1, commitCount));
outputWindow()->appendError(stdErr);
} else {
outputWindow()->appendError(tr("Cannot commit %n file(s): %1\n", 0, commitCount).arg(stdErr));
}
/* 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::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;
}
}
void GitClient::fetch(const QString &workingDirectory, const QString &remote)
{
QStringList arguments(QLatin1String("fetch"));
arguments << (remote.isEmpty() ? QLatin1String("--all") : remote);
VcsBase::Command *command = executeGit(workingDirectory, arguments, 0, true);
command->setCookie(workingDirectory);
connect(command, SIGNAL(success(QVariant)), this, SLOT(fetchFinished(QVariant)));
bool GitClient::executeAndHandleConflicts(const QString &workingDirectory,
const QStringList &arguments,
const QString &abortCommand)
// Disable UNIX terminals to suppress SSH prompting.
const unsigned flags = VcsBasePlugin::SshPasswordPrompt
| VcsBasePlugin::ShowStdOutInLogWindow
| VcsBasePlugin::ExpectRepoChanges;
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;
conflictHandler.readStdOutString(resp.stdOut);
conflictHandler.readStdErr(resp.stdErr);