diff options
-rw-r--r-- | src/plugins/diffeditor/diffeditorplugin.cpp | 45 | ||||
-rw-r--r-- | src/plugins/diffeditor/diffutils.cpp | 185 | ||||
-rw-r--r-- | src/plugins/diffeditor/diffutils.h | 22 |
3 files changed, 223 insertions, 29 deletions
diff --git a/src/plugins/diffeditor/diffeditorplugin.cpp b/src/plugins/diffeditor/diffeditorplugin.cpp index 20ff559d28..0d6ad83df0 100644 --- a/src/plugins/diffeditor/diffeditorplugin.cpp +++ b/src/plugins/diffeditor/diffeditorplugin.cpp @@ -483,7 +483,23 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data() "diff --git a/empty b/empty\n" "deleted file mode 100644\n" "index e69de29..0000000\n" - ); + "diff --git a/file a.txt b/file b.txt\n" + "similarity index 99%\n" + "copy from file a.txt\n" + "copy to file b.txt\n" + "index 1234567..9876543\n" + "--- a/file a.txt\n" + "+++ b/file b.txt\n" + "@@ -20,3 +20,3 @@\n" + " A\n" + "-B\n" + "+C\n" + " D\n" + "diff --git a/file a.txt b/file b.txt\n" + "similarity index 99%\n" + "rename from file a.txt\n" + "rename to file b.txt\n" + ); FileData fileData1; fileData1.leftFileInfo = DiffFileInfo(QLatin1String("src/plugins/diffeditor/diffeditor.cpp"), @@ -543,6 +559,7 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data() FileData fileData3; fileData3.leftFileInfo = DiffFileInfo(QLatin1String("new"), QLatin1String("0000000")); fileData3.rightFileInfo = DiffFileInfo(QLatin1String("new"), QLatin1String("257cc56")); + fileData3.fileOperation = FileData::NewFile; ChunkData chunkData3; chunkData3.leftStartingLineNumber = -1; chunkData3.rightStartingLineNumber = 0; @@ -556,6 +573,7 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data() FileData fileData4; fileData4.leftFileInfo = DiffFileInfo(QLatin1String("deleted"), QLatin1String("257cc56")); fileData4.rightFileInfo = DiffFileInfo(QLatin1String("deleted"), QLatin1String("0000000")); + fileData4.fileOperation = FileData::DeleteFile; ChunkData chunkData4; chunkData4.leftStartingLineNumber = 0; chunkData4.rightStartingLineNumber = -1; @@ -569,13 +587,35 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data() FileData fileData5; fileData5.leftFileInfo = DiffFileInfo(QLatin1String("empty"), QLatin1String("0000000")); fileData5.rightFileInfo = DiffFileInfo(QLatin1String("empty"), QLatin1String("e69de29")); + fileData5.fileOperation = FileData::NewFile; FileData fileData6; fileData6.leftFileInfo = DiffFileInfo(QLatin1String("empty"), QLatin1String("e69de29")); fileData6.rightFileInfo = DiffFileInfo(QLatin1String("empty"), QLatin1String("0000000")); + fileData6.fileOperation = FileData::DeleteFile; + + FileData fileData7; + fileData7.leftFileInfo = DiffFileInfo(QLatin1String("file a.txt"), QLatin1String("1234567")); + fileData7.rightFileInfo = DiffFileInfo(QLatin1String("file b.txt"), QLatin1String("9876543")); + fileData7.fileOperation = FileData::CopyFile; + ChunkData chunkData7; + chunkData7.leftStartingLineNumber = 19; + chunkData7.rightStartingLineNumber = 19; + QList<RowData> rows7; + rows7.append(RowData(TextLineData(QLatin1String("A")))); + rows7.append(RowData(TextLineData(QLatin1String("B")), + TextLineData(QLatin1String("C")))); + rows7.append(RowData(TextLineData(QLatin1String("D")))); + chunkData7.rows = rows7; + fileData7.chunks.append(chunkData7); + + FileData fileData8; + fileData8.leftFileInfo = DiffFileInfo(QLatin1String("file a.txt")); + fileData8.rightFileInfo = DiffFileInfo(QLatin1String("file b.txt")); + fileData8.fileOperation = FileData::RenameFile; QList<FileData> fileDataList; - fileDataList << fileData1 << fileData2 << fileData3 << fileData4 << fileData5 << fileData6; + fileDataList << fileData1 << fileData2 << fileData3 << fileData4 << fileData5 << fileData6 << fileData7 << fileData8; QTest::newRow("Git patch") << patch << fileDataList; @@ -599,6 +639,7 @@ void DiffEditor::Internal::DiffEditorPlugin::testReadPatch() QCOMPARE(resultFileData.rightFileInfo.fileName, origFileData.rightFileInfo.fileName); QCOMPARE(resultFileData.rightFileInfo.typeInfo, origFileData.rightFileInfo.typeInfo); QCOMPARE(resultFileData.chunks.count(), origFileData.chunks.count()); + QCOMPARE(resultFileData.fileOperation, origFileData.fileOperation); for (int j = 0; j < origFileData.chunks.count(); j++) { const ChunkData &origChunkData = origFileData.chunks.at(j); const ChunkData &resultChunkData = resultFileData.chunks.at(j); diff --git a/src/plugins/diffeditor/diffutils.cpp b/src/plugins/diffeditor/diffutils.cpp index 0785e815bc..18725363fb 100644 --- a/src/plugins/diffeditor/diffutils.cpp +++ b/src/plugins/diffeditor/diffutils.cpp @@ -859,11 +859,11 @@ static FileData readGitHeaderAndChunks(const QString &headerAndChunks, QString rightFileName = QLatin1String("b/") + fileName; if (newFileMode.indexIn(patch, 0) == 0) { - fileData.leftFileInfo.devNull = true; + fileData.fileOperation = FileData::NewFile; leftFileName = devNull; patch = patch.mid(newFileMode.capturedTexts().at(1).count()); } else if (deletedFileMode.indexIn(patch, 0) == 0) { - fileData.rightFileInfo.devNull = true; + fileData.fileOperation = FileData::DeleteFile; rightFileName = devNull; patch = patch.mid(deletedFileMode.capturedTexts().at(1).count()); } @@ -889,7 +889,8 @@ static FileData readGitHeaderAndChunks(const QString &headerAndChunks, + QLatin1String(" differ$)")); // empty or followed either by leftFileRegExp or by binaryRegExp - if (patch.isEmpty() && (fileData.leftFileInfo.devNull || fileData.rightFileInfo.devNull)) { + if (patch.isEmpty() && (fileData.fileOperation == FileData::NewFile + || fileData.fileOperation == FileData::DeleteFile)) { readOk = true; } else if (leftFileRegExp.indexIn(patch, 0) == 0) { patch = patch.mid(leftFileRegExp.capturedTexts().at(1).count()); @@ -918,51 +919,193 @@ static FileData readGitHeaderAndChunks(const QString &headerAndChunks, return fileData; } +static FileData readCopyRenameChunks(const QString ©RenameChunks, + FileData::FileOperation fileOperation, + const QString &leftFileName, + const QString &rightFileName, + bool ignoreWhitespace, + bool *ok) +{ + FileData fileData; + fileData.fileOperation = fileOperation; + fileData.leftFileInfo.fileName = leftFileName; + fileData.rightFileInfo.fileName = rightFileName; + + QString patch = copyRenameChunks; + bool readOk = false; + + const QRegExp indexRegExp(QLatin1String("(^index (\\w+)\\.{2}(\\w+)(?: \\d+)?(\\n|$))")); // index cap2..cap3(optionally: octal) + + QString leftGitFileName = QLatin1String("a/") + leftFileName; + QString rightGitFileName = QLatin1String("b/") + rightFileName; + + if (fileOperation == FileData::CopyFile || fileOperation == FileData::RenameFile) { + if (indexRegExp.indexIn(patch, 0) == 0) { + const QStringList capturedTexts = indexRegExp.capturedTexts(); + const QString captured = capturedTexts.at(1); + fileData.leftFileInfo.typeInfo = capturedTexts.at(2); + fileData.rightFileInfo.typeInfo = capturedTexts.at(3); + + patch = patch.mid(captured.count()); + + const QRegExp leftFileRegExp(QLatin1String("(^-{3} ") // "--- " + + leftGitFileName // "a/fileName" or "/dev/null" + + QLatin1String("(?:\\t[^\\n]*)*\\n)")); // optionally followed by: \t anything \t anything ...) + const QRegExp rightFileRegExp(QLatin1String("(^\\+{3} ") // "+++ " + + rightGitFileName // "b/fileName" or "/dev/null" + + QLatin1String("(?:\\t[^\\n]*)*\\n)")); // optionally followed by: \t anything \t anything ...) + + // followed by leftFileRegExp + if (leftFileRegExp.indexIn(patch, 0) == 0) { + patch = patch.mid(leftFileRegExp.capturedTexts().at(1).count()); + + // followed by rightFileRegExp + if (rightFileRegExp.indexIn(patch, 0) == 0) { + patch = patch.mid(rightFileRegExp.capturedTexts().at(1).count()); + + fileData.chunks = readChunks(patch, + ignoreWhitespace, + &fileData.lastChunkAtTheEndOfFile, + &readOk); + } + } + } else if (copyRenameChunks.isEmpty()) { + readOk = true; + } + } + + if (ok) + *ok = readOk; + + if (!readOk) + return FileData(); + + return fileData; +} + static QList<FileData> readGitPatch(const QString &patch, bool ignoreWhitespace, bool *ok) { - const QRegExp gitRegExp(QLatin1String("((?:\\n|^)diff --git a/([^\\n]+) b/\\2\\n)")); // diff --git a/cap2 b/cap2 + const QRegExp simpleGitRegExp(QLatin1String("((?:\\n|^)diff --git a/([^\\n]+) b/\\2\\n)")); // diff --git a/cap2 b/cap2 + + const QRegExp similarityRegExp(QLatin1String( + "((?:\\n|^)diff --git a/([^\\n]+) b/([^\\n]+)\\n" // diff --git a/cap2 b/cap3 + "(?:dis)?similarity index \\d{1,3}%\\n" // similarity / dissimilarity index xxx% (100% max) + "(copy|rename) from \\2\\n" // copy / rename from cap2 + "\\4 to \\3\\n)")); // copy / rename (cap4) to cap3 bool readOk = false; QList<FileData> fileDataList; - int pos = gitRegExp.indexIn(patch, 0); + const int simpleGitPos = simpleGitRegExp.indexIn(patch, 0); + const int similarityPos = similarityRegExp.indexIn(patch, 0); + + bool simpleGitMatched = false; + int pos = -1; + if (simpleGitPos < 0) { + pos = similarityPos; + } else if (similarityPos < 0) { + pos = simpleGitPos; + simpleGitMatched = true; + } else { + pos = qMin(simpleGitPos, similarityPos); + simpleGitMatched = (pos == simpleGitPos); + } + if (pos == 0) { // git style patch readOk = true; int endOfLastHeader = 0; - QString lastFileName; + QString lastLeftFileName; + QString lastRightFileName; + FileData::FileOperation lastOperation = FileData::ChangeFile; do { - const QStringList capturedTexts = gitRegExp.capturedTexts(); - const QString captured = capturedTexts.at(1); - const QString fileName = capturedTexts.at(2); if (endOfLastHeader > 0) { const QString headerAndChunks = patch.mid(endOfLastHeader, pos - endOfLastHeader); - const FileData fileData = readGitHeaderAndChunks(headerAndChunks, - lastFileName, - ignoreWhitespace, - &readOk); + FileData fileData; + if (lastOperation == FileData::ChangeFile) { + fileData = readGitHeaderAndChunks(headerAndChunks, + lastLeftFileName, + ignoreWhitespace, + &readOk); + } else { + fileData = readCopyRenameChunks(headerAndChunks, + lastOperation, + lastLeftFileName, + lastRightFileName, + ignoreWhitespace, + &readOk); + } if (!readOk) break; fileDataList.append(fileData); } - pos += captured.count(); - endOfLastHeader = pos; - lastFileName = fileName; - } while ((pos = gitRegExp.indexIn(patch, pos)) != -1); + + if (simpleGitMatched) { + const QStringList capturedTexts = simpleGitRegExp.capturedTexts(); + const QString captured = capturedTexts.at(1); + const QString fileName = capturedTexts.at(2); + pos += captured.count(); + endOfLastHeader = pos; + lastLeftFileName = fileName; + lastRightFileName = fileName; + lastOperation = FileData::ChangeFile; + } else { + const QStringList capturedTexts = similarityRegExp.capturedTexts(); + const QString captured = capturedTexts.at(1); + const QString leftFileName = capturedTexts.at(2); + const QString rightFileName = capturedTexts.at(3); + const QString operation = capturedTexts.at(4); + pos += captured.count(); + endOfLastHeader = pos; + lastLeftFileName = leftFileName; + lastRightFileName = rightFileName; + if (operation == QLatin1String("copy")) + lastOperation = FileData::CopyFile; + else if (operation == QLatin1String("rename")) + lastOperation = FileData::RenameFile; + else + break; // either copy or rename, otherwise broken + } + + const int simpleGitPos = simpleGitRegExp.indexIn(patch, pos); + const int similarityPos = similarityRegExp.indexIn(patch, pos); + + simpleGitMatched = false; + pos = -1; + if (simpleGitPos < 0) { + pos = similarityPos; + } else if (similarityPos < 0) { + pos = simpleGitPos; + simpleGitMatched = true; + } else { + pos = qMin(simpleGitPos, similarityPos); + simpleGitMatched = (pos == simpleGitPos); + } + } while (pos != -1); if (endOfLastHeader > 0 && readOk) { const QString headerAndChunks = patch.mid(endOfLastHeader, patch.count() - endOfLastHeader - 1); - const FileData fileData = readGitHeaderAndChunks(headerAndChunks, - lastFileName, - ignoreWhitespace, - &readOk); + FileData fileData; + if (lastOperation == FileData::ChangeFile) { + fileData = readGitHeaderAndChunks(headerAndChunks, + lastLeftFileName, + ignoreWhitespace, + &readOk); + } else { + fileData = readCopyRenameChunks(headerAndChunks, + lastOperation, + lastLeftFileName, + lastRightFileName, + ignoreWhitespace, + &readOk); + } if (readOk) fileDataList.append(fileData); } diff --git a/src/plugins/diffeditor/diffutils.h b/src/plugins/diffeditor/diffutils.h index 1c5d237e2d..b649538026 100644 --- a/src/plugins/diffeditor/diffutils.h +++ b/src/plugins/diffeditor/diffutils.h @@ -46,13 +46,12 @@ class Diff; class DIFFEDITOR_EXPORT DiffFileInfo { public: - DiffFileInfo() : devNull(false) {} - DiffFileInfo(const QString &file) : fileName(file), devNull(false) {} + DiffFileInfo() {} + DiffFileInfo(const QString &file) : fileName(file) {} DiffFileInfo(const QString &file, const QString &type) - : fileName(file), typeInfo(type), devNull(false) {} + : fileName(file), typeInfo(type) {} QString fileName; QString typeInfo; - bool devNull; }; class DIFFEDITOR_EXPORT TextLineData { @@ -100,17 +99,28 @@ public: class DIFFEDITOR_EXPORT FileData { public: + enum FileOperation { + ChangeFile, + NewFile, + DeleteFile, + CopyFile, + RenameFile + }; + FileData() - : binaryFiles(false), + : fileOperation(ChangeFile), + binaryFiles(false), lastChunkAtTheEndOfFile(false), contextChunksIncluded(false) {} FileData(const ChunkData &chunkData) - : binaryFiles(false), + : fileOperation(ChangeFile), + binaryFiles(false), lastChunkAtTheEndOfFile(false), contextChunksIncluded(false) { chunks.append(chunkData); } QList<ChunkData> chunks; DiffFileInfo leftFileInfo; DiffFileInfo rightFileInfo; + FileOperation fileOperation; bool binaryFiles; bool lastChunkAtTheEndOfFile; bool contextChunksIncluded; |