2022-08-19 15:59:36 +02:00
|
|
|
// Copyright (C) 2016 The Qt Company Ltd.
|
|
|
|
|
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0+ OR GPL-3.0 WITH Qt-GPL-exception-1.0
|
2014-02-28 10:40:20 +01:00
|
|
|
|
|
|
|
|
#include "diffutils.h"
|
2017-06-30 16:06:36 +02:00
|
|
|
|
2019-11-20 16:01:17 +01:00
|
|
|
#include <utils/algorithm.h>
|
2018-08-29 14:36:47 +02:00
|
|
|
#include <utils/differ.h>
|
2017-06-30 16:06:36 +02:00
|
|
|
|
|
|
|
|
#include <QFutureInterfaceBase>
|
2017-06-16 13:45:43 +02:00
|
|
|
#include <QRegularExpression>
|
2014-02-28 10:40:20 +01:00
|
|
|
#include <QStringList>
|
2014-11-11 16:27:23 +01:00
|
|
|
#include <QTextStream>
|
2014-02-28 10:40:20 +01:00
|
|
|
|
2018-08-29 14:36:47 +02:00
|
|
|
using namespace Utils;
|
|
|
|
|
|
2014-02-28 10:40:20 +01:00
|
|
|
namespace DiffEditor {
|
|
|
|
|
|
2019-11-20 16:01:17 +01:00
|
|
|
int ChunkSelection::selectedRowsCount() const
|
|
|
|
|
{
|
2020-09-07 15:41:01 +02:00
|
|
|
return Utils::toSet(leftSelection).unite(Utils::toSet(rightSelection)).size();
|
2019-11-20 16:01:17 +01:00
|
|
|
}
|
|
|
|
|
|
2014-03-11 15:31:19 +01:00
|
|
|
static QList<TextLineData> assemblyRows(const QList<TextLineData> &lines,
|
2014-02-28 10:40:20 +01:00
|
|
|
const QMap<int, int> &lineSpans)
|
|
|
|
|
{
|
|
|
|
|
QList<TextLineData> data;
|
|
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
const int lineCount = lines.size();
|
2014-02-28 10:40:20 +01:00
|
|
|
for (int i = 0; i <= lineCount; i++) {
|
|
|
|
|
for (int j = 0; j < lineSpans.value(i); j++)
|
|
|
|
|
data.append(TextLineData(TextLineData::Separator));
|
|
|
|
|
if (i < lineCount)
|
|
|
|
|
data.append(lines.at(i));
|
|
|
|
|
}
|
|
|
|
|
return data;
|
|
|
|
|
}
|
|
|
|
|
|
2014-03-11 15:31:19 +01:00
|
|
|
static bool lastLinesEqual(const QList<TextLineData> &leftLines,
|
|
|
|
|
const QList<TextLineData> &rightLines)
|
2014-02-28 10:40:20 +01:00
|
|
|
{
|
2020-01-15 19:10:34 +01:00
|
|
|
const bool leftLineEqual = !leftLines.isEmpty()
|
2014-03-11 15:31:19 +01:00
|
|
|
? leftLines.last().text.isEmpty()
|
2014-02-28 10:40:20 +01:00
|
|
|
: true;
|
2020-01-15 19:10:34 +01:00
|
|
|
const bool rightLineEqual = !rightLines.isEmpty()
|
2014-03-11 15:31:19 +01:00
|
|
|
? rightLines.last().text.isEmpty()
|
2014-02-28 10:40:20 +01:00
|
|
|
: true;
|
|
|
|
|
return leftLineEqual && rightLineEqual;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handleLine(const QStringList &newLines,
|
|
|
|
|
int line,
|
2014-03-11 15:31:19 +01:00
|
|
|
QList<TextLineData> *lines,
|
|
|
|
|
int *lineNumber)
|
2014-02-28 10:40:20 +01:00
|
|
|
{
|
2020-09-07 15:41:01 +02:00
|
|
|
if (line < newLines.size()) {
|
2014-02-28 10:40:20 +01:00
|
|
|
const QString text = newLines.at(line);
|
|
|
|
|
if (lines->isEmpty() || line > 0) {
|
|
|
|
|
if (line > 0)
|
|
|
|
|
++*lineNumber;
|
2014-03-11 15:31:19 +01:00
|
|
|
lines->append(TextLineData(text));
|
2014-02-28 10:40:20 +01:00
|
|
|
} else {
|
2014-03-11 15:31:19 +01:00
|
|
|
lines->last().text += text;
|
2014-02-28 10:40:20 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handleDifference(const QString &text,
|
2014-03-11 15:31:19 +01:00
|
|
|
QList<TextLineData> *lines,
|
|
|
|
|
int *lineNumber)
|
2014-02-28 10:40:20 +01:00
|
|
|
{
|
2017-12-06 21:30:57 +01:00
|
|
|
const QStringList newLines = text.split('\n');
|
2020-09-07 15:41:01 +02:00
|
|
|
for (int line = 0; line < newLines.size(); ++line) {
|
|
|
|
|
const int startPos = line > 0 ? -1 : lines->isEmpty() ? 0 : lines->last().text.size();
|
2014-03-11 15:31:19 +01:00
|
|
|
handleLine(newLines, line, lines, lineNumber);
|
2020-09-07 15:41:01 +02:00
|
|
|
const int endPos = line < newLines.size() - 1
|
|
|
|
|
? -1
|
|
|
|
|
: lines->isEmpty() ? 0 : lines->last().text.size();
|
2014-03-11 15:31:19 +01:00
|
|
|
if (!lines->isEmpty())
|
|
|
|
|
lines->last().changedPositions.insert(startPos, endPos);
|
|
|
|
|
}
|
2014-02-28 10:40:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* leftDiffList can contain only deletions and equalities,
|
|
|
|
|
* while rightDiffList can contain only insertions and equalities.
|
|
|
|
|
* The number of equalities on both lists must be the same.
|
|
|
|
|
*/
|
2014-02-13 16:43:28 +01:00
|
|
|
ChunkData DiffUtils::calculateOriginalData(const QList<Diff> &leftDiffList,
|
2018-08-29 14:36:47 +02:00
|
|
|
const QList<Diff> &rightDiffList)
|
2014-02-28 10:40:20 +01:00
|
|
|
{
|
|
|
|
|
int i = 0;
|
|
|
|
|
int j = 0;
|
|
|
|
|
|
2014-03-11 15:31:19 +01:00
|
|
|
QList<TextLineData> leftLines;
|
|
|
|
|
QList<TextLineData> rightLines;
|
2014-02-28 10:40:20 +01:00
|
|
|
|
|
|
|
|
// <line number, span count>
|
|
|
|
|
QMap<int, int> leftSpans;
|
|
|
|
|
QMap<int, int> rightSpans;
|
|
|
|
|
// <left line number, right line number>
|
|
|
|
|
QMap<int, int> equalLines;
|
|
|
|
|
|
|
|
|
|
int leftLineNumber = 0;
|
|
|
|
|
int rightLineNumber = 0;
|
|
|
|
|
int leftLineAligned = -1;
|
|
|
|
|
int rightLineAligned = -1;
|
|
|
|
|
bool lastLineEqual = true;
|
|
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
while (i <= leftDiffList.size() && j <= rightDiffList.size()) {
|
|
|
|
|
const Diff leftDiff = i < leftDiffList.size() ? leftDiffList.at(i) : Diff(Diff::Equal);
|
|
|
|
|
const Diff rightDiff = j < rightDiffList.size() ? rightDiffList.at(j) : Diff(Diff::Equal);
|
2014-02-28 10:40:20 +01:00
|
|
|
|
|
|
|
|
if (leftDiff.command == Diff::Delete) {
|
2020-09-07 15:41:01 +02:00
|
|
|
if (j == rightDiffList.size() && lastLineEqual && leftDiff.text.startsWith('\n'))
|
2014-07-09 12:37:47 +02:00
|
|
|
equalLines.insert(leftLineNumber, rightLineNumber);
|
2014-02-28 10:40:20 +01:00
|
|
|
// process delete
|
2014-03-11 15:31:19 +01:00
|
|
|
handleDifference(leftDiff.text, &leftLines, &leftLineNumber);
|
2014-02-28 10:40:20 +01:00
|
|
|
lastLineEqual = lastLinesEqual(leftLines, rightLines);
|
2020-09-07 15:41:01 +02:00
|
|
|
if (j == rightDiffList.size())
|
2014-07-09 12:37:47 +02:00
|
|
|
lastLineEqual = false;
|
2014-02-28 10:40:20 +01:00
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
if (rightDiff.command == Diff::Insert) {
|
2020-09-07 15:41:01 +02:00
|
|
|
if (i == leftDiffList.size() && lastLineEqual && rightDiff.text.startsWith('\n'))
|
2014-07-09 12:37:47 +02:00
|
|
|
equalLines.insert(leftLineNumber, rightLineNumber);
|
2014-02-28 10:40:20 +01:00
|
|
|
// process insert
|
2014-03-11 15:31:19 +01:00
|
|
|
handleDifference(rightDiff.text, &rightLines, &rightLineNumber);
|
2014-02-28 10:40:20 +01:00
|
|
|
lastLineEqual = lastLinesEqual(leftLines, rightLines);
|
2020-09-07 15:41:01 +02:00
|
|
|
if (i == leftDiffList.size())
|
2014-07-09 12:37:47 +02:00
|
|
|
lastLineEqual = false;
|
2014-02-28 10:40:20 +01:00
|
|
|
j++;
|
|
|
|
|
}
|
|
|
|
|
if (leftDiff.command == Diff::Equal && rightDiff.command == Diff::Equal) {
|
|
|
|
|
// process equal
|
2017-12-06 21:30:57 +01:00
|
|
|
const QStringList newLeftLines = leftDiff.text.split('\n');
|
|
|
|
|
const QStringList newRightLines = rightDiff.text.split('\n');
|
2014-02-28 10:40:20 +01:00
|
|
|
|
|
|
|
|
int line = 0;
|
|
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
if (i < leftDiffList.size() || j < rightDiffList.size()
|
|
|
|
|
|| (!leftLines.isEmpty() && !rightLines.isEmpty())) {
|
|
|
|
|
while (line < qMax(newLeftLines.size(), newRightLines.size())) {
|
2014-02-13 16:43:28 +01:00
|
|
|
handleLine(newLeftLines, line, &leftLines, &leftLineNumber);
|
|
|
|
|
handleLine(newRightLines, line, &rightLines, &rightLineNumber);
|
|
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
const int commonLineCount = qMin(newLeftLines.size(), newRightLines.size());
|
2014-02-13 16:43:28 +01:00
|
|
|
if (line < commonLineCount) {
|
|
|
|
|
// try to align
|
|
|
|
|
const int leftDifference = leftLineNumber - leftLineAligned;
|
|
|
|
|
const int rightDifference = rightLineNumber - rightLineAligned;
|
|
|
|
|
|
|
|
|
|
if (leftDifference && rightDifference) {
|
|
|
|
|
bool doAlign = true;
|
|
|
|
|
if (line == 0
|
|
|
|
|
&& (newLeftLines.at(0).isEmpty()
|
|
|
|
|
|| newRightLines.at(0).isEmpty())
|
|
|
|
|
&& !lastLineEqual) {
|
|
|
|
|
// omit alignment when first lines of equalities
|
|
|
|
|
// are empty and last generated lines are not equal
|
2014-02-28 10:40:20 +01:00
|
|
|
doAlign = false;
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (line == commonLineCount - 1) {
|
|
|
|
|
// omit alignment when last lines of equalities are empty
|
|
|
|
|
if (leftLines.last().text.isEmpty()
|
|
|
|
|
|| rightLines.last().text.isEmpty())
|
|
|
|
|
doAlign = false;
|
|
|
|
|
|
|
|
|
|
// unless it's the last dummy line (don't omit in that case)
|
2020-09-07 15:41:01 +02:00
|
|
|
if (i == leftDiffList.size() && j == rightDiffList.size())
|
2014-02-13 16:43:28 +01:00
|
|
|
doAlign = true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (doAlign) {
|
|
|
|
|
// align here
|
|
|
|
|
leftLineAligned = leftLineNumber;
|
|
|
|
|
rightLineAligned = rightLineNumber;
|
|
|
|
|
|
|
|
|
|
// insert separators if needed
|
|
|
|
|
if (rightDifference > leftDifference)
|
|
|
|
|
leftSpans.insert(leftLineNumber,
|
|
|
|
|
rightDifference - leftDifference);
|
|
|
|
|
else if (leftDifference > rightDifference)
|
|
|
|
|
rightSpans.insert(rightLineNumber,
|
|
|
|
|
leftDifference - rightDifference);
|
|
|
|
|
}
|
2014-02-28 10:40:20 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-13 16:43:28 +01:00
|
|
|
// check if lines are equal
|
2020-09-07 15:41:01 +02:00
|
|
|
if ((line < commonLineCount - 1) // before the last common line in equality
|
|
|
|
|
|| (line == commonLineCount - 1 // or the last common line in equality
|
|
|
|
|
&& i == leftDiffList.size() // and it's the last iteration
|
|
|
|
|
&& j == rightDiffList.size())) {
|
2014-02-13 16:43:28 +01:00
|
|
|
if (line > 0 || lastLineEqual)
|
|
|
|
|
equalLines.insert(leftLineNumber, rightLineNumber);
|
|
|
|
|
}
|
2014-02-28 10:40:20 +01:00
|
|
|
|
2014-02-13 16:43:28 +01:00
|
|
|
if (line > 0)
|
|
|
|
|
lastLineEqual = true;
|
2014-02-28 10:40:20 +01:00
|
|
|
|
2014-02-13 16:43:28 +01:00
|
|
|
line++;
|
|
|
|
|
}
|
2014-02-28 10:40:20 +01:00
|
|
|
}
|
|
|
|
|
i++;
|
|
|
|
|
j++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<TextLineData> leftData = assemblyRows(leftLines,
|
|
|
|
|
leftSpans);
|
|
|
|
|
QList<TextLineData> rightData = assemblyRows(rightLines,
|
|
|
|
|
rightSpans);
|
|
|
|
|
|
|
|
|
|
// fill ending separators
|
2020-09-07 15:41:01 +02:00
|
|
|
for (int i = leftData.size(); i < rightData.size(); i++)
|
2014-02-28 10:40:20 +01:00
|
|
|
leftData.append(TextLineData(TextLineData::Separator));
|
2020-09-07 15:41:01 +02:00
|
|
|
for (int i = rightData.size(); i < leftData.size(); i++)
|
2014-02-28 10:40:20 +01:00
|
|
|
rightData.append(TextLineData(TextLineData::Separator));
|
|
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
const int visualLineCount = leftData.size();
|
2014-02-28 10:40:20 +01:00
|
|
|
int leftLine = -1;
|
|
|
|
|
int rightLine = -1;
|
2014-02-13 16:43:28 +01:00
|
|
|
ChunkData chunkData;
|
|
|
|
|
|
2014-02-28 10:40:20 +01:00
|
|
|
for (int i = 0; i < visualLineCount; i++) {
|
|
|
|
|
const TextLineData &leftTextLine = leftData.at(i);
|
|
|
|
|
const TextLineData &rightTextLine = rightData.at(i);
|
|
|
|
|
RowData row(leftTextLine, rightTextLine);
|
|
|
|
|
|
|
|
|
|
if (leftTextLine.textLineType == TextLineData::TextLine)
|
|
|
|
|
++leftLine;
|
|
|
|
|
if (rightTextLine.textLineType == TextLineData::TextLine)
|
|
|
|
|
++rightLine;
|
2014-03-10 12:57:48 +01:00
|
|
|
if (equalLines.value(leftLine, -2) == rightLine)
|
2014-02-28 10:40:20 +01:00
|
|
|
row.equal = true;
|
|
|
|
|
|
|
|
|
|
chunkData.rows.append(row);
|
|
|
|
|
}
|
|
|
|
|
return chunkData;
|
|
|
|
|
}
|
|
|
|
|
|
2015-01-30 16:59:25 +01:00
|
|
|
FileData DiffUtils::calculateContextData(const ChunkData &originalData, int contextLineCount,
|
2014-02-13 16:43:28 +01:00
|
|
|
int joinChunkThreshold)
|
2014-02-28 10:40:20 +01:00
|
|
|
{
|
2015-01-30 16:59:25 +01:00
|
|
|
if (contextLineCount < 0)
|
2014-02-28 10:40:20 +01:00
|
|
|
return FileData(originalData);
|
|
|
|
|
|
|
|
|
|
FileData fileData;
|
2014-02-13 16:43:28 +01:00
|
|
|
fileData.contextChunksIncluded = true;
|
2014-07-09 12:37:47 +02:00
|
|
|
fileData.lastChunkAtTheEndOfFile = true;
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2014-02-28 10:40:20 +01:00
|
|
|
QMap<int, bool> hiddenRows;
|
|
|
|
|
int i = 0;
|
2020-09-07 15:41:01 +02:00
|
|
|
while (i < originalData.rows.size()) {
|
2014-02-28 10:40:20 +01:00
|
|
|
const RowData &row = originalData.rows[i];
|
|
|
|
|
if (row.equal) {
|
|
|
|
|
// count how many equal
|
|
|
|
|
int equalRowStart = i;
|
|
|
|
|
i++;
|
2020-09-07 15:41:01 +02:00
|
|
|
while (i < originalData.rows.size()) {
|
2014-02-28 10:40:20 +01:00
|
|
|
const RowData originalRow = originalData.rows.at(i);
|
|
|
|
|
if (!originalRow.equal)
|
|
|
|
|
break;
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
const bool first = equalRowStart == 0; // includes first line?
|
2020-09-07 15:41:01 +02:00
|
|
|
const bool last = i == originalData.rows.size(); // includes last line?
|
2014-02-28 10:40:20 +01:00
|
|
|
|
2014-02-13 16:43:28 +01:00
|
|
|
const int firstLine = first
|
2015-01-30 16:59:25 +01:00
|
|
|
? 0 : equalRowStart + contextLineCount;
|
2020-09-07 15:41:01 +02:00
|
|
|
const int lastLine = last ? originalData.rows.size() : i - contextLineCount;
|
2014-02-28 10:40:20 +01:00
|
|
|
|
|
|
|
|
if (firstLine < lastLine - joinChunkThreshold) {
|
|
|
|
|
for (int j = firstLine; j < lastLine; j++) {
|
|
|
|
|
hiddenRows.insert(j, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// iterate to the next row
|
|
|
|
|
i++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
i = 0;
|
2014-02-13 16:43:28 +01:00
|
|
|
int leftLineNumber = 0;
|
|
|
|
|
int rightLineNumber = 0;
|
2020-09-07 15:41:01 +02:00
|
|
|
while (i < originalData.rows.size()) {
|
2014-03-11 15:31:19 +01:00
|
|
|
const bool contextChunk = hiddenRows.contains(i);
|
|
|
|
|
ChunkData chunkData;
|
|
|
|
|
chunkData.contextChunk = contextChunk;
|
2014-02-13 16:43:28 +01:00
|
|
|
chunkData.leftStartingLineNumber = leftLineNumber;
|
|
|
|
|
chunkData.rightStartingLineNumber = rightLineNumber;
|
2020-09-07 15:41:01 +02:00
|
|
|
while (i < originalData.rows.size()) {
|
2014-03-11 15:31:19 +01:00
|
|
|
if (contextChunk != hiddenRows.contains(i))
|
|
|
|
|
break;
|
|
|
|
|
RowData rowData = originalData.rows.at(i);
|
|
|
|
|
chunkData.rows.append(rowData);
|
2014-02-13 16:43:28 +01:00
|
|
|
if (rowData.leftLine.textLineType == TextLineData::TextLine)
|
|
|
|
|
++leftLineNumber;
|
|
|
|
|
if (rowData.rightLine.textLineType == TextLineData::TextLine)
|
|
|
|
|
++rightLineNumber;
|
2014-03-11 15:31:19 +01:00
|
|
|
++i;
|
2014-02-28 10:40:20 +01:00
|
|
|
}
|
2014-03-11 15:31:19 +01:00
|
|
|
fileData.chunks.append(chunkData);
|
2014-02-28 10:40:20 +01:00
|
|
|
}
|
2014-03-11 15:31:19 +01:00
|
|
|
|
2014-02-28 10:40:20 +01:00
|
|
|
return fileData;
|
|
|
|
|
}
|
|
|
|
|
|
2014-02-13 16:43:28 +01:00
|
|
|
QString DiffUtils::makePatchLine(const QChar &startLineCharacter,
|
|
|
|
|
const QString &textLine,
|
|
|
|
|
bool lastChunk,
|
|
|
|
|
bool lastLine)
|
|
|
|
|
{
|
|
|
|
|
QString line;
|
|
|
|
|
|
|
|
|
|
const bool addNoNewline = lastChunk // it's the last chunk in file
|
|
|
|
|
&& lastLine // it's the last row in chunk
|
|
|
|
|
&& !textLine.isEmpty(); // the row is not empty
|
|
|
|
|
|
|
|
|
|
const bool addLine = !lastChunk // not the last chunk in file
|
|
|
|
|
|| !lastLine // not the last row in chunk
|
|
|
|
|
|| addNoNewline; // no addNoNewline case
|
|
|
|
|
|
|
|
|
|
if (addLine) {
|
2017-12-06 21:30:57 +01:00
|
|
|
line = startLineCharacter + textLine + '\n';
|
2014-02-13 16:43:28 +01:00
|
|
|
if (addNoNewline)
|
2017-12-06 21:30:57 +01:00
|
|
|
line += "\\ No newline at end of file\n";
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return line;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString DiffUtils::makePatch(const ChunkData &chunkData,
|
2014-06-30 15:04:36 +02:00
|
|
|
bool lastChunk)
|
2014-02-13 16:43:28 +01:00
|
|
|
{
|
2016-11-24 11:10:00 +01:00
|
|
|
if (chunkData.contextChunk)
|
|
|
|
|
return QString();
|
|
|
|
|
|
2014-02-13 16:43:28 +01:00
|
|
|
QString diffText;
|
|
|
|
|
int leftLineCount = 0;
|
|
|
|
|
int rightLineCount = 0;
|
|
|
|
|
QList<TextLineData> leftBuffer, rightBuffer;
|
|
|
|
|
|
2014-08-06 15:03:26 +03:00
|
|
|
int rowToBeSplit = -1;
|
|
|
|
|
|
2014-07-09 12:37:47 +02:00
|
|
|
if (lastChunk) {
|
2014-08-06 15:03:26 +03:00
|
|
|
// Detect the case when the last equal line is followed by
|
|
|
|
|
// only separators on left or on right. In that case
|
|
|
|
|
// the last equal line needs to be split.
|
2020-09-07 15:41:01 +02:00
|
|
|
const int rowCount = chunkData.rows.size();
|
2014-08-06 15:03:26 +03:00
|
|
|
int i = 0;
|
|
|
|
|
for (i = rowCount; i > 0; i--) {
|
|
|
|
|
const RowData &rowData = chunkData.rows.at(i - 1);
|
|
|
|
|
if (rowData.leftLine.textLineType != TextLineData::Separator
|
|
|
|
|
|| rowData.rightLine.textLineType != TextLineData::TextLine)
|
2014-07-09 12:37:47 +02:00
|
|
|
break;
|
|
|
|
|
}
|
2014-08-06 15:03:26 +03:00
|
|
|
const int leftSeparator = i;
|
|
|
|
|
for (i = rowCount; i > 0; i--) {
|
|
|
|
|
const RowData &rowData = chunkData.rows.at(i - 1);
|
|
|
|
|
if (rowData.rightLine.textLineType != TextLineData::Separator
|
|
|
|
|
|| rowData.leftLine.textLineType != TextLineData::TextLine)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
const int rightSeparator = i;
|
|
|
|
|
const int commonSeparator = qMin(leftSeparator, rightSeparator);
|
|
|
|
|
if (commonSeparator > 0
|
|
|
|
|
&& commonSeparator < rowCount
|
|
|
|
|
&& chunkData.rows.at(commonSeparator - 1).equal)
|
|
|
|
|
rowToBeSplit = commonSeparator - 1;
|
2014-07-09 12:37:47 +02:00
|
|
|
}
|
|
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
for (int i = 0; i <= chunkData.rows.size(); i++) {
|
|
|
|
|
const RowData &rowData = i < chunkData.rows.size()
|
|
|
|
|
? chunkData.rows.at(i)
|
|
|
|
|
: RowData(TextLineData(TextLineData::Separator)); // dummy,
|
|
|
|
|
// ensure we process buffers to the end.
|
|
|
|
|
// rowData will be equal
|
2014-08-06 15:03:26 +03:00
|
|
|
if (rowData.equal && i != rowToBeSplit) {
|
2020-01-15 19:10:34 +01:00
|
|
|
if (!leftBuffer.isEmpty()) {
|
2020-09-07 15:41:01 +02:00
|
|
|
for (int j = 0; j < leftBuffer.size(); j++) {
|
2017-12-06 21:30:57 +01:00
|
|
|
const QString line = makePatchLine('-',
|
2020-09-07 15:41:01 +02:00
|
|
|
leftBuffer.at(j).text,
|
|
|
|
|
lastChunk,
|
|
|
|
|
i == chunkData.rows.size()
|
|
|
|
|
&& j == leftBuffer.size() - 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
if (!line.isEmpty())
|
|
|
|
|
++leftLineCount;
|
|
|
|
|
|
|
|
|
|
diffText += line;
|
|
|
|
|
}
|
|
|
|
|
leftBuffer.clear();
|
|
|
|
|
}
|
2020-01-15 19:10:34 +01:00
|
|
|
if (!rightBuffer.isEmpty()) {
|
2020-09-07 15:41:01 +02:00
|
|
|
for (int j = 0; j < rightBuffer.size(); j++) {
|
2017-12-06 21:30:57 +01:00
|
|
|
const QString line = makePatchLine('+',
|
2020-09-07 15:41:01 +02:00
|
|
|
rightBuffer.at(j).text,
|
|
|
|
|
lastChunk,
|
|
|
|
|
i == chunkData.rows.size()
|
|
|
|
|
&& j == rightBuffer.size() - 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
if (!line.isEmpty())
|
|
|
|
|
++rightLineCount;
|
|
|
|
|
|
|
|
|
|
diffText += line;
|
|
|
|
|
}
|
|
|
|
|
rightBuffer.clear();
|
|
|
|
|
}
|
2020-09-07 15:41:01 +02:00
|
|
|
if (i < chunkData.rows.size()) {
|
2017-12-06 21:30:57 +01:00
|
|
|
const QString line = makePatchLine(' ',
|
2020-09-07 15:41:01 +02:00
|
|
|
rowData.rightLine.text,
|
|
|
|
|
lastChunk,
|
|
|
|
|
i == chunkData.rows.size() - 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
if (!line.isEmpty()) {
|
|
|
|
|
++leftLineCount;
|
|
|
|
|
++rightLineCount;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
diffText += line;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (rowData.leftLine.textLineType == TextLineData::TextLine)
|
|
|
|
|
leftBuffer.append(rowData.leftLine);
|
|
|
|
|
if (rowData.rightLine.textLineType == TextLineData::TextLine)
|
|
|
|
|
rightBuffer.append(rowData.rightLine);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-06 21:30:57 +01:00
|
|
|
const QString chunkLine = "@@ -"
|
2014-02-13 16:43:28 +01:00
|
|
|
+ QString::number(chunkData.leftStartingLineNumber + 1)
|
2017-12-06 21:30:57 +01:00
|
|
|
+ ','
|
2014-02-13 16:43:28 +01:00
|
|
|
+ QString::number(leftLineCount)
|
2017-12-06 21:30:57 +01:00
|
|
|
+ " +"
|
2014-02-13 16:43:28 +01:00
|
|
|
+ QString::number(chunkData.rightStartingLineNumber + 1)
|
2017-12-06 21:30:57 +01:00
|
|
|
+ ','
|
2014-02-13 16:43:28 +01:00
|
|
|
+ QString::number(rightLineCount)
|
2017-12-06 21:30:57 +01:00
|
|
|
+ " @@"
|
2014-08-14 17:20:42 +02:00
|
|
|
+ chunkData.contextInfo
|
2017-12-06 21:30:57 +01:00
|
|
|
+ '\n';
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
diffText.prepend(chunkLine);
|
|
|
|
|
|
2014-06-30 15:04:36 +02:00
|
|
|
return diffText;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString DiffUtils::makePatch(const ChunkData &chunkData,
|
|
|
|
|
const QString &leftFileName,
|
|
|
|
|
const QString &rightFileName,
|
|
|
|
|
bool lastChunk)
|
|
|
|
|
{
|
|
|
|
|
QString diffText = makePatch(chunkData, lastChunk);
|
|
|
|
|
|
2017-12-06 21:30:57 +01:00
|
|
|
const QString rightFileInfo = "+++ " + rightFileName + '\n';
|
|
|
|
|
const QString leftFileInfo = "--- " + leftFileName + '\n';
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
diffText.prepend(rightFileInfo);
|
|
|
|
|
diffText.prepend(leftFileInfo);
|
|
|
|
|
|
|
|
|
|
return diffText;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-11 09:44:35 +02:00
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2014-11-11 16:27:23 +01:00
|
|
|
QString DiffUtils::makePatch(const QList<FileData> &fileDataList, unsigned formatFlags)
|
2014-06-30 15:04:36 +02:00
|
|
|
{
|
|
|
|
|
QString diffText;
|
2014-11-11 16:27:23 +01:00
|
|
|
QTextStream str(&diffText);
|
2014-06-30 15:04:36 +02:00
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
for (int i = 0; i < fileDataList.size(); i++) {
|
2014-06-30 15:04:36 +02:00
|
|
|
const FileData &fileData = fileDataList.at(i);
|
2014-11-11 16:27:23 +01:00
|
|
|
if (formatFlags & GitFormat) {
|
|
|
|
|
str << "diff --git a/" << fileData.leftFileInfo.fileName
|
|
|
|
|
<< " b/" << fileData.rightFileInfo.fileName << '\n';
|
|
|
|
|
}
|
2017-07-11 09:44:35 +02:00
|
|
|
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";
|
|
|
|
|
|
2014-06-30 15:04:36 +02:00
|
|
|
if (fileData.binaryFiles) {
|
2014-11-11 16:27:23 +01:00
|
|
|
str << "Binary files ";
|
2017-07-11 09:44:35 +02:00
|
|
|
str << leftFileName(fileData, formatFlags);
|
|
|
|
|
str << " and ";
|
|
|
|
|
str << rightFileName(fileData, formatFlags);
|
|
|
|
|
str << " differ\n";
|
2014-06-30 15:04:36 +02:00
|
|
|
} else {
|
2017-07-11 09:44:35 +02:00
|
|
|
if (!fileData.chunks.isEmpty()) {
|
|
|
|
|
str << "--- ";
|
|
|
|
|
str << leftFileName(fileData, formatFlags) << "\n";
|
|
|
|
|
str << "+++ ";
|
|
|
|
|
str << rightFileName(fileData, formatFlags) << "\n";
|
2020-09-07 15:41:01 +02:00
|
|
|
for (int j = 0; j < fileData.chunks.size(); j++) {
|
2017-07-11 09:44:35 +02:00
|
|
|
str << makePatch(fileData.chunks.at(j),
|
2020-09-07 15:41:01 +02:00
|
|
|
(j == fileData.chunks.size() - 1)
|
|
|
|
|
&& fileData.lastChunkAtTheEndOfFile);
|
2017-07-11 09:44:35 +02:00
|
|
|
}
|
2014-06-30 15:04:36 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return diffText;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static QList<RowData> readLines(QStringView patch, bool lastChunk, bool *lastChunkAtTheEndOfFile, bool *ok)
|
2014-02-13 16:43:28 +01:00
|
|
|
{
|
|
|
|
|
QList<Diff> diffList;
|
|
|
|
|
|
2017-12-06 21:30:57 +01:00
|
|
|
const QChar newLine = '\n';
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
int lastEqual = -1;
|
|
|
|
|
int lastDelete = -1;
|
|
|
|
|
int lastInsert = -1;
|
|
|
|
|
|
|
|
|
|
int noNewLineInEqual = -1;
|
|
|
|
|
int noNewLineInDelete = -1;
|
|
|
|
|
int noNewLineInInsert = -1;
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
const QVector<QStringView> lines = patch.split(newLine);
|
2014-02-13 16:43:28 +01:00
|
|
|
int i;
|
2020-09-07 15:41:01 +02:00
|
|
|
for (i = 0; i < lines.size(); i++) {
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView line = lines.at(i);
|
2014-11-06 13:23:40 +01:00
|
|
|
if (line.isEmpty()) { // need to have at least one character (1 column)
|
|
|
|
|
if (lastChunk)
|
2020-09-07 15:41:01 +02:00
|
|
|
i = lines.size(); // pretend as we've read all the lines (we just ignore the rest)
|
2014-11-06 13:23:40 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2017-06-16 13:45:43 +02:00
|
|
|
const QChar firstCharacter = line.at(0);
|
2017-12-06 21:30:57 +01:00
|
|
|
if (firstCharacter == '\\') { // no new line marker
|
2014-02-13 16:43:28 +01:00
|
|
|
if (!lastChunk) // can only appear in last chunk of the file
|
|
|
|
|
break;
|
|
|
|
|
if (!diffList.isEmpty()) {
|
|
|
|
|
Diff &last = diffList.last();
|
|
|
|
|
if (last.text.isEmpty())
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
if (last.command == Diff::Equal) {
|
|
|
|
|
if (noNewLineInEqual >= 0)
|
|
|
|
|
break;
|
2020-09-07 15:41:01 +02:00
|
|
|
noNewLineInEqual = diffList.size() - 1;
|
2014-02-13 16:43:28 +01:00
|
|
|
} else if (last.command == Diff::Delete) {
|
|
|
|
|
if (noNewLineInDelete >= 0)
|
|
|
|
|
break;
|
2020-09-07 15:41:01 +02:00
|
|
|
noNewLineInDelete = diffList.size() - 1;
|
2014-02-13 16:43:28 +01:00
|
|
|
} else if (last.command == Diff::Insert) {
|
|
|
|
|
if (noNewLineInInsert >= 0)
|
|
|
|
|
break;
|
2020-09-07 15:41:01 +02:00
|
|
|
noNewLineInInsert = diffList.size() - 1;
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
Diff::Command command = Diff::Equal;
|
2017-12-06 21:30:57 +01:00
|
|
|
if (firstCharacter == ' ') { // common line
|
2014-02-13 16:43:28 +01:00
|
|
|
command = Diff::Equal;
|
2017-12-06 21:30:57 +01:00
|
|
|
} else if (firstCharacter == '-') { // deleted line
|
2014-02-13 16:43:28 +01:00
|
|
|
command = Diff::Delete;
|
2017-12-06 21:30:57 +01:00
|
|
|
} else if (firstCharacter == '+') { // inserted line
|
2014-02-13 16:43:28 +01:00
|
|
|
command = Diff::Insert;
|
2014-11-06 13:23:40 +01:00
|
|
|
} else { // no other character may exist as the first character
|
|
|
|
|
if (lastChunk)
|
2020-09-07 15:41:01 +02:00
|
|
|
i = lines.size(); // pretend as we've read all the lines (we just ignore the rest)
|
2014-11-06 13:23:40 +01:00
|
|
|
break;
|
|
|
|
|
}
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-06-28 16:08:45 +02:00
|
|
|
Diff diffToBeAdded(command, line.mid(1).toString() + newLine);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
if (!diffList.isEmpty() && diffList.last().command == command)
|
|
|
|
|
diffList.last().text.append(diffToBeAdded.text);
|
|
|
|
|
else
|
|
|
|
|
diffList.append(diffToBeAdded);
|
|
|
|
|
|
|
|
|
|
if (command == Diff::Equal) // common line
|
2020-09-07 15:41:01 +02:00
|
|
|
lastEqual = diffList.size() - 1;
|
2014-02-13 16:43:28 +01:00
|
|
|
else if (command == Diff::Delete) // deleted line
|
2020-09-07 15:41:01 +02:00
|
|
|
lastDelete = diffList.size() - 1;
|
2014-02-13 16:43:28 +01:00
|
|
|
else if (command == Diff::Insert) // inserted line
|
2020-09-07 15:41:01 +02:00
|
|
|
lastInsert = diffList.size() - 1;
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
if (i < lines.size() // we broke before
|
|
|
|
|
// or we have noNewLine in some equal line and in either delete or insert line
|
|
|
|
|
|| (noNewLineInEqual >= 0 && (noNewLineInDelete >= 0 || noNewLineInInsert >= 0))
|
|
|
|
|
// or we have noNewLine in not the last equal line
|
|
|
|
|
|| (noNewLineInEqual >= 0 && noNewLineInEqual != lastEqual)
|
|
|
|
|
// or we have noNewLine in not the last delete line or there is a equal line after the noNewLine for delete
|
|
|
|
|
|| (noNewLineInDelete >= 0 && (noNewLineInDelete != lastDelete || lastEqual > lastDelete))
|
|
|
|
|
// or we have noNewLine in not the last insert line or there is a equal line after the noNewLine for insert
|
|
|
|
|
|| (noNewLineInInsert >= 0 && (noNewLineInInsert != lastInsert || lastEqual > lastInsert))) {
|
2014-02-13 16:43:28 +01:00
|
|
|
if (ok)
|
|
|
|
|
*ok = false;
|
|
|
|
|
return QList<RowData>();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = true;
|
|
|
|
|
|
|
|
|
|
bool removeNewLineFromLastEqual = false;
|
|
|
|
|
bool removeNewLineFromLastDelete = false;
|
|
|
|
|
bool removeNewLineFromLastInsert = false;
|
|
|
|
|
bool prependNewLineAfterLastEqual = false;
|
|
|
|
|
|
|
|
|
|
if (noNewLineInDelete >= 0 || noNewLineInInsert >= 0) {
|
|
|
|
|
if (noNewLineInDelete >= 0)
|
|
|
|
|
removeNewLineFromLastDelete = true;
|
|
|
|
|
if (noNewLineInInsert >= 0)
|
|
|
|
|
removeNewLineFromLastInsert = true;
|
2014-07-09 12:37:47 +02:00
|
|
|
} else {
|
|
|
|
|
if (noNewLineInEqual >= 0) {
|
2014-02-13 16:43:28 +01:00
|
|
|
removeNewLineFromLastEqual = true;
|
2014-07-28 12:17:31 +03:00
|
|
|
} else {
|
2014-07-09 12:37:47 +02:00
|
|
|
if (lastEqual > lastDelete && lastEqual > lastInsert) {
|
|
|
|
|
removeNewLineFromLastEqual = true;
|
|
|
|
|
} else if (lastDelete > lastEqual && lastDelete > lastInsert) {
|
|
|
|
|
if (lastInsert > lastEqual) {
|
|
|
|
|
removeNewLineFromLastDelete = true;
|
|
|
|
|
removeNewLineFromLastInsert = true;
|
|
|
|
|
} else if (lastEqual > lastInsert) {
|
|
|
|
|
removeNewLineFromLastEqual = true;
|
|
|
|
|
removeNewLineFromLastDelete = true;
|
|
|
|
|
prependNewLineAfterLastEqual = true;
|
|
|
|
|
}
|
|
|
|
|
} else if (lastInsert > lastEqual && lastInsert > lastDelete) {
|
|
|
|
|
if (lastDelete > lastEqual) {
|
|
|
|
|
removeNewLineFromLastDelete = true;
|
|
|
|
|
removeNewLineFromLastInsert = true;
|
|
|
|
|
} else if (lastEqual > lastDelete) {
|
|
|
|
|
removeNewLineFromLastEqual = true;
|
|
|
|
|
removeNewLineFromLastInsert = true;
|
|
|
|
|
prependNewLineAfterLastEqual = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2014-07-09 12:37:47 +02:00
|
|
|
|
2014-02-13 16:43:28 +01:00
|
|
|
if (removeNewLineFromLastEqual) {
|
|
|
|
|
Diff &diff = diffList[lastEqual];
|
2020-09-07 15:41:01 +02:00
|
|
|
diff.text = diff.text.left(diff.text.size() - 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
if (removeNewLineFromLastDelete) {
|
|
|
|
|
Diff &diff = diffList[lastDelete];
|
2020-09-07 15:41:01 +02:00
|
|
|
diff.text = diff.text.left(diff.text.size() - 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
if (removeNewLineFromLastInsert) {
|
|
|
|
|
Diff &diff = diffList[lastInsert];
|
2020-09-07 15:41:01 +02:00
|
|
|
diff.text = diff.text.left(diff.text.size() - 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
if (prependNewLineAfterLastEqual) {
|
|
|
|
|
Diff &diff = diffList[lastEqual + 1];
|
|
|
|
|
diff.text = newLine + diff.text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (lastChunkAtTheEndOfFile) {
|
|
|
|
|
*lastChunkAtTheEndOfFile = noNewLineInEqual >= 0
|
|
|
|
|
|| noNewLineInDelete >= 0|| noNewLineInInsert >= 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// diffList = Differ::merge(diffList);
|
|
|
|
|
QList<Diff> leftDiffList;
|
|
|
|
|
QList<Diff> rightDiffList;
|
|
|
|
|
Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList);
|
|
|
|
|
QList<Diff> outputLeftDiffList;
|
|
|
|
|
QList<Diff> outputRightDiffList;
|
|
|
|
|
|
2014-10-24 14:11:12 +02:00
|
|
|
Differ::diffBetweenEqualities(leftDiffList,
|
|
|
|
|
rightDiffList,
|
|
|
|
|
&outputLeftDiffList,
|
|
|
|
|
&outputRightDiffList);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
return DiffUtils::calculateOriginalData(outputLeftDiffList,
|
|
|
|
|
outputRightDiffList).rows;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static QStringView readLine(QStringView text, QStringView *remainingText, bool *hasNewLine)
|
2017-07-13 14:52:55 +02:00
|
|
|
{
|
|
|
|
|
const QChar newLine('\n');
|
|
|
|
|
const int indexOfFirstNewLine = text.indexOf(newLine);
|
|
|
|
|
if (indexOfFirstNewLine < 0) {
|
|
|
|
|
if (remainingText)
|
2022-07-13 09:27:18 +02:00
|
|
|
*remainingText = QStringView();
|
2017-07-13 14:52:55 +02:00
|
|
|
if (hasNewLine)
|
|
|
|
|
*hasNewLine = false;
|
|
|
|
|
return text;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (hasNewLine)
|
|
|
|
|
*hasNewLine = true;
|
|
|
|
|
|
|
|
|
|
if (remainingText)
|
|
|
|
|
*remainingText = text.mid(indexOfFirstNewLine + 1);
|
|
|
|
|
|
|
|
|
|
return text.left(indexOfFirstNewLine);
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static bool detectChunkData(QStringView chunkDiff, ChunkData *chunkData, QStringView *remainingPatch)
|
2017-07-13 14:52:55 +02:00
|
|
|
{
|
|
|
|
|
bool hasNewLine;
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView chunkLine = readLine(chunkDiff, remainingPatch, &hasNewLine);
|
2017-07-13 14:52:55 +02:00
|
|
|
|
|
|
|
|
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;
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView leftPos = chunkLine.mid(leftPosStart, leftPosLength);
|
2017-07-13 14:52:55 +02:00
|
|
|
|
|
|
|
|
const int rightPosStart = rightPosIndex + rightPosMarker.size();
|
|
|
|
|
const int rightPosLength = optionalHintIndex - rightPosStart;
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView rightPos = chunkLine.mid(rightPosStart, rightPosLength);
|
2017-07-13 14:52:55 +02:00
|
|
|
|
|
|
|
|
const int optionalHintStart = optionalHintIndex + optionalHintMarker.size();
|
|
|
|
|
const int optionalHintLength = chunkLine.size() - optionalHintStart;
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView optionalHint = chunkLine.mid(optionalHintStart, optionalHintLength);
|
2017-07-13 14:52:55 +02:00
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static QList<ChunkData> readChunks(QStringView patch, bool *lastChunkAtTheEndOfFile, bool *ok)
|
2014-02-13 16:43:28 +01:00
|
|
|
{
|
2017-07-13 14:52:55 +02:00
|
|
|
QList<ChunkData> chunkDataList;
|
|
|
|
|
int position = -1;
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
QVector<int> startingPositions; // store starting positions of @@
|
|
|
|
|
if (patch.startsWith(QStringLiteral("@@ -")))
|
|
|
|
|
startingPositions.append(position + 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
while ((position = patch.indexOf(QStringLiteral("\n@@ -"), position + 1)) >= 0)
|
|
|
|
|
startingPositions.append(position + 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
const QChar newLine('\n');
|
|
|
|
|
bool readOk = true;
|
|
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
const int count = startingPositions.size();
|
2017-07-13 14:52:55 +02:00
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
|
const int chunkStart = startingPositions.at(i);
|
|
|
|
|
const int chunkEnd = (i < count - 1)
|
2020-09-07 15:41:01 +02:00
|
|
|
// 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.size() - 1) == newLine ? patch.size() - 1
|
|
|
|
|
: patch.size());
|
2017-07-13 14:52:55 +02:00
|
|
|
|
|
|
|
|
// extract just one chunk
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView chunkDiff = patch.mid(chunkStart, chunkEnd - chunkStart);
|
2017-07-13 14:52:55 +02:00
|
|
|
|
|
|
|
|
ChunkData chunkData;
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView lines;
|
2017-07-13 14:52:55 +02:00
|
|
|
readOk = detectChunkData(chunkDiff, &chunkData, &lines);
|
|
|
|
|
|
|
|
|
|
if (!readOk)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
chunkData.rows = readLines(lines, i == (startingPositions.size() - 1),
|
|
|
|
|
lastChunkAtTheEndOfFile, &readOk);
|
|
|
|
|
if (!readOk)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
chunkDataList.append(chunkData);
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = readOk;
|
|
|
|
|
|
|
|
|
|
return chunkDataList;
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static FileData readDiffHeaderAndChunks(QStringView headerAndChunks, bool *ok)
|
2014-02-13 16:43:28 +01:00
|
|
|
{
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView patch = headerAndChunks;
|
2014-02-13 16:43:28 +01:00
|
|
|
FileData fileData;
|
|
|
|
|
bool readOk = false;
|
|
|
|
|
|
2017-06-16 13:45:43 +02:00
|
|
|
const QRegularExpression leftFileRegExp(
|
|
|
|
|
"(?:\\n|^)-{3} " // "--- "
|
|
|
|
|
"([^\\t\\n]+)" // "fileName1"
|
|
|
|
|
"(?:\\t[^\\n]*)*\\n"); // optionally followed by: \t anything \t anything ...)
|
|
|
|
|
const QRegularExpression rightFileRegExp(
|
|
|
|
|
"^\\+{3} " // "+++ "
|
|
|
|
|
"([^\\t\\n]+)" // "fileName2"
|
|
|
|
|
"(?:\\t[^\\n]*)*\\n"); // optionally followed by: \t anything \t anything ...)
|
|
|
|
|
const QRegularExpression binaryRegExp(
|
|
|
|
|
"^Binary files ([^\\t\\n]+) and ([^\\t\\n]+) differ$");
|
|
|
|
|
|
|
|
|
|
// followed either by leftFileRegExp
|
|
|
|
|
const QRegularExpressionMatch leftMatch = leftFileRegExp.match(patch);
|
|
|
|
|
if (leftMatch.hasMatch() && leftMatch.capturedStart() == 0) {
|
|
|
|
|
patch = patch.mid(leftMatch.capturedEnd());
|
|
|
|
|
fileData.leftFileInfo.fileName = leftMatch.captured(1);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
// followed by rightFileRegExp
|
2017-06-16 13:45:43 +02:00
|
|
|
const QRegularExpressionMatch rightMatch = rightFileRegExp.match(patch);
|
|
|
|
|
if (rightMatch.hasMatch() && rightMatch.capturedStart() == 0) {
|
|
|
|
|
patch = patch.mid(rightMatch.capturedEnd());
|
|
|
|
|
fileData.rightFileInfo.fileName = rightMatch.captured(1);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
fileData.chunks = readChunks(patch,
|
|
|
|
|
&fileData.lastChunkAtTheEndOfFile,
|
|
|
|
|
&readOk);
|
|
|
|
|
}
|
2017-06-16 13:45:43 +02:00
|
|
|
} else {
|
|
|
|
|
// or by binaryRegExp
|
|
|
|
|
const QRegularExpressionMatch binaryMatch = binaryRegExp.match(patch);
|
|
|
|
|
if (binaryMatch.hasMatch() && binaryMatch.capturedStart() == 0) {
|
|
|
|
|
fileData.leftFileInfo.fileName = binaryMatch.captured(1);
|
|
|
|
|
fileData.rightFileInfo.fileName = binaryMatch.captured(2);
|
|
|
|
|
fileData.binaryFiles = true;
|
|
|
|
|
readOk = true;
|
|
|
|
|
}
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = readOk;
|
|
|
|
|
|
|
|
|
|
if (!readOk)
|
|
|
|
|
return FileData();
|
|
|
|
|
|
|
|
|
|
return fileData;
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static QList<FileData> readDiffPatch(QStringView patch, bool *ok, QFutureInterfaceBase *jobController)
|
2014-03-11 15:31:19 +01:00
|
|
|
{
|
2017-06-16 13:45:43 +02:00
|
|
|
const QRegularExpression diffRegExp("(?:\\n|^)" // new line of the beginning of a patch
|
|
|
|
|
"(" // either
|
|
|
|
|
"-{3} " // ---
|
|
|
|
|
"[^\\t\\n]+" // filename1
|
|
|
|
|
"(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ...
|
|
|
|
|
"\\+{3} " // +++
|
|
|
|
|
"[^\\t\\n]+" // filename2
|
|
|
|
|
"(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ...
|
|
|
|
|
"|" // or
|
|
|
|
|
"Binary files "
|
|
|
|
|
"[^\\t\\n]+" // filename1
|
|
|
|
|
" and "
|
|
|
|
|
"[^\\t\\n]+" // filename2
|
|
|
|
|
" differ"
|
|
|
|
|
")"); // end of or
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
bool readOk = false;
|
|
|
|
|
|
|
|
|
|
QList<FileData> fileDataList;
|
|
|
|
|
|
2017-06-16 13:45:43 +02:00
|
|
|
QRegularExpressionMatch diffMatch = diffRegExp.match(patch);
|
|
|
|
|
if (diffMatch.hasMatch()) {
|
2014-02-13 16:43:28 +01:00
|
|
|
readOk = true;
|
|
|
|
|
int lastPos = -1;
|
|
|
|
|
do {
|
2017-06-30 16:06:36 +02:00
|
|
|
if (jobController && jobController->isCanceled())
|
|
|
|
|
return QList<FileData>();
|
|
|
|
|
|
2017-06-16 13:45:43 +02:00
|
|
|
int pos = diffMatch.capturedStart();
|
2014-02-13 16:43:28 +01:00
|
|
|
if (lastPos >= 0) {
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView headerAndChunks = patch.mid(lastPos, pos - lastPos);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
const FileData fileData = readDiffHeaderAndChunks(headerAndChunks,
|
|
|
|
|
&readOk);
|
|
|
|
|
|
|
|
|
|
if (!readOk)
|
|
|
|
|
break;
|
|
|
|
|
|
|
|
|
|
fileDataList.append(fileData);
|
|
|
|
|
}
|
|
|
|
|
lastPos = pos;
|
2017-06-16 13:45:43 +02:00
|
|
|
pos = diffMatch.capturedEnd();
|
|
|
|
|
diffMatch = diffRegExp.match(patch, pos);
|
|
|
|
|
} while (diffMatch.hasMatch());
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-06-16 13:45:43 +02:00
|
|
|
if (readOk) {
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView headerAndChunks = patch.mid(lastPos, patch.size() - lastPos - 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
const FileData fileData = readDiffHeaderAndChunks(headerAndChunks,
|
|
|
|
|
&readOk);
|
|
|
|
|
|
|
|
|
|
if (readOk)
|
|
|
|
|
fileDataList.append(fileData);
|
|
|
|
|
}
|
2014-03-11 15:31:19 +01:00
|
|
|
}
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = readOk;
|
|
|
|
|
|
|
|
|
|
if (!readOk)
|
|
|
|
|
return QList<FileData>();
|
|
|
|
|
|
|
|
|
|
return fileDataList;
|
2014-03-11 15:31:19 +01:00
|
|
|
}
|
|
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
// 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
|
2022-02-06 08:11:09 +02:00
|
|
|
// 2a. old mode [oldFileModeNumber]\n
|
|
|
|
|
// new mode [newFileModeNumber]\n
|
|
|
|
|
// 2b. <Nothing, only in case when no ChangeMode>
|
|
|
|
|
// 3. [dis]similarity index [0-100]%\n
|
2017-07-13 14:52:55 +02:00
|
|
|
// [copy / rename] from [leftFileName]\n
|
|
|
|
|
// [copy / rename] to [rightFileName]
|
2022-02-06 08:11:09 +02:00
|
|
|
// 4a. <Nothing more, only when similarity index was 100%>
|
|
|
|
|
// 4b. index [leftIndexSha]..[rightIndexSha] <optionally: octalNumber>
|
|
|
|
|
// 5. --- [leftFileNameOrDevNull]\n
|
2017-07-13 14:52:55 +02:00
|
|
|
// +++ [rightFileNameOrDevNull]\n
|
|
|
|
|
// <Chunks>
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static bool detectIndexAndBinary(QStringView patch, FileData *fileData, QStringView *remainingPatch)
|
2015-04-23 17:48:32 +02:00
|
|
|
{
|
2017-07-13 14:52:55 +02:00
|
|
|
bool hasNewLine;
|
|
|
|
|
*remainingPatch = patch;
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-11-26 10:24:32 +02:00
|
|
|
if (remainingPatch->isEmpty()) {
|
|
|
|
|
switch (fileData->fileOperation) {
|
|
|
|
|
case FileData::CopyFile:
|
|
|
|
|
case FileData::RenameFile:
|
|
|
|
|
case FileData::ChangeMode:
|
|
|
|
|
// in case of 100% similarity we don't have more lines in the patch
|
|
|
|
|
return true;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView afterNextLine;
|
2017-07-13 14:52:55 +02:00
|
|
|
// index [leftIndexSha]..[rightIndexSha] <optionally: octalNumber>
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView nextLine = readLine(patch, &afterNextLine, &hasNewLine);
|
2017-07-13 14:52:55 +02:00
|
|
|
|
|
|
|
|
const QLatin1String indexHeader("index ");
|
|
|
|
|
|
|
|
|
|
if (nextLine.startsWith(indexHeader)) {
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView indices = nextLine.mid(indexHeader.size());
|
2017-07-13 14:52:55 +02:00
|
|
|
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;
|
|
|
|
|
}
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (remainingPatch->isEmpty() && (fileData->fileOperation == FileData::NewFile
|
|
|
|
|
|| fileData->fileOperation == FileData::DeleteFile)) {
|
|
|
|
|
// OK in case of empty file
|
|
|
|
|
return true;
|
2014-11-02 22:10:15 +02:00
|
|
|
}
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
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;
|
2014-11-02 22:10:15 +02:00
|
|
|
|
2017-12-06 21:30:57 +01:00
|
|
|
const QString binaryLine = "Binary files "
|
|
|
|
|
+ leftFileName + " and "
|
|
|
|
|
+ rightFileName + " differ";
|
2015-04-23 17:48:32 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (*remainingPatch == binaryLine) {
|
|
|
|
|
fileData->binaryFiles = true;
|
2022-07-13 09:27:18 +02:00
|
|
|
*remainingPatch = QStringView();
|
2017-07-13 14:52:55 +02:00
|
|
|
return true;
|
|
|
|
|
}
|
2014-11-02 22:10:15 +02:00
|
|
|
|
2017-12-06 21:30:57 +01:00
|
|
|
const QString leftStart = "--- " + leftFileName;
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView afterMinuses;
|
2017-07-13 14:52:55 +02:00
|
|
|
// --- leftFileName
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView minuses = readLine(*remainingPatch, &afterMinuses, &hasNewLine);
|
2017-07-13 14:52:55 +02:00
|
|
|
if (!hasNewLine)
|
|
|
|
|
return false; // we need to have at least one more line
|
2014-11-02 22:10:15 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (!minuses.startsWith(leftStart))
|
|
|
|
|
return false;
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-12-06 21:30:57 +01:00
|
|
|
const QString rightStart = "+++ " + rightFileName;
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView afterPluses;
|
2017-07-13 14:52:55 +02:00
|
|
|
// +++ rightFileName
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView pluses = readLine(afterMinuses, &afterPluses, &hasNewLine);
|
2017-07-13 14:52:55 +02:00
|
|
|
if (!hasNewLine)
|
|
|
|
|
return false; // we need to have at least one more line
|
2014-03-10 12:57:48 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (!pluses.startsWith(rightStart))
|
|
|
|
|
return false;
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
*remainingPatch = afterPluses;
|
|
|
|
|
return true;
|
2014-02-13 16:43:28 +01:00
|
|
|
}
|
2014-03-10 12:57:48 +01:00
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static bool extractCommonFileName(QStringView fileNames, QStringView *fileName)
|
2014-07-07 14:26:41 +02:00
|
|
|
{
|
2017-07-13 14:52:55 +02:00
|
|
|
// we should have 1 space between filenames
|
|
|
|
|
if (fileNames.size() % 2 == 0)
|
|
|
|
|
return false;
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2020-09-07 15:41:01 +02:00
|
|
|
if (!fileNames.startsWith(QLatin1String("a/")))
|
2017-07-13 14:52:55 +02:00
|
|
|
return false;
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
// drop the space in between
|
|
|
|
|
const int fileNameSize = fileNames.size() / 2;
|
2020-09-07 15:41:01 +02:00
|
|
|
if (!fileNames.mid(fileNameSize).startsWith(QLatin1String(" b/")))
|
2017-07-13 14:52:55 +02:00
|
|
|
return false;
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
// drop "a/"
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView leftFileName = fileNames.mid(2, fileNameSize - 2);
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
// drop the first filename + " b/"
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView rightFileName = fileNames.mid(fileNameSize + 3, fileNameSize - 2);
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (leftFileName != rightFileName)
|
|
|
|
|
return false;
|
2015-04-23 17:48:32 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
*fileName = leftFileName;
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static bool detectFileData(QStringView patch, FileData *fileData, QStringView *remainingPatch)
|
2020-09-07 15:41:01 +02:00
|
|
|
{
|
2017-07-13 14:52:55 +02:00
|
|
|
bool hasNewLine;
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView afterDiffGit;
|
2017-07-13 14:52:55 +02:00
|
|
|
// diff --git a/leftFileName b/rightFileName
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView diffGit = readLine(patch, &afterDiffGit, &hasNewLine);
|
2017-07-13 14:52:55 +02:00
|
|
|
if (!hasNewLine)
|
|
|
|
|
return false; // we need to have at least one more line
|
|
|
|
|
|
|
|
|
|
const QLatin1String gitHeader("diff --git ");
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView fileNames = diffGit.mid(gitHeader.size());
|
|
|
|
|
QStringView commonFileName;
|
2017-07-13 14:52:55 +02:00
|
|
|
if (extractCommonFileName(fileNames, &commonFileName)) {
|
|
|
|
|
// change / new / delete
|
|
|
|
|
|
|
|
|
|
fileData->fileOperation = FileData::ChangeFile;
|
|
|
|
|
fileData->leftFileInfo.fileName = fileData->rightFileInfo.fileName = commonFileName.toString();
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView afterSecondLine;
|
|
|
|
|
const QStringView secondLine = readLine(afterDiffGit, &afterSecondLine, &hasNewLine);
|
2017-07-13 14:52:55 +02:00
|
|
|
|
|
|
|
|
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 "))) {
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView afterThirdLine;
|
2017-07-13 14:52:55 +02:00
|
|
|
// new mode
|
|
|
|
|
readLine(afterSecondLine, &afterThirdLine, &hasNewLine);
|
|
|
|
|
if (!hasNewLine)
|
2017-11-26 10:24:32 +02:00
|
|
|
fileData->fileOperation = FileData::ChangeMode;
|
2017-07-13 14:52:55 +02:00
|
|
|
|
|
|
|
|
// TODO: validate new mode line
|
|
|
|
|
*remainingPatch = afterThirdLine;
|
|
|
|
|
} else {
|
|
|
|
|
*remainingPatch = afterDiffGit;
|
|
|
|
|
}
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
} else {
|
|
|
|
|
// copy / rename
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView afterModeOrSimilarity;
|
|
|
|
|
QStringView afterSimilarity;
|
|
|
|
|
const QStringView secondLine = readLine(afterDiffGit, &afterModeOrSimilarity, &hasNewLine);
|
2022-02-06 08:11:09 +02:00
|
|
|
if (secondLine.startsWith(QLatin1String("old mode "))) {
|
|
|
|
|
if (!hasNewLine)
|
|
|
|
|
return false;
|
|
|
|
|
readLine(afterModeOrSimilarity, &afterModeOrSimilarity, &hasNewLine); // new mode
|
|
|
|
|
if (!hasNewLine)
|
|
|
|
|
return false;
|
|
|
|
|
// (dis)similarity index [0-100]%
|
|
|
|
|
readLine(afterModeOrSimilarity, &afterSimilarity, &hasNewLine);
|
|
|
|
|
} else {
|
|
|
|
|
afterSimilarity = afterModeOrSimilarity;
|
|
|
|
|
}
|
|
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (!hasNewLine)
|
|
|
|
|
return false; // we need to have at least one more line
|
|
|
|
|
|
|
|
|
|
// TODO: validate similarity line
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView afterCopyRenameFrom;
|
2017-07-13 14:52:55 +02:00
|
|
|
// [copy / rename] from leftFileName
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView copyRenameFrom = readLine(afterSimilarity, &afterCopyRenameFrom, &hasNewLine);
|
2017-07-13 14:52:55 +02:00
|
|
|
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;
|
2014-07-07 14:26:41 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView afterCopyRenameTo;
|
2017-07-13 14:52:55 +02:00
|
|
|
// [copy / rename] to rightFileName
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView copyRenameTo = readLine(afterCopyRenameFrom, &afterCopyRenameTo, &hasNewLine);
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
// if (dis)similarity index is 100% we don't have more lines
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
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);
|
2014-07-07 14:26:41 +02:00
|
|
|
}
|
|
|
|
|
|
2022-07-13 09:27:18 +02:00
|
|
|
static QList<FileData> readGitPatch(QStringView patch, bool *ok, QFutureInterfaceBase *jobController)
|
2014-02-13 16:43:28 +01:00
|
|
|
{
|
2017-07-13 14:52:55 +02:00
|
|
|
int position = -1;
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
QVector<int> startingPositions; // store starting positions of git headers
|
|
|
|
|
if (patch.startsWith(QStringLiteral("diff --git ")))
|
|
|
|
|
startingPositions.append(position + 1);
|
2017-06-16 13:45:43 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
while ((position = patch.indexOf(QStringLiteral("\ndiff --git "), position + 1)) >= 0)
|
|
|
|
|
startingPositions.append(position + 1);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
class PatchInfo {
|
|
|
|
|
public:
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView patch;
|
2017-07-13 14:52:55 +02:00
|
|
|
FileData fileData;
|
|
|
|
|
};
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
const QChar newLine('\n');
|
|
|
|
|
bool readOk = true;
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
QVector<PatchInfo> patches;
|
2020-09-07 15:41:01 +02:00
|
|
|
const int count = startingPositions.size();
|
2017-07-13 14:52:55 +02:00
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
|
if (jobController && jobController->isCanceled())
|
|
|
|
|
return QList<FileData>();
|
2017-06-16 13:45:43 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
const int diffStart = startingPositions.at(i);
|
|
|
|
|
const int diffEnd = (i < count - 1)
|
2020-09-07 15:41:01 +02:00
|
|
|
// 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.size() - 1) == newLine ? patch.size() - 1
|
|
|
|
|
: patch.size());
|
2017-06-16 13:45:43 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
// extract the patch for just one file
|
2022-07-13 09:27:18 +02:00
|
|
|
const QStringView fileDiff = patch.mid(diffStart, diffEnd - diffStart);
|
2017-06-16 13:45:43 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
FileData fileData;
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView remainingFileDiff;
|
2017-07-13 14:52:55 +02:00
|
|
|
readOk = detectFileData(fileDiff, &fileData, &remainingFileDiff);
|
2017-06-16 13:45:43 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (!readOk)
|
|
|
|
|
break;
|
2014-10-06 11:18:51 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
patches.append(PatchInfo { remainingFileDiff, fileData });
|
|
|
|
|
}
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (!readOk) {
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = readOk;
|
|
|
|
|
return QList<FileData>();
|
|
|
|
|
}
|
2017-06-16 13:45:43 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (jobController)
|
2020-09-07 15:41:01 +02:00
|
|
|
jobController->setProgressRange(0, patches.size());
|
2017-06-30 16:06:36 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
QList<FileData> fileDataList;
|
|
|
|
|
readOk = false;
|
|
|
|
|
int i = 0;
|
2018-04-08 23:40:00 +03:00
|
|
|
for (const auto &patchInfo : qAsConst(patches)) {
|
2017-07-13 14:52:55 +02:00
|
|
|
if (jobController) {
|
|
|
|
|
if (jobController->isCanceled())
|
|
|
|
|
return QList<FileData>();
|
|
|
|
|
jobController->setProgressValue(i++);
|
|
|
|
|
}
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
FileData fileData = patchInfo.fileData;
|
|
|
|
|
if (!patchInfo.patch.isEmpty() || fileData.fileOperation == FileData::ChangeFile)
|
|
|
|
|
fileData.chunks = readChunks(patchInfo.patch, &fileData.lastChunkAtTheEndOfFile, &readOk);
|
|
|
|
|
else
|
|
|
|
|
readOk = true;
|
2014-07-07 14:26:41 +02:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
if (!readOk)
|
|
|
|
|
break;
|
2014-02-13 16:43:28 +01:00
|
|
|
|
2017-07-13 14:52:55 +02:00
|
|
|
fileDataList.append(fileData);
|
2014-03-10 12:57:48 +01:00
|
|
|
}
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = readOk;
|
|
|
|
|
|
|
|
|
|
if (!readOk)
|
|
|
|
|
return QList<FileData>();
|
|
|
|
|
|
|
|
|
|
return fileDataList;
|
2014-03-10 12:57:48 +01:00
|
|
|
}
|
|
|
|
|
|
2017-06-30 16:06:36 +02:00
|
|
|
QList<FileData> DiffUtils::readPatch(const QString &patch, bool *ok,
|
|
|
|
|
QFutureInterfaceBase *jobController)
|
2014-03-10 12:57:48 +01:00
|
|
|
{
|
2014-02-13 16:43:28 +01:00
|
|
|
bool readOk = false;
|
|
|
|
|
|
|
|
|
|
QList<FileData> fileDataList;
|
|
|
|
|
|
2017-06-30 16:06:36 +02:00
|
|
|
if (jobController) {
|
|
|
|
|
jobController->setProgressRange(0, 1);
|
|
|
|
|
jobController->setProgressValue(0);
|
|
|
|
|
}
|
2022-07-13 09:27:18 +02:00
|
|
|
QStringView croppedPatch = QStringView(patch);
|
2016-11-29 16:11:42 +02:00
|
|
|
// Crop e.g. "-- \n2.10.2.windows.1\n\n" at end of file
|
2017-06-16 13:45:43 +02:00
|
|
|
const QRegularExpression formatPatchEndingRegExp("(\\n-- \\n\\S*\\n\\n$)");
|
|
|
|
|
const QRegularExpressionMatch match = formatPatchEndingRegExp.match(croppedPatch);
|
|
|
|
|
if (match.hasMatch())
|
|
|
|
|
croppedPatch = croppedPatch.left(match.capturedStart() + 1);
|
2014-07-11 10:34:22 +02:00
|
|
|
|
2017-06-30 16:06:36 +02:00
|
|
|
fileDataList = readGitPatch(croppedPatch, &readOk, jobController);
|
2014-02-13 16:43:28 +01:00
|
|
|
if (!readOk)
|
2017-06-30 16:06:36 +02:00
|
|
|
fileDataList = readDiffPatch(croppedPatch, &readOk, jobController);
|
2014-02-13 16:43:28 +01:00
|
|
|
|
|
|
|
|
if (ok)
|
|
|
|
|
*ok = readOk;
|
|
|
|
|
|
|
|
|
|
return fileDataList;
|
2014-03-10 12:57:48 +01:00
|
|
|
}
|
|
|
|
|
|
2014-02-28 10:40:20 +01:00
|
|
|
} // namespace DiffEditor
|