diff options
Diffstat (limited to 'src/plugins/diffeditor/diffutils.cpp')
-rw-r--r-- | src/plugins/diffeditor/diffutils.cpp | 657 |
1 files changed, 375 insertions, 282 deletions
diff --git a/src/plugins/diffeditor/diffutils.cpp b/src/plugins/diffeditor/diffutils.cpp index 5b8ca9d674..aa4ee87027 100644 --- a/src/plugins/diffeditor/diffutils.cpp +++ b/src/plugins/diffeditor/diffutils.cpp @@ -27,6 +27,7 @@ #include "differ.h" #include "texteditor/fontsettings.h" +#include "utils/asconst.h" #include <QFutureInterfaceBase> #include <QRegularExpression> @@ -695,58 +696,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"); + QList<ChunkData> chunkDataList; + int position = -1; - bool readOk = false; + QVector<int> startingPositions; // store starting positions of @@ + if (patch.startsWith(QStringLiteral("@@ -"))) + startingPositions.append(position + 1); - QList<ChunkData> chunkDataList; + while ((position = patch.indexOf(QStringLiteral("\n@@ -"), position + 1)) >= 0) + startingPositions.append(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()); - - if (endOfLastChunk > 0) { - QStringRef lines = patch.mid(endOfLastChunk); - chunkDataList.last().rows = readLines(lines, - true, - lastChunkAtTheEndOfFile, - &readOk); - } + 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 +952,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'); -} - -static FileData readGitHeaderAndChunks(QStringRef headerAndChunks, - const QString &fileName, - bool *ok) -{ - FileData fileData; - fileData.leftFileInfo.fileName = fileName; - fileData.rightFileInfo.fileName = fileName; - - QStringRef patch = headerAndChunks; - bool readOk = false; + bool hasNewLine; + *remainingPatch = patch; - const QString devNull(QLatin1String("/dev/null")); - - // 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"); - - // 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"); - - const QRegularExpression modeChangeRegExp("^old mode \\d+\\nnew mode \\d+\\n"); - - // index cap1..cap2(optionally: octal) - const QRegularExpression indexRegExp("^index (\\w+)\\.{2}(\\w+)(?: \\d+)?(\\n|$)"); - - QString leftFileName = QLatin1String("a/") + fileName; - QString rightFileName = QLatin1String("b/") + fileName; - - 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()); - } else { - const QRegularExpressionMatch modeChangeMatch = modeChangeRegExp.match(patch); - if (modeChangeMatch.hasMatch() && modeChangeMatch.capturedStart() == 0) { - patch = patch.mid(modeChangeMatch.capturedEnd()); - } - } + 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; } - const QRegularExpressionMatch indexMatch = indexRegExp.match(patch); - if (indexMatch.hasMatch() && indexMatch.capturedStart() == 0) { - fileData.leftFileInfo.typeInfo = indexMatch.captured(1); - fileData.rightFileInfo.typeInfo = indexMatch.captured(2); + 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; + } - patch = patch.mid(indexMatch.capturedEnd()); + if (remainingPatch->isEmpty() && (fileData->fileOperation == FileData::NewFile + || fileData->fileOperation == FileData::DeleteFile)) { + // OK in case of empty file + return true; } - 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'); + 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; - // 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); + const QString binaryLine = QLatin1String("Binary files ") + + leftFileName + QLatin1String(" and ") + + rightFileName + QLatin1String(" differ"); - const QString rightStart = QString::fromLatin1("+++ ") + rightFileName; - QChar rightFollow = patch.count() > rightStart.count() ? patch.at(rightStart.count()) : QLatin1Char('\n'); + if (*remainingPatch == binaryLine) { + fileData->binaryFiles = true; + *remainingPatch = QStringRef(); + return true; + } - // followed by rightFileRegExp - if (patch.startsWith(rightStart) && fileNameEnd(rightFollow)) { - patch = patch.mid(patch.indexOf(QLatin1Char('\n'), rightStart.count()) + 1); + 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 - fileData.chunks = readChunks(patch, - &fileData.lastChunkAtTheEndOfFile, - &readOk); - } - } else if (patch == binaryLine) { - readOk = true; - fileData.binaryFiles = true; - } + if (!minuses.startsWith(leftStart)) + return false; - if (ok) - *ok = readOk; + 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 (!readOk) - return FileData(); + if (!pluses.startsWith(rightStart)) + return false; - return fileData; + *remainingPatch = afterPluses; + return true; } -static FileData readCopyRenameChunks(QStringRef copyRenameChunks, - FileData::FileOperation fileOperation, - const QString &leftFileName, - const QString &rightFileName, - bool *ok) +static bool extractCommonFileName(QStringRef fileNames, QStringRef *fileName) { - FileData fileData; - fileData.fileOperation = fileOperation; - fileData.leftFileInfo.fileName = leftFileName; - fileData.rightFileInfo.fileName = rightFileName; - - QStringRef patch = copyRenameChunks; - bool readOk = false; + // we should have 1 space between filenames + if (fileNames.size() % 2 == 0) + return false; - // index cap1..cap2(optionally: octal) - const QRegularExpression indexRegExp("^index (\\w+)\\.{2}(\\w+)(?: \\d+)?(\\n|$)"); + if (!fileNames.startsWith(QStringLiteral("a/"))) + return false; - 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); + // drop the space in between + const int fileNameSize = fileNames.size() / 2; + if (!fileNames.mid(fileNameSize).startsWith(" b/")) + return false; - patch = patch.mid(indexMatch.capturedEnd()); + // drop "a/" + const QStringRef leftFileName = fileNames.mid(2, fileNameSize - 2); - const QString leftStart = QString::fromLatin1("--- a/") + leftFileName; - QChar leftFollow = patch.count() > leftStart.count() ? patch.at(leftStart.count()) : QLatin1Char('\n'); + // drop the first filename + " b/" + const QStringRef rightFileName = fileNames.mid(fileNameSize + 3, fileNameSize - 2); - // followed by leftFileRegExp - if (patch.startsWith(leftStart) && fileNameEnd(leftFollow)) { - patch = patch.mid(patch.indexOf(QLatin1Char('\n'), leftStart.count()) + 1); + if (leftFileName != rightFileName) + return false; - // followed by rightFileRegExp - const QString rightStart = QString::fromLatin1("+++ b/") + rightFileName; - QChar rightFollow = patch.count() > rightStart.count() ? patch.at(rightStart.count()) : QLatin1Char('\n'); + *fileName = leftFileName; + return true; +} - // followed by rightFileRegExp - if (patch.startsWith(rightStart) && fileNameEnd(rightFollow)) { - patch = patch.mid(patch.indexOf(QLatin1Char('\n'), rightStart.count()) + 1); +static bool detectFileData(QStringRef patch, + FileData *fileData, + QStringRef *remainingPatch) { + bool hasNewLine; + + 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 { + *remainingPatch = afterDiffGit; + } - fileData.chunks = readChunks(patch, - &fileData.lastChunkAtTheEndOfFile, - &readOk); - } - } - } else if (copyRenameChunks.isEmpty()) { - readOk = true; + } else { + // copy / rename + + QStringRef afterSimilarity; + // (dis)similarity index [0-100]% + readLine(afterDiffGit, &afterSimilarity, &hasNewLine); + if (!hasNewLine) + return false; // we need to have at least one more line + + // TODO: validate similarity line + + 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 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; } - } - 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; + 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; + } + 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; + class PatchInfo { + public: + QStringRef patch; + FileData fileData; + }; - QList<FileData> fileDataList; + const QChar newLine('\n'); + bool readOk = true; - 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); - } - } + QVector<PatchInfo> patches; + const int count = startingPositions.count(); + for (int i = 0; i < count; i++) { + if (jobController && jobController->isCanceled()) + return QList<FileData>(); - if (simpleGitMatch.hasMatch() && similarityMatch.hasMatch()) { - const int simpleGitPos = simpleGitMatch.capturedStart(); - const int similarityPos = similarityMatch.capturedStart(); - pos = qMin(simpleGitPos, similarityPos); - simpleGitMatched = (pos == simpleGitPos); - return; - } + 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()); - if (simpleGitMatch.hasMatch()) { - pos = simpleGitMatch.capturedStart(); - simpleGitMatched = true; - return; - } + // extract the patch for just one file + const QStringRef fileDiff = patch.mid(diffStart, diffEnd - diffStart); - if (similarityMatch.hasMatch()) { - pos = similarityMatch.capturedStart(); - simpleGitMatched = false; - return; - } + FileData fileData; + QStringRef remainingFileDiff; + readOk = detectFileData(fileDiff, &fileData, &remainingFileDiff); - pos = -1; - simpleGitMatched = true; - }; + if (!readOk) + break; - // Set both pos and simpleGitMatched according to the first match: - calculateGitMatchAndPosition(); + patches.append(PatchInfo { remainingFileDiff, fileData }); + } - if (pos >= 0) { // git style patch - readOk = true; - int endOfLastHeader = 0; - QString lastLeftFileName; - QString lastRightFileName; - FileData::FileOperation lastOperation = FileData::ChangeFile; - - auto collectFileData = [&]() { - if (endOfLastHeader > 0 && readOk) { - const int end = pos < 0 ? patch.count() : pos; - QStringRef headerAndChunks = patch.mid(endOfLastHeader, - qMax(end - endOfLastHeader - 1, 0)); - - FileData fileData; - if (lastOperation == FileData::ChangeFile) { - fileData = readGitHeaderAndChunks(headerAndChunks, - lastLeftFileName, - &readOk); - } else { - fileData = readCopyRenameChunks(headerAndChunks, - lastOperation, - lastLeftFileName, - lastRightFileName, - &readOk); - } - if (readOk) - fileDataList.append(fileData); - } - }; + if (!readOk) { + if (ok) + *ok = readOk; + return QList<FileData>(); + } - do { - if (jobController && jobController->isCanceled()) - return QList<FileData>(); + if (jobController) + jobController->setProgressRange(0, patches.count()); - collectFileData(); - if (!readOk) - break; + 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++); + } - 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; + FileData fileData = patchInfo.fileData; + if (!patchInfo.patch.isEmpty() || fileData.fileOperation == FileData::ChangeFile) + fileData.chunks = readChunks(patchInfo.patch, &fileData.lastChunkAtTheEndOfFile, &readOk); + else + readOk = true; - // give both pos and simpleGitMatched a new value for the next match - calculateGitMatchAndPosition(); - } while (pos != -1); + if (!readOk) + break; - if (readOk) - collectFileData(); + fileDataList.append(fileData); } if (ok) |