Replace QRegExp with QRegularExpression

Use QStringRef where possible.
Speedup readGitPatch() approximately 2 times.

Change-Id: I7bd09d7ac768331b0600456e48c44cfc72b7001d
Reviewed-by: Orgad Shaneh <orgads@gmail.com>
This commit is contained in:
Jarek Kobus
2017-06-16 13:45:43 +02:00
parent e4a6267036
commit f07fda598e
3 changed files with 265 additions and 181 deletions

View File

@@ -1298,6 +1298,53 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data()
QTest::newRow("Dirty submodule") << patch QTest::newRow("Dirty submodule") << patch
<< fileDataList7; << fileDataList7;
//////////////
patch = _("diff --git a/demos/arthurplugin/arthurplugin.pro b/demos/arthurplugin/arthurplugin.pro\n"
"new file mode 100644\n"
"index 0000000..c5132b4\n"
"--- /dev/null\n"
"+++ b/demos/arthurplugin/arthurplugin.pro\n"
"@@ -0,0 +1 @@\n"
"+XXX\n"
"diff --git a/demos/arthurplugin/bg1.jpg b/demos/arthurplugin/bg1.jpg\n"
"new file mode 100644\n"
"index 0000000..dfc7cee\n"
"Binary files /dev/null and b/demos/arthurplugin/bg1.jpg differ\n"
"diff --git a/demos/arthurplugin/flower.jpg b/demos/arthurplugin/flower.jpg\n"
"new file mode 100644\n"
"index 0000000..f8e022c\n"
"Binary files /dev/null and b/demos/arthurplugin/flower.jpg differ\n"
);
fileData1 = FileData();
fileData1.leftFileInfo = DiffFileInfo(_("demos/arthurplugin/arthurplugin.pro"), _("0000000"));
fileData1.rightFileInfo = DiffFileInfo(_("demos/arthurplugin/arthurplugin.pro"), _("c5132b4"));
fileData1.fileOperation = FileData::NewFile;
chunkData1 = ChunkData();
chunkData1.leftStartingLineNumber = -1;
chunkData1.rightStartingLineNumber = 0;
rows1.clear();
rows1 << RowData(TextLineData::Separator, _("XXX"));
rows1 << RowData(TextLineData::Separator, TextLineData(TextLineData::TextLine));
chunkData1.rows = rows1;
fileData1.chunks << chunkData1;
fileData2 = FileData();
fileData2.leftFileInfo = DiffFileInfo(_("demos/arthurplugin/bg1.jpg"), _("0000000"));
fileData2.rightFileInfo = DiffFileInfo(_("demos/arthurplugin/bg1.jpg"), _("dfc7cee"));
fileData2.fileOperation = FileData::NewFile;
fileData2.binaryFiles = true;
fileData3 = FileData();
fileData3.leftFileInfo = DiffFileInfo(_("demos/arthurplugin/flower.jpg"), _("0000000"));
fileData3.rightFileInfo = DiffFileInfo(_("demos/arthurplugin/flower.jpg"), _("f8e022c"));
fileData3.fileOperation = FileData::NewFile;
fileData3.binaryFiles = true;
QList<FileData> fileDataList8;
fileDataList8 << fileData1 << fileData2 << fileData3;
QTest::newRow("Binary files") << patch
<< fileDataList8;
////////////// //////////////
// Subversion New // Subversion New
@@ -1313,10 +1360,10 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data()
chunkData1.leftStartingLineNumber = -1; chunkData1.leftStartingLineNumber = -1;
chunkData1.rightStartingLineNumber = 124; chunkData1.rightStartingLineNumber = 124;
fileData1.chunks << chunkData1; fileData1.chunks << chunkData1;
QList<FileData> fileDataList8; QList<FileData> fileDataList9;
fileDataList8 << fileData1; fileDataList9 << fileData1;
QTest::newRow("Subversion New") << patch QTest::newRow("Subversion New") << patch
<< fileDataList8; << fileDataList9;
////////////// //////////////
@@ -1333,10 +1380,10 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data()
chunkData1.leftStartingLineNumber = 0; chunkData1.leftStartingLineNumber = 0;
chunkData1.rightStartingLineNumber = -1; chunkData1.rightStartingLineNumber = -1;
fileData1.chunks << chunkData1; fileData1.chunks << chunkData1;
QList<FileData> fileDataList9; QList<FileData> fileDataList10;
fileDataList9 << fileData1; fileDataList10 << fileData1;
QTest::newRow("Subversion Deleted") << patch QTest::newRow("Subversion Deleted") << patch
<< fileDataList9; << fileDataList10;
////////////// //////////////
@@ -1353,10 +1400,10 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data()
chunkData1.leftStartingLineNumber = 119; chunkData1.leftStartingLineNumber = 119;
chunkData1.rightStartingLineNumber = 119; chunkData1.rightStartingLineNumber = 119;
fileData1.chunks << chunkData1; fileData1.chunks << chunkData1;
QList<FileData> fileDataList10; QList<FileData> fileDataList11;
fileDataList10 << fileData1; fileDataList11 << fileData1;
QTest::newRow("Subversion Normal") << patch QTest::newRow("Subversion Normal") << patch
<< fileDataList10; << fileDataList11;
} }
void DiffEditor::Internal::DiffEditorPlugin::testReadPatch() void DiffEditor::Internal::DiffEditorPlugin::testReadPatch()
@@ -1365,10 +1412,10 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch()
QFETCH(QList<FileData>, fileDataList); QFETCH(QList<FileData>, fileDataList);
bool ok; bool ok;
QList<FileData> result = DiffUtils::readPatch(sourcePatch, &ok); const QList<FileData> &result = DiffUtils::readPatch(sourcePatch, &ok);
QVERIFY(ok); QVERIFY(ok);
QCOMPARE(fileDataList.count(), result.count()); QCOMPARE(result.count(), fileDataList.count());
for (int i = 0; i < fileDataList.count(); i++) { for (int i = 0; i < fileDataList.count(); i++) {
const FileData &origFileData = fileDataList.at(i); const FileData &origFileData = fileDataList.at(i);
const FileData &resultFileData = result.at(i); const FileData &resultFileData = result.at(i);

View File

@@ -34,7 +34,7 @@ publication by Neil Fraser: http://neil.fraser.name/writing/diff/
#include "differ.h" #include "differ.h"
#include <QList> #include <QList>
#include <QRegExp> #include <QRegularExpression>
#include <QStringList> #include <QStringList>
#include <QMap> #include <QMap>
#include <QPair> #include <QPair>
@@ -204,9 +204,9 @@ static QList<Diff> cleanupOverlaps(const QList<Diff> &diffList)
static int cleanupSemanticsScore(const QString &text1, const QString &text2) static int cleanupSemanticsScore(const QString &text1, const QString &text2)
{ {
const QRegExp blankLineEnd = QRegExp(QLatin1String("\\n\\r?\\n$")); const QRegularExpression blankLineEnd("\\n\\r?\\n$");
const QRegExp blankLineStart = QRegExp(QLatin1String("^\\r?\\n\\r?\\n")); const QRegularExpression blankLineStart("^\\r?\\n\\r?\\n");
const QRegExp sentenceEnd = QRegExp(QLatin1String("\\. $")); const QRegularExpression sentenceEnd("\\. $");
if (!text1.count() || !text2.count()) // Edges if (!text1.count() || !text2.count()) // Edges
return 6; return 6;
@@ -219,14 +219,14 @@ static int cleanupSemanticsScore(const QString &text1, const QString &text2)
const bool whitespace2 = nonAlphaNumeric2 && char2.isSpace(); const bool whitespace2 = nonAlphaNumeric2 && char2.isSpace();
const bool lineBreak1 = whitespace1 && char1.category() == QChar::Other_Control; const bool lineBreak1 = whitespace1 && char1.category() == QChar::Other_Control;
const bool lineBreak2 = whitespace2 && char2.category() == QChar::Other_Control; const bool lineBreak2 = whitespace2 && char2.category() == QChar::Other_Control;
const bool blankLine1 = lineBreak1 && blankLineEnd.indexIn(text1) != -1; const bool blankLine1 = lineBreak1 && blankLineEnd.match(text1).hasMatch();
const bool blankLine2 = lineBreak2 && blankLineStart.indexIn(text2) != -1; const bool blankLine2 = lineBreak2 && blankLineStart.match(text2).hasMatch();
if (blankLine1 || blankLine2) // Blank lines if (blankLine1 || blankLine2) // Blank lines
return 5; return 5;
if (lineBreak1 || lineBreak2) // Line breaks if (lineBreak1 || lineBreak2) // Line breaks
return 4; return 4;
if (sentenceEnd.indexIn(text1) != -1) // End of sentence if (sentenceEnd.match(text1).hasMatch()) // End of sentence
return 3; return 3;
if (whitespace1 || whitespace2) // Whitespaces if (whitespace1 || whitespace2) // Whitespaces
return 2; return 2;

View File

@@ -25,7 +25,7 @@
#include "diffutils.h" #include "diffutils.h"
#include "differ.h" #include "differ.h"
#include <QRegExp> #include <QRegularExpression>
#include <QStringList> #include <QStringList>
#include <QTextStream> #include <QTextStream>
#include "texteditor/fontsettings.h" #include "texteditor/fontsettings.h"
@@ -519,14 +519,11 @@ QString DiffUtils::makePatch(const QList<FileData> &fileDataList, unsigned forma
return diffText; return diffText;
} }
static QList<RowData> readLines(const QString &patch, static QList<RowData> readLines(QStringRef patch,
bool lastChunk, bool lastChunk,
bool *lastChunkAtTheEndOfFile, bool *lastChunkAtTheEndOfFile,
bool *ok) bool *ok)
{ {
// const QRegExp lineRegExp(QLatin1String("(?:\\n)" // beginning of the line
// "([ -\\+\\\\])([^\\n]*)" // -, +, \\ or space, followed by any no-newline character
// "(?:\\n|$)")); // end of line or file
QList<Diff> diffList; QList<Diff> diffList;
const QChar newLine = QLatin1Char('\n'); const QChar newLine = QLatin1Char('\n');
@@ -539,16 +536,16 @@ static QList<RowData> readLines(const QString &patch,
int noNewLineInDelete = -1; int noNewLineInDelete = -1;
int noNewLineInInsert = -1; int noNewLineInInsert = -1;
const QStringList lines = patch.split(newLine); const QVector<QStringRef> lines = patch.split(newLine);
int i; int i;
for (i = 0; i < lines.count(); i++) { for (i = 0; i < lines.count(); i++) {
const QString line = lines.at(i); QStringRef line = lines.at(i);
if (line.isEmpty()) { // need to have at least one character (1 column) if (line.isEmpty()) { // need to have at least one character (1 column)
if (lastChunk) if (lastChunk)
i = lines.count(); // pretend as we've read all the lines (we just ignore the rest) i = lines.count(); // pretend as we've read all the lines (we just ignore the rest)
break; break;
} }
QChar firstCharacter = line.at(0); const QChar firstCharacter = line.at(0);
if (firstCharacter == QLatin1Char('\\')) { // no new line marker if (firstCharacter == QLatin1Char('\\')) { // no new line marker
if (!lastChunk) // can only appear in last chunk of the file if (!lastChunk) // can only appear in last chunk of the file
break; break;
@@ -695,34 +692,35 @@ static QList<RowData> readLines(const QString &patch,
outputRightDiffList).rows; outputRightDiffList).rows;
} }
static QList<ChunkData> readChunks(const QString &patch, static QList<ChunkData> readChunks(QStringRef patch,
bool *lastChunkAtTheEndOfFile, bool *lastChunkAtTheEndOfFile,
bool *ok) bool *ok)
{ {
const QRegExp chunkRegExp(QLatin1String( const QRegularExpression chunkRegExp(
// beginning of the line // beginning of the line
"(?:\\n|^)" "(?:\\n|^)"
// @@ -leftPos[,leftCount] +rightPos[,rightCount] @@ // @@ -leftPos[,leftCount] +rightPos[,rightCount] @@
"@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@" "@@ -(\\d+)(?:,\\d+)? \\+(\\d+)(?:,\\d+)? @@"
// optional hint (e.g. function name) // optional hint (e.g. function name)
"(\\ +[^\\n]*)?" "(\\ +[^\\n]*)?"
// end of line (need to be followed by text line) // end of line (need to be followed by text line)
"\\n")); "\\n");
bool readOk = false; bool readOk = false;
QList<ChunkData> chunkDataList; QList<ChunkData> chunkDataList;
int pos = chunkRegExp.indexIn(patch); QRegularExpressionMatch match = chunkRegExp.match(patch);
if (pos == 0) { if (match.hasMatch() && match.capturedStart() == 0) {
int endOfLastChunk = 0; int endOfLastChunk = 0;
do { do {
const int leftStartingPos = chunkRegExp.cap(1).toInt(); const int pos = match.capturedStart();
const int rightStartingPos = chunkRegExp.cap(2).toInt(); const int leftStartingPos = match.capturedRef(1).toInt();
const QString contextInfo = chunkRegExp.cap(3); const int rightStartingPos = match.capturedRef(2).toInt();
const QString contextInfo = match.captured(3);
if (endOfLastChunk > 0) { if (endOfLastChunk > 0) {
const QString lines = patch.mid(endOfLastChunk, QStringRef lines = patch.mid(endOfLastChunk,
pos - endOfLastChunk); pos - endOfLastChunk);
chunkDataList.last().rows = readLines(lines, chunkDataList.last().rows = readLines(lines,
false, false,
lastChunkAtTheEndOfFile, lastChunkAtTheEndOfFile,
@@ -730,17 +728,17 @@ static QList<ChunkData> readChunks(const QString &patch,
if (!readOk) if (!readOk)
break; break;
} }
pos += chunkRegExp.matchedLength(); endOfLastChunk = match.capturedEnd();
endOfLastChunk = pos;
ChunkData chunkData; ChunkData chunkData;
chunkData.leftStartingLineNumber = leftStartingPos - 1; chunkData.leftStartingLineNumber = leftStartingPos - 1;
chunkData.rightStartingLineNumber = rightStartingPos - 1; chunkData.rightStartingLineNumber = rightStartingPos - 1;
chunkData.contextInfo = contextInfo; chunkData.contextInfo = contextInfo;
chunkDataList.append(chunkData); chunkDataList.append(chunkData);
} while ((pos = chunkRegExp.indexIn(patch, pos, QRegExp::CaretAtOffset)) != -1); match = chunkRegExp.match(patch, endOfLastChunk);
} while (match.hasMatch());
if (endOfLastChunk > 0) { if (endOfLastChunk > 0) {
const QString lines = patch.mid(endOfLastChunk); QStringRef lines = patch.mid(endOfLastChunk);
chunkDataList.last().rows = readLines(lines, chunkDataList.last().rows = readLines(lines,
true, true,
lastChunkAtTheEndOfFile, lastChunkAtTheEndOfFile,
@@ -754,42 +752,49 @@ static QList<ChunkData> readChunks(const QString &patch,
return chunkDataList; return chunkDataList;
} }
static FileData readDiffHeaderAndChunks(const QString &headerAndChunks, static FileData readDiffHeaderAndChunks(QStringRef headerAndChunks,
bool *ok) bool *ok)
{ {
QString patch = headerAndChunks; QStringRef patch = headerAndChunks;
FileData fileData; FileData fileData;
bool readOk = false; bool readOk = false;
const QRegExp leftFileRegExp(QLatin1String( const QRegularExpression leftFileRegExp(
"(?:\\n|^)-{3} " // "--- " "(?:\\n|^)-{3} " // "--- "
"([^\\t\\n]+)" // "fileName1" "([^\\t\\n]+)" // "fileName1"
"(?:\\t[^\\n]*)*\\n")); // optionally followed by: \t anything \t anything ...) "(?:\\t[^\\n]*)*\\n"); // optionally followed by: \t anything \t anything ...)
const QRegExp rightFileRegExp(QLatin1String( const QRegularExpression rightFileRegExp(
"^\\+{3} " // "+++ " "^\\+{3} " // "+++ "
"([^\\t\\n]+)" // "fileName2" "([^\\t\\n]+)" // "fileName2"
"(?:\\t[^\\n]*)*\\n")); // optionally followed by: \t anything \t anything ...) "(?:\\t[^\\n]*)*\\n"); // optionally followed by: \t anything \t anything ...)
const QRegExp binaryRegExp(QLatin1String("^Binary files ([^\\t\\n]+) and ([^\\t\\n]+) differ$")); const QRegularExpression binaryRegExp(
"^Binary files ([^\\t\\n]+) and ([^\\t\\n]+) differ$");
// followed either by leftFileRegExp or by binaryRegExp // followed either by leftFileRegExp
if (leftFileRegExp.indexIn(patch) == 0) { const QRegularExpressionMatch leftMatch = leftFileRegExp.match(patch);
patch.remove(0, leftFileRegExp.matchedLength()); if (leftMatch.hasMatch() && leftMatch.capturedStart() == 0) {
fileData.leftFileInfo.fileName = leftFileRegExp.cap(1); patch = patch.mid(leftMatch.capturedEnd());
fileData.leftFileInfo.fileName = leftMatch.captured(1);
// followed by rightFileRegExp // followed by rightFileRegExp
if (rightFileRegExp.indexIn(patch) == 0) { const QRegularExpressionMatch rightMatch = rightFileRegExp.match(patch);
patch.remove(0, rightFileRegExp.matchedLength()); if (rightMatch.hasMatch() && rightMatch.capturedStart() == 0) {
fileData.rightFileInfo.fileName = rightFileRegExp.cap(1); patch = patch.mid(rightMatch.capturedEnd());
fileData.rightFileInfo.fileName = rightMatch.captured(1);
fileData.chunks = readChunks(patch, fileData.chunks = readChunks(patch,
&fileData.lastChunkAtTheEndOfFile, &fileData.lastChunkAtTheEndOfFile,
&readOk); &readOk);
} }
} else if (binaryRegExp.indexIn(patch) == 0) { } else {
fileData.leftFileInfo.fileName = binaryRegExp.cap(1); // or by binaryRegExp
fileData.rightFileInfo.fileName = binaryRegExp.cap(2); const QRegularExpressionMatch binaryMatch = binaryRegExp.match(patch);
fileData.binaryFiles = true; if (binaryMatch.hasMatch() && binaryMatch.capturedStart() == 0) {
readOk = true; fileData.leftFileInfo.fileName = binaryMatch.captured(1);
fileData.rightFileInfo.fileName = binaryMatch.captured(2);
fileData.binaryFiles = true;
readOk = true;
}
} }
if (ok) if (ok)
@@ -802,37 +807,38 @@ static FileData readDiffHeaderAndChunks(const QString &headerAndChunks,
} }
static QList<FileData> readDiffPatch(const QString &patch, static QList<FileData> readDiffPatch(QStringRef patch,
bool *ok) bool *ok)
{ {
const QRegExp diffRegExp(QLatin1String("(?:\\n|^)" // new line of the beginning of a patch const QRegularExpression diffRegExp("(?:\\n|^)" // new line of the beginning of a patch
"(" // either "(" // either
"-{3} " // --- "-{3} " // ---
"[^\\t\\n]+" // filename1 "[^\\t\\n]+" // filename1
"(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ... "(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ...
"\\+{3} " // +++ "\\+{3} " // +++
"[^\\t\\n]+" // filename2 "[^\\t\\n]+" // filename2
"(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ... "(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ...
"|" // or "|" // or
"Binary files " "Binary files "
"[^\\t\\n]+" // filename1 "[^\\t\\n]+" // filename1
" and " " and "
"[^\\t\\n]+" // filename2 "[^\\t\\n]+" // filename2
" differ" " differ"
")")); // end of or ")"); // end of or
bool readOk = false; bool readOk = false;
QList<FileData> fileDataList; QList<FileData> fileDataList;
int pos = diffRegExp.indexIn(patch); QRegularExpressionMatch diffMatch = diffRegExp.match(patch);
if (pos >= 0) { // git style patch if (diffMatch.hasMatch()) {
readOk = true; readOk = true;
int lastPos = -1; int lastPos = -1;
do { do {
int pos = diffMatch.capturedStart();
if (lastPos >= 0) { if (lastPos >= 0) {
const QString headerAndChunks = patch.mid(lastPos, QStringRef headerAndChunks = patch.mid(lastPos,
pos - lastPos); pos - lastPos);
const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, const FileData fileData = readDiffHeaderAndChunks(headerAndChunks,
&readOk); &readOk);
@@ -843,12 +849,13 @@ static QList<FileData> readDiffPatch(const QString &patch,
fileDataList.append(fileData); fileDataList.append(fileData);
} }
lastPos = pos; lastPos = pos;
pos += diffRegExp.matchedLength(); pos = diffMatch.capturedEnd();
} while ((pos = diffRegExp.indexIn(patch, pos)) != -1); diffMatch = diffRegExp.match(patch, pos);
} while (diffMatch.hasMatch());
if (lastPos >= 0 && readOk) { if (readOk) {
const QString headerAndChunks = patch.mid(lastPos, QStringRef headerAndChunks = patch.mid(lastPos,
patch.count() - lastPos - 1); patch.count() - lastPos - 1);
const FileData fileData = readDiffHeaderAndChunks(headerAndChunks, const FileData fileData = readDiffHeaderAndChunks(headerAndChunks,
&readOk); &readOk);
@@ -872,7 +879,7 @@ static bool fileNameEnd(const QChar &c)
return c == QLatin1Char('\n') || c == QLatin1Char('\t'); return c == QLatin1Char('\n') || c == QLatin1Char('\t');
} }
static FileData readGitHeaderAndChunks(const QString &headerAndChunks, static FileData readGitHeaderAndChunks(QStringRef headerAndChunks,
const QString &fileName, const QString &fileName,
bool *ok) bool *ok)
{ {
@@ -880,41 +887,52 @@ static FileData readGitHeaderAndChunks(const QString &headerAndChunks,
fileData.leftFileInfo.fileName = fileName; fileData.leftFileInfo.fileName = fileName;
fileData.rightFileInfo.fileName = fileName; fileData.rightFileInfo.fileName = fileName;
QString patch = headerAndChunks; QStringRef patch = headerAndChunks;
bool readOk = false; bool readOk = false;
const QString devNull(QLatin1String("/dev/null")); const QString devNull(QLatin1String("/dev/null"));
// will be followed by: index 0000000..shasha, file "a" replaced by "/dev/null", @@ -0,0 +m,n @@ // will be followed by: index 0000000..shasha, file "a" replaced by "/dev/null", @@ -0,0 +m,n @@
const QRegExp newFileMode(QLatin1String("^new file mode \\d+\\n")); // new file mode octal // 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 @@ // will be followed by: index shasha..0000000, file "b" replaced by "/dev/null", @@ -m,n +0,0 @@
const QRegExp deletedFileMode(QLatin1String("^deleted file mode \\d+\\n")); // deleted file mode octal // deleted file mode octal
const QRegularExpression deletedFileMode("^deleted file mode \\d+\\n");
const QRegExp modeChangeRegExp(QLatin1String("^old mode \\d+\\nnew mode \\d+\\n")); const QRegularExpression modeChangeRegExp("^old mode \\d+\\nnew mode \\d+\\n");
const QRegExp indexRegExp(QLatin1String("^index (\\w+)\\.{2}(\\w+)(?: \\d+)?(\\n|$)")); // index cap1..cap2(optionally: octal) // index cap1..cap2(optionally: octal)
const QRegularExpression indexRegExp("^index (\\w+)\\.{2}(\\w+)(?: \\d+)?(\\n|$)");
QString leftFileName = QLatin1String("a/") + fileName; QString leftFileName = QLatin1String("a/") + fileName;
QString rightFileName = QLatin1String("b/") + fileName; QString rightFileName = QLatin1String("b/") + fileName;
if (newFileMode.indexIn(patch) == 0) { const QRegularExpressionMatch newFileMatch = newFileMode.match(patch);
if (newFileMatch.hasMatch() && newFileMatch.capturedStart() == 0) {
fileData.fileOperation = FileData::NewFile; fileData.fileOperation = FileData::NewFile;
leftFileName = devNull; leftFileName = devNull;
patch.remove(0, newFileMode.matchedLength()); patch = patch.mid(newFileMatch.capturedEnd());
} else if (deletedFileMode.indexIn(patch) == 0) { } else {
fileData.fileOperation = FileData::DeleteFile; const QRegularExpressionMatch deletedFileMatch = deletedFileMode.match(patch);
rightFileName = devNull; if (deletedFileMatch.hasMatch() && deletedFileMatch.capturedStart() == 0) {
patch.remove(0, deletedFileMode.matchedLength()); fileData.fileOperation = FileData::DeleteFile;
} else if (modeChangeRegExp.indexIn(patch) == 0) { rightFileName = devNull;
patch.remove(0, modeChangeRegExp.matchedLength()); patch = patch.mid(deletedFileMatch.capturedEnd());
} else {
const QRegularExpressionMatch modeChangeMatch = modeChangeRegExp.match(patch);
if (modeChangeMatch.hasMatch() && modeChangeMatch.capturedStart() == 0) {
patch = patch.mid(modeChangeMatch.capturedEnd());
}
}
} }
if (indexRegExp.indexIn(patch) == 0) { const QRegularExpressionMatch indexMatch = indexRegExp.match(patch);
fileData.leftFileInfo.typeInfo = indexRegExp.cap(1); if (indexMatch.hasMatch() && indexMatch.capturedStart() == 0) {
fileData.rightFileInfo.typeInfo = indexRegExp.cap(2); fileData.leftFileInfo.typeInfo = indexMatch.captured(1);
fileData.rightFileInfo.typeInfo = indexMatch.captured(2);
patch.remove(0, indexRegExp.matchedLength()); patch = patch.mid(indexMatch.capturedEnd());
} }
const QString binaryLine = QString::fromLatin1("Binary files ") + leftFileName const QString binaryLine = QString::fromLatin1("Binary files ") + leftFileName
@@ -927,14 +945,14 @@ static FileData readGitHeaderAndChunks(const QString &headerAndChunks,
|| fileData.fileOperation == FileData::DeleteFile)) { || fileData.fileOperation == FileData::DeleteFile)) {
readOk = true; readOk = true;
} else if (patch.startsWith(leftStart) && fileNameEnd(leftFollow)) { } else if (patch.startsWith(leftStart) && fileNameEnd(leftFollow)) {
patch.remove(0, patch.indexOf(QLatin1Char('\n'), leftStart.count()) + 1); patch = patch.mid(patch.indexOf(QLatin1Char('\n'), leftStart.count()) + 1);
const QString rightStart = QString::fromLatin1("+++ ") + rightFileName; const QString rightStart = QString::fromLatin1("+++ ") + rightFileName;
QChar rightFollow = patch.count() > rightStart.count() ? patch.at(rightStart.count()) : QLatin1Char('\n'); QChar rightFollow = patch.count() > rightStart.count() ? patch.at(rightStart.count()) : QLatin1Char('\n');
// followed by rightFileRegExp // followed by rightFileRegExp
if (patch.startsWith(rightStart) && fileNameEnd(rightFollow)) { if (patch.startsWith(rightStart) && fileNameEnd(rightFollow)) {
patch.remove(0, patch.indexOf(QLatin1Char('\n'), rightStart.count()) + 1); patch = patch.mid(patch.indexOf(QLatin1Char('\n'), rightStart.count()) + 1);
fileData.chunks = readChunks(patch, fileData.chunks = readChunks(patch,
&fileData.lastChunkAtTheEndOfFile, &fileData.lastChunkAtTheEndOfFile,
@@ -954,7 +972,7 @@ static FileData readGitHeaderAndChunks(const QString &headerAndChunks,
return fileData; return fileData;
} }
static FileData readCopyRenameChunks(const QString &copyRenameChunks, static FileData readCopyRenameChunks(QStringRef copyRenameChunks,
FileData::FileOperation fileOperation, FileData::FileOperation fileOperation,
const QString &leftFileName, const QString &leftFileName,
const QString &rightFileName, const QString &rightFileName,
@@ -965,24 +983,26 @@ static FileData readCopyRenameChunks(const QString &copyRenameChunks,
fileData.leftFileInfo.fileName = leftFileName; fileData.leftFileInfo.fileName = leftFileName;
fileData.rightFileInfo.fileName = rightFileName; fileData.rightFileInfo.fileName = rightFileName;
QString patch = copyRenameChunks; QStringRef patch = copyRenameChunks;
bool readOk = false; bool readOk = false;
const QRegExp indexRegExp(QLatin1String("^index (\\w+)\\.{2}(\\w+)(?: \\d+)?(\\n|$)")); // index cap1..cap2(optionally: octal) // index cap1..cap2(optionally: octal)
const QRegularExpression indexRegExp("^index (\\w+)\\.{2}(\\w+)(?: \\d+)?(\\n|$)");
if (fileOperation == FileData::CopyFile || fileOperation == FileData::RenameFile) { if (fileOperation == FileData::CopyFile || fileOperation == FileData::RenameFile) {
if (indexRegExp.indexIn(patch) == 0) { const QRegularExpressionMatch indexMatch = indexRegExp.match(patch);
fileData.leftFileInfo.typeInfo = indexRegExp.cap(1); if (indexMatch.hasMatch() && indexMatch.capturedStart() == 0) {
fileData.rightFileInfo.typeInfo = indexRegExp.cap(2); fileData.leftFileInfo.typeInfo = indexMatch.captured(1);
fileData.rightFileInfo.typeInfo = indexMatch.captured(2);
patch.remove(0, indexRegExp.matchedLength()); patch = patch.mid(indexMatch.capturedEnd());
const QString leftStart = QString::fromLatin1("--- a/") + leftFileName; const QString leftStart = QString::fromLatin1("--- a/") + leftFileName;
QChar leftFollow = patch.count() > leftStart.count() ? patch.at(leftStart.count()) : QLatin1Char('\n'); QChar leftFollow = patch.count() > leftStart.count() ? patch.at(leftStart.count()) : QLatin1Char('\n');
// followed by leftFileRegExp // followed by leftFileRegExp
if (patch.startsWith(leftStart) && fileNameEnd(leftFollow)) { if (patch.startsWith(leftStart) && fileNameEnd(leftFollow)) {
patch.remove(0, patch.indexOf(QLatin1Char('\n'), leftStart.count()) + 1); patch = patch.mid(patch.indexOf(QLatin1Char('\n'), leftStart.count()) + 1);
// followed by rightFileRegExp // followed by rightFileRegExp
const QString rightStart = QString::fromLatin1("+++ b/") + rightFileName; const QString rightStart = QString::fromLatin1("+++ b/") + rightFileName;
@@ -990,7 +1010,7 @@ static FileData readCopyRenameChunks(const QString &copyRenameChunks,
// followed by rightFileRegExp // followed by rightFileRegExp
if (patch.startsWith(rightStart) && fileNameEnd(rightFollow)) { if (patch.startsWith(rightStart) && fileNameEnd(rightFollow)) {
patch.remove(0, patch.indexOf(QLatin1Char('\n'), rightStart.count()) + 1); patch = patch.mid(patch.indexOf(QLatin1Char('\n'), rightStart.count()) + 1);
fileData.chunks = readChunks(patch, fileData.chunks = readChunks(patch,
&fileData.lastChunkAtTheEndOfFile, &fileData.lastChunkAtTheEndOfFile,
@@ -1011,15 +1031,19 @@ static FileData readCopyRenameChunks(const QString &copyRenameChunks,
return fileData; return fileData;
} }
static QList<FileData> readGitPatch(const QString &patch, bool *ok) static QList<FileData> readGitPatch(QStringRef patch, bool *ok)
{ {
const QRegExp simpleGitRegExp(QLatin1String("(?:\\n|^)diff --git a/([^\\n]+) b/\\1\\n")); // diff --git a/cap1 b/cap1
const QRegExp similarityRegExp(QLatin1String( const QRegularExpression simpleGitRegExp(
"(?:\\n|^)diff --git a/([^\\n]+) b/([^\\n]+)\\n" // diff --git a/cap1 b/cap2 "^diff --git a/([^\\n]+) b/\\1\\n" // diff --git a/cap1 b/cap1
"(?:dis)?similarity index \\d{1,3}%\\n" // similarity / dissimilarity index xxx% (100% max) , QRegularExpression::MultilineOption);
"(copy|rename) from \\1\\n" // copy / rename from cap1
"\\3 to \\2\\n")); // copy / rename (cap3) to cap2 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);
bool readOk = false; bool readOk = false;
@@ -1027,20 +1051,46 @@ static QList<FileData> readGitPatch(const QString &patch, bool *ok)
bool simpleGitMatched; bool simpleGitMatched;
int pos = 0; int pos = 0;
QRegularExpressionMatch simpleGitMatch = simpleGitRegExp.match(patch);
QRegularExpressionMatch similarityMatch = similarityRegExp.match(patch);
auto calculateGitMatchAndPosition = [&]() { auto calculateGitMatchAndPosition = [&]() {
const int simpleGitPos = simpleGitRegExp.indexIn(patch, pos, QRegExp::CaretAtOffset); if (pos > 0) { // don't advance in the initial call
const int similarityPos = similarityRegExp.indexIn(patch, pos, QRegExp::CaretAtOffset); if (simpleGitMatch.hasMatch() && similarityMatch.hasMatch()) {
if (simpleGitPos < 0) { const int simpleGitPos = simpleGitMatch.capturedStart();
pos = similarityPos; const int similarityPos = similarityMatch.capturedStart();
simpleGitMatched = false; 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; return;
} else if (similarityPos < 0) { }
pos = simpleGitPos;
if (simpleGitMatch.hasMatch()) {
pos = simpleGitMatch.capturedStart();
simpleGitMatched = true; simpleGitMatched = true;
return; return;
} }
pos = qMin(simpleGitPos, similarityPos);
simpleGitMatched = (pos == simpleGitPos); if (similarityMatch.hasMatch()) {
pos = similarityMatch.capturedStart();
simpleGitMatched = false;
return;
}
pos = -1;
simpleGitMatched = true;
}; };
// Set both pos and simpleGitMatched according to the first match: // Set both pos and simpleGitMatched according to the first match:
@@ -1052,10 +1102,12 @@ static QList<FileData> readGitPatch(const QString &patch, bool *ok)
QString lastLeftFileName; QString lastLeftFileName;
QString lastRightFileName; QString lastRightFileName;
FileData::FileOperation lastOperation = FileData::ChangeFile; FileData::FileOperation lastOperation = FileData::ChangeFile;
do {
if (endOfLastHeader > 0) { auto collectFileData = [&]() {
const QString headerAndChunks = patch.mid(endOfLastHeader, if (endOfLastHeader > 0 && readOk) {
pos - endOfLastHeader); const int end = pos < 0 ? patch.count() : pos;
QStringRef headerAndChunks = patch.mid(endOfLastHeader,
qMax(end - endOfLastHeader - 1, 0));
FileData fileData; FileData fileData;
if (lastOperation == FileData::ChangeFile) { if (lastOperation == FileData::ChangeFile) {
@@ -1069,25 +1121,27 @@ static QList<FileData> readGitPatch(const QString &patch, bool *ok)
lastRightFileName, lastRightFileName,
&readOk); &readOk);
} }
if (!readOk) if (readOk)
break; fileDataList.append(fileData);
fileDataList.append(fileData);
} }
};
do {
collectFileData();
if (!readOk)
break;
if (simpleGitMatched) { if (simpleGitMatched) {
const QString fileName = simpleGitRegExp.cap(1); const QString fileName = simpleGitMatch.captured(1);
pos += simpleGitRegExp.matchedLength(); pos = simpleGitMatch.capturedEnd();
endOfLastHeader = pos;
lastLeftFileName = fileName; lastLeftFileName = fileName;
lastRightFileName = fileName; lastRightFileName = fileName;
lastOperation = FileData::ChangeFile; lastOperation = FileData::ChangeFile;
} else { } else {
lastLeftFileName = similarityRegExp.cap(1); lastLeftFileName = similarityMatch.captured(1);
lastRightFileName = similarityRegExp.cap(2); lastRightFileName = similarityMatch.captured(2);
const QString operation = similarityRegExp.cap(3); const QString operation = similarityMatch.captured(3);
pos += similarityRegExp.matchedLength(); pos = similarityMatch.capturedEnd();
endOfLastHeader = pos;
if (operation == QLatin1String("copy")) if (operation == QLatin1String("copy"))
lastOperation = FileData::CopyFile; lastOperation = FileData::CopyFile;
else if (operation == QLatin1String("rename")) else if (operation == QLatin1String("rename"))
@@ -1095,31 +1149,14 @@ static QList<FileData> readGitPatch(const QString &patch, bool *ok)
else else
break; // either copy or rename, otherwise broken break; // either copy or rename, otherwise broken
} }
endOfLastHeader = pos;
// give both pos and simpleGitMatched a new value for the next match // give both pos and simpleGitMatched a new value for the next match
calculateGitMatchAndPosition(); calculateGitMatchAndPosition();
} while (pos != -1); } while (pos != -1);
if (endOfLastHeader > 0 && readOk) { if (readOk)
const QString headerAndChunks = patch.mid(endOfLastHeader, collectFileData();
patch.count() - endOfLastHeader - 1);
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 (ok) if (ok)
@@ -1137,12 +1174,12 @@ QList<FileData> DiffUtils::readPatch(const QString &patch, bool *ok)
QList<FileData> fileDataList; QList<FileData> fileDataList;
QString croppedPatch = patch; QStringRef croppedPatch(&patch);
// Crop e.g. "-- \n2.10.2.windows.1\n\n" at end of file // Crop e.g. "-- \n2.10.2.windows.1\n\n" at end of file
const QRegExp formatPatchEndingRegExp(QLatin1String("(\\n-- \\n\\S*\\n\\n$)")); const QRegularExpression formatPatchEndingRegExp("(\\n-- \\n\\S*\\n\\n$)");
const int pos = formatPatchEndingRegExp.indexIn(patch); const QRegularExpressionMatch match = formatPatchEndingRegExp.match(croppedPatch);
if (pos != -1) if (match.hasMatch())
croppedPatch = patch.left(pos + 1); // crop the ending for git format-patch croppedPatch = croppedPatch.left(match.capturedStart() + 1);
fileDataList = readGitPatch(croppedPatch, &readOk); fileDataList = readGitPatch(croppedPatch, &readOk);
if (!readOk) if (!readOk)