summaryrefslogtreecommitdiff
path: root/src/plugins/diffeditor/diffutils.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/diffeditor/diffutils.cpp')
-rw-r--r--src/plugins/diffeditor/diffutils.cpp657
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)