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