Merge remote-tracking branch 'origin/4.4'

Conflicts:
	src/tools/clangbackend/ipcsource/clangiasyncjob.cpp
	src/tools/clangbackend/ipcsource/clangjobrequest.cpp
	src/tools/clangbackend/ipcsource/clangjobrequest.h

Change-Id: Ib8602530663813ade418f995dfd2a736908cfe75
This commit is contained in:
Eike Ziller
2017-08-15 10:07:51 +02:00
209 changed files with 5006 additions and 2423 deletions

View File

@@ -856,7 +856,7 @@ void DiffEditor::Internal::DiffEditorPlugin::testMakePatch()
QFETCH(bool, lastChunk);
QFETCH(QString, patchText);
QString result = DiffUtils::makePatch(sourceChunk, leftFileName, rightFileName, lastChunk);
const QString result = DiffUtils::makePatch(sourceChunk, leftFileName, rightFileName, lastChunk);
QCOMPARE(result, patchText);

View File

@@ -27,6 +27,7 @@
#include "differ.h"
#include "texteditor/fontsettings.h"
#include "utils/asconst.h"
#include <QFutureInterfaceBase>
#include <QRegularExpression>
@@ -485,6 +486,34 @@ QString DiffUtils::makePatch(const ChunkData &chunkData,
return diffText;
}
static QString leftFileName(const FileData &fileData, unsigned formatFlags)
{
QString diffText;
QTextStream str(&diffText);
if (fileData.fileOperation == FileData::NewFile) {
str << "/dev/null";
} else {
if (formatFlags & DiffUtils::AddLevel)
str << "a/";
str << fileData.leftFileInfo.fileName;
}
return diffText;
}
static QString rightFileName(const FileData &fileData, unsigned formatFlags)
{
QString diffText;
QTextStream str(&diffText);
if (fileData.fileOperation == FileData::DeleteFile) {
str << "/dev/null";
} else {
if (formatFlags & DiffUtils::AddLevel)
str << "b/";
str << fileData.rightFileInfo.fileName;
}
return diffText;
}
QString DiffUtils::makePatch(const QList<FileData> &fileDataList, unsigned formatFlags)
{
QString diffText;
@@ -496,26 +525,36 @@ QString DiffUtils::makePatch(const QList<FileData> &fileDataList, unsigned forma
str << "diff --git a/" << fileData.leftFileInfo.fileName
<< " b/" << fileData.rightFileInfo.fileName << '\n';
}
if (fileData.fileOperation == FileData::NewFile
|| fileData.fileOperation == FileData::DeleteFile) { // git only?
if (fileData.fileOperation == FileData::NewFile)
str << "new";
else
str << "deleted";
str << " file mode 100644\n";
}
str << "index " << fileData.leftFileInfo.typeInfo << ".." << fileData.rightFileInfo.typeInfo;
if (fileData.fileOperation == FileData::ChangeFile)
str << " 100644";
str << "\n";
if (fileData.binaryFiles) {
str << "Binary files ";
if (formatFlags & AddLevel)
str << "a/";
str << fileData.leftFileInfo.fileName << " and ";
if (formatFlags & AddLevel)
str << "b/";
str << fileData.rightFileInfo.fileName << " differ\n";
str << leftFileName(fileData, formatFlags);
str << " and ";
str << rightFileName(fileData, formatFlags);
str << " differ\n";
} else {
str << "--- ";
if (formatFlags & AddLevel)
str << "a/";
str << fileData.leftFileInfo.fileName << "\n+++ ";
if (formatFlags & AddLevel)
str << "b/";
str << fileData.rightFileInfo.fileName << '\n';
for (int j = 0; j < fileData.chunks.count(); j++) {
str << makePatch(fileData.chunks.at(j),
(j == fileData.chunks.count() - 1)
&& fileData.lastChunkAtTheEndOfFile);
if (!fileData.chunks.isEmpty()) {
str << "--- ";
str << leftFileName(fileData, formatFlags) << "\n";
str << "+++ ";
str << rightFileName(fileData, formatFlags) << "\n";
for (int j = 0; j < fileData.chunks.count(); j++) {
str << makePatch(fileData.chunks.at(j),
(j == fileData.chunks.count() - 1)
&& fileData.lastChunkAtTheEndOfFile);
}
}
}
}
@@ -695,58 +734,128 @@ static QList<RowData> readLines(QStringRef patch,
outputRightDiffList).rows;
}
static QStringRef readLine(QStringRef text, QStringRef *remainingText, bool *hasNewLine)
{
const QChar newLine('\n');
const int indexOfFirstNewLine = text.indexOf(newLine);
if (indexOfFirstNewLine < 0) {
if (remainingText)
*remainingText = QStringRef();
if (hasNewLine)
*hasNewLine = false;
return text;
}
if (hasNewLine)
*hasNewLine = true;
if (remainingText)
*remainingText = text.mid(indexOfFirstNewLine + 1);
return text.left(indexOfFirstNewLine);
}
static bool detectChunkData(QStringRef chunkDiff,
ChunkData *chunkData,
QStringRef *remainingPatch)
{
bool hasNewLine;
const QStringRef chunkLine = readLine(chunkDiff, remainingPatch, &hasNewLine);
const QLatin1String leftPosMarker("@@ -");
const QLatin1String rightPosMarker(" +");
const QLatin1String optionalHintMarker(" @@");
const int leftPosIndex = chunkLine.indexOf(leftPosMarker);
if (leftPosIndex != 0)
return false;
const int rightPosIndex = chunkLine.indexOf(rightPosMarker, leftPosIndex + leftPosMarker.size());
if (rightPosIndex < 0)
return false;
const int optionalHintIndex = chunkLine.indexOf(optionalHintMarker, rightPosIndex + rightPosMarker.size());
if (optionalHintIndex < 0)
return false;
const int leftPosStart = leftPosIndex + leftPosMarker.size();
const int leftPosLength = rightPosIndex - leftPosStart;
QStringRef leftPos = chunkLine.mid(leftPosStart, leftPosLength);
const int rightPosStart = rightPosIndex + rightPosMarker.size();
const int rightPosLength = optionalHintIndex - rightPosStart;
QStringRef rightPos = chunkLine.mid(rightPosStart, rightPosLength);
const int optionalHintStart = optionalHintIndex + optionalHintMarker.size();
const int optionalHintLength = chunkLine.size() - optionalHintStart;
const QStringRef optionalHint = chunkLine.mid(optionalHintStart, optionalHintLength);
const QChar comma(',');
bool ok;
const int leftCommaIndex = leftPos.indexOf(comma);
if (leftCommaIndex >= 0)
leftPos = leftPos.left(leftCommaIndex);
const int leftLineNumber = leftPos.toString().toInt(&ok);
if (!ok)
return false;
const int rightCommaIndex = rightPos.indexOf(comma);
if (rightCommaIndex >= 0)
rightPos = rightPos.left(rightCommaIndex);
const int rightLineNumber = rightPos.toString().toInt(&ok);
if (!ok)
return false;
chunkData->leftStartingLineNumber = leftLineNumber - 1;
chunkData->rightStartingLineNumber = rightLineNumber - 1;
chunkData->contextInfo = optionalHint.toString();
return true;
}
static QList<ChunkData> readChunks(QStringRef patch,
bool *lastChunkAtTheEndOfFile,
bool *ok)
{
const QRegularExpression chunkRegExp(
// beginning of the line
"(?:\\n|^)"
// @@ -leftPos[,leftCount] +rightPos[,rightCount] @@
"@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@"
// optional hint (e.g. function name)
"(\\ +[^\\n]*)?"
// end of line (need to be followed by text line)
"\\n");
bool readOk = false;
QList<ChunkData> chunkDataList;
int position = -1;
QRegularExpressionMatch match = chunkRegExp.match(patch);
if (match.hasMatch() && match.capturedStart() == 0) {
int endOfLastChunk = 0;
do {
const int pos = match.capturedStart();
const int leftStartingPos = match.capturedRef(1).toInt();
const int rightStartingPos = match.capturedRef(2).toInt();
const QString contextInfo = match.captured(3);
if (endOfLastChunk > 0) {
QStringRef lines = patch.mid(endOfLastChunk,
pos - endOfLastChunk);
chunkDataList.last().rows = readLines(lines,
false,
lastChunkAtTheEndOfFile,
&readOk);
if (!readOk)
break;
}
endOfLastChunk = match.capturedEnd();
ChunkData chunkData;
chunkData.leftStartingLineNumber = leftStartingPos - 1;
chunkData.rightStartingLineNumber = rightStartingPos - 1;
chunkData.contextInfo = contextInfo;
chunkDataList.append(chunkData);
match = chunkRegExp.match(patch, endOfLastChunk);
} while (match.hasMatch());
QVector<int> startingPositions; // store starting positions of @@
if (patch.startsWith(QStringLiteral("@@ -")))
startingPositions.append(position + 1);
if (endOfLastChunk > 0) {
QStringRef lines = patch.mid(endOfLastChunk);
chunkDataList.last().rows = readLines(lines,
true,
lastChunkAtTheEndOfFile,
&readOk);
}
while ((position = patch.indexOf(QStringLiteral("\n@@ -"), position + 1)) >= 0)
startingPositions.append(position + 1);
const QChar newLine('\n');
bool readOk = true;
const int count = startingPositions.count();
for (int i = 0; i < count; i++) {
const int chunkStart = startingPositions.at(i);
const int chunkEnd = (i < count - 1)
// drop the newline before the next chunk start
? startingPositions.at(i + 1) - 1
// drop the possible newline by the end of patch
: (patch.at(patch.count() - 1) == newLine ? patch.count() - 1 : patch.count());
// extract just one chunk
const QStringRef chunkDiff = patch.mid(chunkStart, chunkEnd - chunkStart);
ChunkData chunkData;
QStringRef lines;
readOk = detectChunkData(chunkDiff, &chunkData, &lines);
if (!readOk)
break;
chunkData.rows = readLines(lines, i == (startingPositions.size() - 1),
lastChunkAtTheEndOfFile, &readOk);
if (!readOk)
break;
chunkDataList.append(chunkData);
}
if (ok)
@@ -881,293 +990,315 @@ static QList<FileData> readDiffPatch(QStringRef patch,
return fileDataList;
}
static bool fileNameEnd(const QChar &c)
// The git diff patch format (ChangeFile, NewFile, DeleteFile)
// 0. <some text lines to skip, e.g. show description>\n
// 1. diff --git a/[fileName] b/[fileName]\n
// 2a. new file mode [fileModeNumber]\n
// 2b. deleted file mode [fileModeNumber]\n
// 2c. old mode [oldFileModeNumber]\n
// new mode [newFileModeNumber]\n
// 2d. <Nothing, only in case of ChangeFile>
// 3a. index [leftIndexSha]..[rightIndexSha] <optionally: octalNumber>
// 3b. <Nothing, only in case of ChangeFile, "Dirty submodule" case>
// 4a. <Nothing more, only possible in case of NewFile or DeleteFile> ???
// 4b. \nBinary files [leftFileNameOrDevNull] and [rightFileNameOrDevNull] differ
// 4c. --- [leftFileNameOrDevNull]\n
// +++ [rightFileNameOrDevNull]\n
// <Chunks>
// The git diff patch format (CopyFile, RenameFile)
// 0. [some text lines to skip, e.g. show description]\n
// 1. diff --git a/[leftFileName] b/[rightFileName]\n
// 2. [dis]similarity index [0-100]%\n
// [copy / rename] from [leftFileName]\n
// [copy / rename] to [rightFileName]
// 3a. <Nothing more, only when similarity index was 100%>
// 3b. index [leftIndexSha]..[rightIndexSha] <optionally: octalNumber>
// 4. --- [leftFileNameOrDevNull]\n
// +++ [rightFileNameOrDevNull]\n
// <Chunks>
static bool detectIndexAndBinary(QStringRef patch,
FileData *fileData,
QStringRef *remainingPatch)
{
return c == QLatin1Char('\n') || c == QLatin1Char('\t');
bool hasNewLine;
*remainingPatch = patch;
if (remainingPatch->isEmpty() && (fileData->fileOperation == FileData::CopyFile
|| fileData->fileOperation == FileData::RenameFile)) {
// in case of 100% similarity we don't have more lines in the patch
return true;
}
QStringRef afterNextLine;
// index [leftIndexSha]..[rightIndexSha] <optionally: octalNumber>
const QStringRef nextLine = readLine(patch, &afterNextLine, &hasNewLine);
const QLatin1String indexHeader("index ");
if (nextLine.startsWith(indexHeader)) {
const QStringRef indices = nextLine.mid(indexHeader.size());
const int dotsPosition = indices.indexOf(QStringLiteral(".."));
if (dotsPosition < 0)
return false;
fileData->leftFileInfo.typeInfo = indices.left(dotsPosition).toString();
// if there is no space we take the remaining string
const int spacePosition = indices.indexOf(QChar::Space, dotsPosition + 2);
const int length = spacePosition < 0 ? -1 : spacePosition - dotsPosition - 2;
fileData->rightFileInfo.typeInfo = indices.mid(dotsPosition + 2, length).toString();
*remainingPatch = afterNextLine;
} else if (fileData->fileOperation != FileData::ChangeFile) {
// no index only in case of ChangeFile,
// the dirty submodule diff case, see "Dirty Submodule" test:
return false;
}
if (remainingPatch->isEmpty() && (fileData->fileOperation == FileData::NewFile
|| fileData->fileOperation == FileData::DeleteFile)) {
// OK in case of empty file
return true;
}
const QString devNull("/dev/null");
const QString leftFileName = fileData->fileOperation == FileData::NewFile
? devNull : QLatin1String("a/") + fileData->leftFileInfo.fileName;
const QString rightFileName = fileData->fileOperation == FileData::DeleteFile
? devNull : QLatin1String("b/") + fileData->rightFileInfo.fileName;
const QString binaryLine = QLatin1String("Binary files ")
+ leftFileName + QLatin1String(" and ")
+ rightFileName + QLatin1String(" differ");
if (*remainingPatch == binaryLine) {
fileData->binaryFiles = true;
*remainingPatch = QStringRef();
return true;
}
const QString leftStart = QLatin1String("--- ") + leftFileName;
QStringRef afterMinuses;
// --- leftFileName
const QStringRef minuses = readLine(*remainingPatch, &afterMinuses, &hasNewLine);
if (!hasNewLine)
return false; // we need to have at least one more line
if (!minuses.startsWith(leftStart))
return false;
const QString rightStart = QLatin1String("+++ ") + rightFileName;
QStringRef afterPluses;
// +++ rightFileName
const QStringRef pluses = readLine(afterMinuses, &afterPluses, &hasNewLine);
if (!hasNewLine)
return false; // we need to have at least one more line
if (!pluses.startsWith(rightStart))
return false;
*remainingPatch = afterPluses;
return true;
}
static FileData readGitHeaderAndChunks(QStringRef headerAndChunks,
const QString &fileName,
bool *ok)
static bool extractCommonFileName(QStringRef fileNames, QStringRef *fileName)
{
FileData fileData;
fileData.leftFileInfo.fileName = fileName;
fileData.rightFileInfo.fileName = fileName;
// we should have 1 space between filenames
if (fileNames.size() % 2 == 0)
return false;
QStringRef patch = headerAndChunks;
bool readOk = false;
if (!fileNames.startsWith(QStringLiteral("a/")))
return false;
const QString devNull(QLatin1String("/dev/null"));
// drop the space in between
const int fileNameSize = fileNames.size() / 2;
if (!fileNames.mid(fileNameSize).startsWith(" b/"))
return false;
// will be followed by: index 0000000..shasha, file "a" replaced by "/dev/null", @@ -0,0 +m,n @@
// new file mode octal
const QRegularExpression newFileMode("^new file mode \\d+\\n");
// drop "a/"
const QStringRef leftFileName = fileNames.mid(2, fileNameSize - 2);
// will be followed by: index shasha..0000000, file "b" replaced by "/dev/null", @@ -m,n +0,0 @@
// deleted file mode octal
const QRegularExpression deletedFileMode("^deleted file mode \\d+\\n");
// drop the first filename + " b/"
const QStringRef rightFileName = fileNames.mid(fileNameSize + 3, fileNameSize - 2);
const QRegularExpression modeChangeRegExp("^old mode \\d+\\nnew mode \\d+\\n");
if (leftFileName != rightFileName)
return false;
// index cap1..cap2(optionally: octal)
const QRegularExpression indexRegExp("^index (\\w+)\\.{2}(\\w+)(?: \\d+)?(\\n|$)");
*fileName = leftFileName;
return true;
}
QString leftFileName = QLatin1String("a/") + fileName;
QString rightFileName = QLatin1String("b/") + fileName;
static bool detectFileData(QStringRef patch,
FileData *fileData,
QStringRef *remainingPatch) {
bool hasNewLine;
const QRegularExpressionMatch newFileMatch = newFileMode.match(patch);
if (newFileMatch.hasMatch() && newFileMatch.capturedStart() == 0) {
fileData.fileOperation = FileData::NewFile;
leftFileName = devNull;
patch = patch.mid(newFileMatch.capturedEnd());
} else {
const QRegularExpressionMatch deletedFileMatch = deletedFileMode.match(patch);
if (deletedFileMatch.hasMatch() && deletedFileMatch.capturedStart() == 0) {
fileData.fileOperation = FileData::DeleteFile;
rightFileName = devNull;
patch = patch.mid(deletedFileMatch.capturedEnd());
QStringRef afterDiffGit;
// diff --git a/leftFileName b/rightFileName
const QStringRef diffGit = readLine(patch, &afterDiffGit, &hasNewLine);
if (!hasNewLine)
return false; // we need to have at least one more line
const QLatin1String gitHeader("diff --git ");
const QStringRef fileNames = diffGit.mid(gitHeader.size());
QStringRef commonFileName;
if (extractCommonFileName(fileNames, &commonFileName)) {
// change / new / delete
fileData->fileOperation = FileData::ChangeFile;
fileData->leftFileInfo.fileName = fileData->rightFileInfo.fileName = commonFileName.toString();
QStringRef afterSecondLine;
const QStringRef secondLine = readLine(afterDiffGit, &afterSecondLine, &hasNewLine);
if (!hasNewLine)
return false; // we need to have at least one more line
if (secondLine.startsWith(QStringLiteral("new file mode "))) {
fileData->fileOperation = FileData::NewFile;
*remainingPatch = afterSecondLine;
} else if (secondLine.startsWith(QStringLiteral("deleted file mode "))) {
fileData->fileOperation = FileData::DeleteFile;
*remainingPatch = afterSecondLine;
} else if (secondLine.startsWith(QStringLiteral("old mode "))) {
QStringRef afterThirdLine;
// new mode
readLine(afterSecondLine, &afterThirdLine, &hasNewLine);
if (!hasNewLine)
return false; // we need to have at least one more line
// TODO: validate new mode line
*remainingPatch = afterThirdLine;
} else {
const QRegularExpressionMatch modeChangeMatch = modeChangeRegExp.match(patch);
if (modeChangeMatch.hasMatch() && modeChangeMatch.capturedStart() == 0) {
patch = patch.mid(modeChangeMatch.capturedEnd());
}
*remainingPatch = afterDiffGit;
}
}
const QRegularExpressionMatch indexMatch = indexRegExp.match(patch);
if (indexMatch.hasMatch() && indexMatch.capturedStart() == 0) {
fileData.leftFileInfo.typeInfo = indexMatch.captured(1);
fileData.rightFileInfo.typeInfo = indexMatch.captured(2);
} else {
// copy / rename
patch = patch.mid(indexMatch.capturedEnd());
}
QStringRef afterSimilarity;
// (dis)similarity index [0-100]%
readLine(afterDiffGit, &afterSimilarity, &hasNewLine);
if (!hasNewLine)
return false; // we need to have at least one more line
const QString binaryLine = QString::fromLatin1("Binary files ") + leftFileName
+ QLatin1String(" and ") + rightFileName + QLatin1String(" differ");
const QString leftStart = QString::fromLatin1("--- ") + leftFileName;
QChar leftFollow = patch.count() > leftStart.count() ? patch.at(leftStart.count()) : QLatin1Char('\n');
// TODO: validate similarity line
// empty or followed either by leftFileRegExp or by binaryRegExp
if (patch.isEmpty() && (fileData.fileOperation == FileData::NewFile
|| fileData.fileOperation == FileData::DeleteFile)) {
readOk = true;
} else if (patch.startsWith(leftStart) && fileNameEnd(leftFollow)) {
patch = patch.mid(patch.indexOf(QLatin1Char('\n'), leftStart.count()) + 1);
QStringRef afterCopyRenameFrom;
// [copy / rename] from leftFileName
const QStringRef copyRenameFrom = readLine(afterSimilarity, &afterCopyRenameFrom, &hasNewLine);
if (!hasNewLine)
return false; // we need to have at least one more line
const QString rightStart = QString::fromLatin1("+++ ") + rightFileName;
QChar rightFollow = patch.count() > rightStart.count() ? patch.at(rightStart.count()) : QLatin1Char('\n');
// followed by rightFileRegExp
if (patch.startsWith(rightStart) && fileNameEnd(rightFollow)) {
patch = patch.mid(patch.indexOf(QLatin1Char('\n'), rightStart.count()) + 1);
fileData.chunks = readChunks(patch,
&fileData.lastChunkAtTheEndOfFile,
&readOk);
const QLatin1String copyFrom("copy from ");
const QLatin1String renameFrom("rename from ");
if (copyRenameFrom.startsWith(copyFrom)) {
fileData->fileOperation = FileData::CopyFile;
fileData->leftFileInfo.fileName = copyRenameFrom.mid(copyFrom.size()).toString();
} else if (copyRenameFrom.startsWith(renameFrom)) {
fileData->fileOperation = FileData::RenameFile;
fileData->leftFileInfo.fileName = copyRenameFrom.mid(renameFrom.size()).toString();
} else {
return false;
}
} else if (patch == binaryLine) {
readOk = true;
fileData.binaryFiles = true;
}
if (ok)
*ok = readOk;
QStringRef afterCopyRenameTo;
// [copy / rename] to rightFileName
const QStringRef copyRenameTo = readLine(afterCopyRenameFrom, &afterCopyRenameTo, &hasNewLine);
if (!readOk)
return FileData();
// if (dis)similarity index is 100% we don't have more lines
return fileData;
}
static FileData readCopyRenameChunks(QStringRef copyRenameChunks,
FileData::FileOperation fileOperation,
const QString &leftFileName,
const QString &rightFileName,
bool *ok)
{
FileData fileData;
fileData.fileOperation = fileOperation;
fileData.leftFileInfo.fileName = leftFileName;
fileData.rightFileInfo.fileName = rightFileName;
QStringRef patch = copyRenameChunks;
bool readOk = false;
// index cap1..cap2(optionally: octal)
const QRegularExpression indexRegExp("^index (\\w+)\\.{2}(\\w+)(?: \\d+)?(\\n|$)");
if (fileOperation == FileData::CopyFile || fileOperation == FileData::RenameFile) {
const QRegularExpressionMatch indexMatch = indexRegExp.match(patch);
if (indexMatch.hasMatch() && indexMatch.capturedStart() == 0) {
fileData.leftFileInfo.typeInfo = indexMatch.captured(1);
fileData.rightFileInfo.typeInfo = indexMatch.captured(2);
patch = patch.mid(indexMatch.capturedEnd());
const QString leftStart = QString::fromLatin1("--- a/") + leftFileName;
QChar leftFollow = patch.count() > leftStart.count() ? patch.at(leftStart.count()) : QLatin1Char('\n');
// followed by leftFileRegExp
if (patch.startsWith(leftStart) && fileNameEnd(leftFollow)) {
patch = patch.mid(patch.indexOf(QLatin1Char('\n'), leftStart.count()) + 1);
// followed by rightFileRegExp
const QString rightStart = QString::fromLatin1("+++ b/") + rightFileName;
QChar rightFollow = patch.count() > rightStart.count() ? patch.at(rightStart.count()) : QLatin1Char('\n');
// followed by rightFileRegExp
if (patch.startsWith(rightStart) && fileNameEnd(rightFollow)) {
patch = patch.mid(patch.indexOf(QLatin1Char('\n'), rightStart.count()) + 1);
fileData.chunks = readChunks(patch,
&fileData.lastChunkAtTheEndOfFile,
&readOk);
}
}
} else if (copyRenameChunks.isEmpty()) {
readOk = true;
const QLatin1String copyTo("copy to ");
const QLatin1String renameTo("rename to ");
if (fileData->fileOperation == FileData::CopyFile && copyRenameTo.startsWith(copyTo)) {
fileData->rightFileInfo.fileName = copyRenameTo.mid(copyTo.size()).toString();
} else if (fileData->fileOperation == FileData::RenameFile && copyRenameTo.startsWith(renameTo)) {
fileData->rightFileInfo.fileName = copyRenameTo.mid(renameTo.size()).toString();
} else {
return false;
}
*remainingPatch = afterCopyRenameTo;
}
if (ok)
*ok = readOk;
if (!readOk)
return FileData();
return fileData;
return detectIndexAndBinary(*remainingPatch, fileData, remainingPatch);
}
static QList<FileData> readGitPatch(QStringRef patch, bool *ok,
QFutureInterfaceBase *jobController)
{
int position = -1;
const QRegularExpression simpleGitRegExp(
"^diff --git a/([^\\n]+) b/\\1\\n" // diff --git a/cap1 b/cap1
, QRegularExpression::MultilineOption);
QVector<int> startingPositions; // store starting positions of git headers
if (patch.startsWith(QStringLiteral("diff --git ")))
startingPositions.append(position + 1);
const QRegularExpression similarityRegExp(
"^diff --git a/([^\\n]+) b/([^\\n]+)\\n" // diff --git a/cap1 b/cap2
"(?:dis)?similarity index \\d{1,3}%\\n" // similarity / dissimilarity index xxx% (100% max)
"(copy|rename) from \\1\\n" // copy / rename from cap1
"\\3 to \\2\\n" // copy / rename (cap3) to cap2
, QRegularExpression::MultilineOption);
while ((position = patch.indexOf(QStringLiteral("\ndiff --git "), position + 1)) >= 0)
startingPositions.append(position + 1);
bool readOk = false;
QList<FileData> fileDataList;
bool simpleGitMatched;
int pos = 0;
QRegularExpressionMatch simpleGitMatch = simpleGitRegExp.match(patch);
QRegularExpressionMatch similarityMatch = similarityRegExp.match(patch);
auto calculateGitMatchAndPosition = [&]() {
if (pos > 0) { // don't advance in the initial call
if (simpleGitMatch.hasMatch() && similarityMatch.hasMatch()) {
const int simpleGitPos = simpleGitMatch.capturedStart();
const int similarityPos = similarityMatch.capturedStart();
if (simpleGitPos <= similarityPos)
simpleGitMatch = simpleGitRegExp.match(patch, simpleGitMatch.capturedEnd() - 1); // advance only simpleGit
else
similarityMatch = similarityRegExp.match(patch, similarityMatch.capturedEnd() - 1); // advance only similarity
} else if (simpleGitMatch.hasMatch()) {
simpleGitMatch = simpleGitRegExp.match(patch, simpleGitMatch.capturedEnd() - 1);
} else if (similarityMatch.hasMatch()) {
similarityMatch = similarityRegExp.match(patch, similarityMatch.capturedEnd() - 1);
}
}
if (simpleGitMatch.hasMatch() && similarityMatch.hasMatch()) {
const int simpleGitPos = simpleGitMatch.capturedStart();
const int similarityPos = similarityMatch.capturedStart();
pos = qMin(simpleGitPos, similarityPos);
simpleGitMatched = (pos == simpleGitPos);
return;
}
if (simpleGitMatch.hasMatch()) {
pos = simpleGitMatch.capturedStart();
simpleGitMatched = true;
return;
}
if (similarityMatch.hasMatch()) {
pos = similarityMatch.capturedStart();
simpleGitMatched = false;
return;
}
pos = -1;
simpleGitMatched = true;
class PatchInfo {
public:
QStringRef patch;
FileData fileData;
};
// Set both pos and simpleGitMatched according to the first match:
calculateGitMatchAndPosition();
const QChar newLine('\n');
bool readOk = true;
if (pos >= 0) { // git style patch
readOk = true;
int endOfLastHeader = 0;
QString lastLeftFileName;
QString lastRightFileName;
FileData::FileOperation lastOperation = FileData::ChangeFile;
QVector<PatchInfo> patches;
const int count = startingPositions.count();
for (int i = 0; i < count; i++) {
if (jobController && jobController->isCanceled())
return QList<FileData>();
auto collectFileData = [&]() {
if (endOfLastHeader > 0 && readOk) {
const int end = pos < 0 ? patch.count() : pos;
QStringRef headerAndChunks = patch.mid(endOfLastHeader,
qMax(end - endOfLastHeader - 1, 0));
const int diffStart = startingPositions.at(i);
const int diffEnd = (i < count - 1)
// drop the newline before the next header start
? startingPositions.at(i + 1) - 1
// drop the possible newline by the end of file
: (patch.at(patch.count() - 1) == newLine ? patch.count() - 1 : patch.count());
FileData fileData;
if (lastOperation == FileData::ChangeFile) {
fileData = readGitHeaderAndChunks(headerAndChunks,
lastLeftFileName,
&readOk);
} else {
fileData = readCopyRenameChunks(headerAndChunks,
lastOperation,
lastLeftFileName,
lastRightFileName,
&readOk);
}
if (readOk)
fileDataList.append(fileData);
}
};
// extract the patch for just one file
const QStringRef fileDiff = patch.mid(diffStart, diffEnd - diffStart);
do {
if (jobController && jobController->isCanceled())
FileData fileData;
QStringRef remainingFileDiff;
readOk = detectFileData(fileDiff, &fileData, &remainingFileDiff);
if (!readOk)
break;
patches.append(PatchInfo { remainingFileDiff, fileData });
}
if (!readOk) {
if (ok)
*ok = readOk;
return QList<FileData>();
}
if (jobController)
jobController->setProgressRange(0, patches.count());
QList<FileData> fileDataList;
readOk = false;
int i = 0;
for (const auto &patchInfo : Utils::asConst(patches)) {
if (jobController) {
if (jobController->isCanceled())
return QList<FileData>();
jobController->setProgressValue(i++);
}
collectFileData();
if (!readOk)
break;
FileData fileData = patchInfo.fileData;
if (!patchInfo.patch.isEmpty() || fileData.fileOperation == FileData::ChangeFile)
fileData.chunks = readChunks(patchInfo.patch, &fileData.lastChunkAtTheEndOfFile, &readOk);
else
readOk = true;
if (simpleGitMatched) {
const QString fileName = simpleGitMatch.captured(1);
pos = simpleGitMatch.capturedEnd();
lastLeftFileName = fileName;
lastRightFileName = fileName;
lastOperation = FileData::ChangeFile;
} else {
lastLeftFileName = similarityMatch.captured(1);
lastRightFileName = similarityMatch.captured(2);
const QString operation = similarityMatch.captured(3);
pos = similarityMatch.capturedEnd();
if (operation == QLatin1String("copy"))
lastOperation = FileData::CopyFile;
else if (operation == QLatin1String("rename"))
lastOperation = FileData::RenameFile;
else
break; // either copy or rename, otherwise broken
}
endOfLastHeader = pos;
if (!readOk)
break;
// give both pos and simpleGitMatched a new value for the next match
calculateGitMatchAndPosition();
} while (pos != -1);
if (readOk)
collectFileData();
fileDataList.append(fileData);
}
if (ok)

View File

@@ -112,6 +112,7 @@ protected:
QColor replacementPenColor(int blockNumber) const override;
QString plainTextFromSelection(const QTextCursor &cursor) const override;
void mouseDoubleClickEvent(QMouseEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
void paintEvent(QPaintEvent *e) override;
void scrollContentsBy(int dx, int dy) override;
@@ -393,6 +394,16 @@ void SideDiffEditorWidget::mouseDoubleClickEvent(QMouseEvent *e)
SelectableTextEditorWidget::mouseDoubleClickEvent(e);
}
void SideDiffEditorWidget::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
jumpToOriginalFile(textCursor());
e->accept();
return;
}
SelectableTextEditorWidget::keyPressEvent(e);
}
void SideDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e)
{
QPointer<QMenu> menu = createStandardContextMenu();

View File

@@ -161,6 +161,16 @@ void UnifiedDiffEditorWidget::mouseDoubleClickEvent(QMouseEvent *e)
SelectableTextEditorWidget::mouseDoubleClickEvent(e);
}
void UnifiedDiffEditorWidget::keyPressEvent(QKeyEvent *e)
{
if (e->key() == Qt::Key_Enter || e->key() == Qt::Key_Return) {
jumpToOriginalFile(textCursor());
e->accept();
return;
}
SelectableTextEditorWidget::keyPressEvent(e);
}
void UnifiedDiffEditorWidget::contextMenuEvent(QContextMenuEvent *e)
{
QPointer<QMenu> menu = createStandardContextMenu();

View File

@@ -74,6 +74,7 @@ signals:
protected:
void mouseDoubleClickEvent(QMouseEvent *e) override;
void keyPressEvent(QKeyEvent *e) override;
void contextMenuEvent(QContextMenuEvent *e) override;
QString lineNumber(int blockNumber) const override;
int lineNumberDigits() const override;