1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "diffutils.h"
27
28 #include <utils/algorithm.h>
29 #include <utils/differ.h>
30 #include <utils/porting.h>
31
32 #include <QFutureInterfaceBase>
33 #include <QRegularExpression>
34 #include <QStringList>
35 #include <QTextStream>
36
37 using namespace Utils;
38
39 namespace DiffEditor {
40
selectedRowsCount() const41 int ChunkSelection::selectedRowsCount() const
42 {
43 return Utils::toSet(leftSelection).unite(Utils::toSet(rightSelection)).size();
44 }
45
assemblyRows(const QList<TextLineData> & lines,const QMap<int,int> & lineSpans)46 static QList<TextLineData> assemblyRows(const QList<TextLineData> &lines,
47 const QMap<int, int> &lineSpans)
48 {
49 QList<TextLineData> data;
50
51 const int lineCount = lines.size();
52 for (int i = 0; i <= lineCount; i++) {
53 for (int j = 0; j < lineSpans.value(i); j++)
54 data.append(TextLineData(TextLineData::Separator));
55 if (i < lineCount)
56 data.append(lines.at(i));
57 }
58 return data;
59 }
60
lastLinesEqual(const QList<TextLineData> & leftLines,const QList<TextLineData> & rightLines)61 static bool lastLinesEqual(const QList<TextLineData> &leftLines,
62 const QList<TextLineData> &rightLines)
63 {
64 const bool leftLineEqual = !leftLines.isEmpty()
65 ? leftLines.last().text.isEmpty()
66 : true;
67 const bool rightLineEqual = !rightLines.isEmpty()
68 ? rightLines.last().text.isEmpty()
69 : true;
70 return leftLineEqual && rightLineEqual;
71 }
72
handleLine(const QStringList & newLines,int line,QList<TextLineData> * lines,int * lineNumber)73 static void handleLine(const QStringList &newLines,
74 int line,
75 QList<TextLineData> *lines,
76 int *lineNumber)
77 {
78 if (line < newLines.size()) {
79 const QString text = newLines.at(line);
80 if (lines->isEmpty() || line > 0) {
81 if (line > 0)
82 ++*lineNumber;
83 lines->append(TextLineData(text));
84 } else {
85 lines->last().text += text;
86 }
87 }
88 }
89
handleDifference(const QString & text,QList<TextLineData> * lines,int * lineNumber)90 static void handleDifference(const QString &text,
91 QList<TextLineData> *lines,
92 int *lineNumber)
93 {
94 const QStringList newLines = text.split('\n');
95 for (int line = 0; line < newLines.size(); ++line) {
96 const int startPos = line > 0 ? -1 : lines->isEmpty() ? 0 : lines->last().text.size();
97 handleLine(newLines, line, lines, lineNumber);
98 const int endPos = line < newLines.size() - 1
99 ? -1
100 : lines->isEmpty() ? 0 : lines->last().text.size();
101 if (!lines->isEmpty())
102 lines->last().changedPositions.insert(startPos, endPos);
103 }
104 }
105
106 /*
107 * leftDiffList can contain only deletions and equalities,
108 * while rightDiffList can contain only insertions and equalities.
109 * The number of equalities on both lists must be the same.
110 */
calculateOriginalData(const QList<Diff> & leftDiffList,const QList<Diff> & rightDiffList)111 ChunkData DiffUtils::calculateOriginalData(const QList<Diff> &leftDiffList,
112 const QList<Diff> &rightDiffList)
113 {
114 int i = 0;
115 int j = 0;
116
117 QList<TextLineData> leftLines;
118 QList<TextLineData> rightLines;
119
120 // <line number, span count>
121 QMap<int, int> leftSpans;
122 QMap<int, int> rightSpans;
123 // <left line number, right line number>
124 QMap<int, int> equalLines;
125
126 int leftLineNumber = 0;
127 int rightLineNumber = 0;
128 int leftLineAligned = -1;
129 int rightLineAligned = -1;
130 bool lastLineEqual = true;
131
132 while (i <= leftDiffList.size() && j <= rightDiffList.size()) {
133 const Diff leftDiff = i < leftDiffList.size() ? leftDiffList.at(i) : Diff(Diff::Equal);
134 const Diff rightDiff = j < rightDiffList.size() ? rightDiffList.at(j) : Diff(Diff::Equal);
135
136 if (leftDiff.command == Diff::Delete) {
137 if (j == rightDiffList.size() && lastLineEqual && leftDiff.text.startsWith('\n'))
138 equalLines.insert(leftLineNumber, rightLineNumber);
139 // process delete
140 handleDifference(leftDiff.text, &leftLines, &leftLineNumber);
141 lastLineEqual = lastLinesEqual(leftLines, rightLines);
142 if (j == rightDiffList.size())
143 lastLineEqual = false;
144 i++;
145 }
146 if (rightDiff.command == Diff::Insert) {
147 if (i == leftDiffList.size() && lastLineEqual && rightDiff.text.startsWith('\n'))
148 equalLines.insert(leftLineNumber, rightLineNumber);
149 // process insert
150 handleDifference(rightDiff.text, &rightLines, &rightLineNumber);
151 lastLineEqual = lastLinesEqual(leftLines, rightLines);
152 if (i == leftDiffList.size())
153 lastLineEqual = false;
154 j++;
155 }
156 if (leftDiff.command == Diff::Equal && rightDiff.command == Diff::Equal) {
157 // process equal
158 const QStringList newLeftLines = leftDiff.text.split('\n');
159 const QStringList newRightLines = rightDiff.text.split('\n');
160
161 int line = 0;
162
163 if (i < leftDiffList.size() || j < rightDiffList.size()
164 || (!leftLines.isEmpty() && !rightLines.isEmpty())) {
165 while (line < qMax(newLeftLines.size(), newRightLines.size())) {
166 handleLine(newLeftLines, line, &leftLines, &leftLineNumber);
167 handleLine(newRightLines, line, &rightLines, &rightLineNumber);
168
169 const int commonLineCount = qMin(newLeftLines.size(), newRightLines.size());
170 if (line < commonLineCount) {
171 // try to align
172 const int leftDifference = leftLineNumber - leftLineAligned;
173 const int rightDifference = rightLineNumber - rightLineAligned;
174
175 if (leftDifference && rightDifference) {
176 bool doAlign = true;
177 if (line == 0
178 && (newLeftLines.at(0).isEmpty()
179 || newRightLines.at(0).isEmpty())
180 && !lastLineEqual) {
181 // omit alignment when first lines of equalities
182 // are empty and last generated lines are not equal
183 doAlign = false;
184 }
185
186 if (line == commonLineCount - 1) {
187 // omit alignment when last lines of equalities are empty
188 if (leftLines.last().text.isEmpty()
189 || rightLines.last().text.isEmpty())
190 doAlign = false;
191
192 // unless it's the last dummy line (don't omit in that case)
193 if (i == leftDiffList.size() && j == rightDiffList.size())
194 doAlign = true;
195 }
196
197 if (doAlign) {
198 // align here
199 leftLineAligned = leftLineNumber;
200 rightLineAligned = rightLineNumber;
201
202 // insert separators if needed
203 if (rightDifference > leftDifference)
204 leftSpans.insert(leftLineNumber,
205 rightDifference - leftDifference);
206 else if (leftDifference > rightDifference)
207 rightSpans.insert(rightLineNumber,
208 leftDifference - rightDifference);
209 }
210 }
211 }
212
213 // check if lines are equal
214 if ((line < commonLineCount - 1) // before the last common line in equality
215 || (line == commonLineCount - 1 // or the last common line in equality
216 && i == leftDiffList.size() // and it's the last iteration
217 && j == rightDiffList.size())) {
218 if (line > 0 || lastLineEqual)
219 equalLines.insert(leftLineNumber, rightLineNumber);
220 }
221
222 if (line > 0)
223 lastLineEqual = true;
224
225 line++;
226 }
227 }
228 i++;
229 j++;
230 }
231 }
232
233 QList<TextLineData> leftData = assemblyRows(leftLines,
234 leftSpans);
235 QList<TextLineData> rightData = assemblyRows(rightLines,
236 rightSpans);
237
238 // fill ending separators
239 for (int i = leftData.size(); i < rightData.size(); i++)
240 leftData.append(TextLineData(TextLineData::Separator));
241 for (int i = rightData.size(); i < leftData.size(); i++)
242 rightData.append(TextLineData(TextLineData::Separator));
243
244 const int visualLineCount = leftData.size();
245 int leftLine = -1;
246 int rightLine = -1;
247 ChunkData chunkData;
248
249 for (int i = 0; i < visualLineCount; i++) {
250 const TextLineData &leftTextLine = leftData.at(i);
251 const TextLineData &rightTextLine = rightData.at(i);
252 RowData row(leftTextLine, rightTextLine);
253
254 if (leftTextLine.textLineType == TextLineData::TextLine)
255 ++leftLine;
256 if (rightTextLine.textLineType == TextLineData::TextLine)
257 ++rightLine;
258 if (equalLines.value(leftLine, -2) == rightLine)
259 row.equal = true;
260
261 chunkData.rows.append(row);
262 }
263 return chunkData;
264 }
265
calculateContextData(const ChunkData & originalData,int contextLineCount,int joinChunkThreshold)266 FileData DiffUtils::calculateContextData(const ChunkData &originalData, int contextLineCount,
267 int joinChunkThreshold)
268 {
269 if (contextLineCount < 0)
270 return FileData(originalData);
271
272 FileData fileData;
273 fileData.contextChunksIncluded = true;
274 fileData.lastChunkAtTheEndOfFile = true;
275
276 QMap<int, bool> hiddenRows;
277 int i = 0;
278 while (i < originalData.rows.size()) {
279 const RowData &row = originalData.rows[i];
280 if (row.equal) {
281 // count how many equal
282 int equalRowStart = i;
283 i++;
284 while (i < originalData.rows.size()) {
285 const RowData originalRow = originalData.rows.at(i);
286 if (!originalRow.equal)
287 break;
288 i++;
289 }
290 const bool first = equalRowStart == 0; // includes first line?
291 const bool last = i == originalData.rows.size(); // includes last line?
292
293 const int firstLine = first
294 ? 0 : equalRowStart + contextLineCount;
295 const int lastLine = last ? originalData.rows.size() : i - contextLineCount;
296
297 if (firstLine < lastLine - joinChunkThreshold) {
298 for (int j = firstLine; j < lastLine; j++) {
299 hiddenRows.insert(j, true);
300 }
301 }
302 } else {
303 // iterate to the next row
304 i++;
305 }
306 }
307 i = 0;
308 int leftLineNumber = 0;
309 int rightLineNumber = 0;
310 while (i < originalData.rows.size()) {
311 const bool contextChunk = hiddenRows.contains(i);
312 ChunkData chunkData;
313 chunkData.contextChunk = contextChunk;
314 chunkData.leftStartingLineNumber = leftLineNumber;
315 chunkData.rightStartingLineNumber = rightLineNumber;
316 while (i < originalData.rows.size()) {
317 if (contextChunk != hiddenRows.contains(i))
318 break;
319 RowData rowData = originalData.rows.at(i);
320 chunkData.rows.append(rowData);
321 if (rowData.leftLine.textLineType == TextLineData::TextLine)
322 ++leftLineNumber;
323 if (rowData.rightLine.textLineType == TextLineData::TextLine)
324 ++rightLineNumber;
325 ++i;
326 }
327 fileData.chunks.append(chunkData);
328 }
329
330 return fileData;
331 }
332
makePatchLine(const QChar & startLineCharacter,const QString & textLine,bool lastChunk,bool lastLine)333 QString DiffUtils::makePatchLine(const QChar &startLineCharacter,
334 const QString &textLine,
335 bool lastChunk,
336 bool lastLine)
337 {
338 QString line;
339
340 const bool addNoNewline = lastChunk // it's the last chunk in file
341 && lastLine // it's the last row in chunk
342 && !textLine.isEmpty(); // the row is not empty
343
344 const bool addLine = !lastChunk // not the last chunk in file
345 || !lastLine // not the last row in chunk
346 || addNoNewline; // no addNoNewline case
347
348 if (addLine) {
349 line = startLineCharacter + textLine + '\n';
350 if (addNoNewline)
351 line += "\\ No newline at end of file\n";
352 }
353
354 return line;
355 }
356
makePatch(const ChunkData & chunkData,bool lastChunk)357 QString DiffUtils::makePatch(const ChunkData &chunkData,
358 bool lastChunk)
359 {
360 if (chunkData.contextChunk)
361 return QString();
362
363 QString diffText;
364 int leftLineCount = 0;
365 int rightLineCount = 0;
366 QList<TextLineData> leftBuffer, rightBuffer;
367
368 int rowToBeSplit = -1;
369
370 if (lastChunk) {
371 // Detect the case when the last equal line is followed by
372 // only separators on left or on right. In that case
373 // the last equal line needs to be split.
374 const int rowCount = chunkData.rows.size();
375 int i = 0;
376 for (i = rowCount; i > 0; i--) {
377 const RowData &rowData = chunkData.rows.at(i - 1);
378 if (rowData.leftLine.textLineType != TextLineData::Separator
379 || rowData.rightLine.textLineType != TextLineData::TextLine)
380 break;
381 }
382 const int leftSeparator = i;
383 for (i = rowCount; i > 0; i--) {
384 const RowData &rowData = chunkData.rows.at(i - 1);
385 if (rowData.rightLine.textLineType != TextLineData::Separator
386 || rowData.leftLine.textLineType != TextLineData::TextLine)
387 break;
388 }
389 const int rightSeparator = i;
390 const int commonSeparator = qMin(leftSeparator, rightSeparator);
391 if (commonSeparator > 0
392 && commonSeparator < rowCount
393 && chunkData.rows.at(commonSeparator - 1).equal)
394 rowToBeSplit = commonSeparator - 1;
395 }
396
397 for (int i = 0; i <= chunkData.rows.size(); i++) {
398 const RowData &rowData = i < chunkData.rows.size()
399 ? chunkData.rows.at(i)
400 : RowData(TextLineData(TextLineData::Separator)); // dummy,
401 // ensure we process buffers to the end.
402 // rowData will be equal
403 if (rowData.equal && i != rowToBeSplit) {
404 if (!leftBuffer.isEmpty()) {
405 for (int j = 0; j < leftBuffer.size(); j++) {
406 const QString line = makePatchLine('-',
407 leftBuffer.at(j).text,
408 lastChunk,
409 i == chunkData.rows.size()
410 && j == leftBuffer.size() - 1);
411
412 if (!line.isEmpty())
413 ++leftLineCount;
414
415 diffText += line;
416 }
417 leftBuffer.clear();
418 }
419 if (!rightBuffer.isEmpty()) {
420 for (int j = 0; j < rightBuffer.size(); j++) {
421 const QString line = makePatchLine('+',
422 rightBuffer.at(j).text,
423 lastChunk,
424 i == chunkData.rows.size()
425 && j == rightBuffer.size() - 1);
426
427 if (!line.isEmpty())
428 ++rightLineCount;
429
430 diffText += line;
431 }
432 rightBuffer.clear();
433 }
434 if (i < chunkData.rows.size()) {
435 const QString line = makePatchLine(' ',
436 rowData.rightLine.text,
437 lastChunk,
438 i == chunkData.rows.size() - 1);
439
440 if (!line.isEmpty()) {
441 ++leftLineCount;
442 ++rightLineCount;
443 }
444
445 diffText += line;
446 }
447 } else {
448 if (rowData.leftLine.textLineType == TextLineData::TextLine)
449 leftBuffer.append(rowData.leftLine);
450 if (rowData.rightLine.textLineType == TextLineData::TextLine)
451 rightBuffer.append(rowData.rightLine);
452 }
453 }
454
455 const QString chunkLine = "@@ -"
456 + QString::number(chunkData.leftStartingLineNumber + 1)
457 + ','
458 + QString::number(leftLineCount)
459 + " +"
460 + QString::number(chunkData.rightStartingLineNumber + 1)
461 + ','
462 + QString::number(rightLineCount)
463 + " @@"
464 + chunkData.contextInfo
465 + '\n';
466
467 diffText.prepend(chunkLine);
468
469 return diffText;
470 }
471
makePatch(const ChunkData & chunkData,const QString & leftFileName,const QString & rightFileName,bool lastChunk)472 QString DiffUtils::makePatch(const ChunkData &chunkData,
473 const QString &leftFileName,
474 const QString &rightFileName,
475 bool lastChunk)
476 {
477 QString diffText = makePatch(chunkData, lastChunk);
478
479 const QString rightFileInfo = "+++ " + rightFileName + '\n';
480 const QString leftFileInfo = "--- " + leftFileName + '\n';
481
482 diffText.prepend(rightFileInfo);
483 diffText.prepend(leftFileInfo);
484
485 return diffText;
486 }
487
leftFileName(const FileData & fileData,unsigned formatFlags)488 static QString leftFileName(const FileData &fileData, unsigned formatFlags)
489 {
490 QString diffText;
491 QTextStream str(&diffText);
492 if (fileData.fileOperation == FileData::NewFile) {
493 str << "/dev/null";
494 } else {
495 if (formatFlags & DiffUtils::AddLevel)
496 str << "a/";
497 str << fileData.leftFileInfo.fileName;
498 }
499 return diffText;
500 }
501
rightFileName(const FileData & fileData,unsigned formatFlags)502 static QString rightFileName(const FileData &fileData, unsigned formatFlags)
503 {
504 QString diffText;
505 QTextStream str(&diffText);
506 if (fileData.fileOperation == FileData::DeleteFile) {
507 str << "/dev/null";
508 } else {
509 if (formatFlags & DiffUtils::AddLevel)
510 str << "b/";
511 str << fileData.rightFileInfo.fileName;
512 }
513 return diffText;
514 }
515
makePatch(const QList<FileData> & fileDataList,unsigned formatFlags)516 QString DiffUtils::makePatch(const QList<FileData> &fileDataList, unsigned formatFlags)
517 {
518 QString diffText;
519 QTextStream str(&diffText);
520
521 for (int i = 0; i < fileDataList.size(); i++) {
522 const FileData &fileData = fileDataList.at(i);
523 if (formatFlags & GitFormat) {
524 str << "diff --git a/" << fileData.leftFileInfo.fileName
525 << " b/" << fileData.rightFileInfo.fileName << '\n';
526 }
527 if (fileData.fileOperation == FileData::NewFile
528 || fileData.fileOperation == FileData::DeleteFile) { // git only?
529 if (fileData.fileOperation == FileData::NewFile)
530 str << "new";
531 else
532 str << "deleted";
533 str << " file mode 100644\n";
534 }
535 str << "index " << fileData.leftFileInfo.typeInfo << ".." << fileData.rightFileInfo.typeInfo;
536 if (fileData.fileOperation == FileData::ChangeFile)
537 str << " 100644";
538 str << "\n";
539
540 if (fileData.binaryFiles) {
541 str << "Binary files ";
542 str << leftFileName(fileData, formatFlags);
543 str << " and ";
544 str << rightFileName(fileData, formatFlags);
545 str << " differ\n";
546 } else {
547 if (!fileData.chunks.isEmpty()) {
548 str << "--- ";
549 str << leftFileName(fileData, formatFlags) << "\n";
550 str << "+++ ";
551 str << rightFileName(fileData, formatFlags) << "\n";
552 for (int j = 0; j < fileData.chunks.size(); j++) {
553 str << makePatch(fileData.chunks.at(j),
554 (j == fileData.chunks.size() - 1)
555 && fileData.lastChunkAtTheEndOfFile);
556 }
557 }
558 }
559 }
560 return diffText;
561 }
562
readLines(StringView patch,bool lastChunk,bool * lastChunkAtTheEndOfFile,bool * ok)563 static QList<RowData> readLines(StringView patch, bool lastChunk, bool *lastChunkAtTheEndOfFile, bool *ok)
564 {
565 QList<Diff> diffList;
566
567 const QChar newLine = '\n';
568
569 int lastEqual = -1;
570 int lastDelete = -1;
571 int lastInsert = -1;
572
573 int noNewLineInEqual = -1;
574 int noNewLineInDelete = -1;
575 int noNewLineInInsert = -1;
576
577 const QVector<StringView> lines = patch.split(newLine);
578 int i;
579 for (i = 0; i < lines.size(); i++) {
580 StringView line = lines.at(i);
581 if (line.isEmpty()) { // need to have at least one character (1 column)
582 if (lastChunk)
583 i = lines.size(); // pretend as we've read all the lines (we just ignore the rest)
584 break;
585 }
586 const QChar firstCharacter = line.at(0);
587 if (firstCharacter == '\\') { // no new line marker
588 if (!lastChunk) // can only appear in last chunk of the file
589 break;
590 if (!diffList.isEmpty()) {
591 Diff &last = diffList.last();
592 if (last.text.isEmpty())
593 break;
594
595 if (last.command == Diff::Equal) {
596 if (noNewLineInEqual >= 0)
597 break;
598 noNewLineInEqual = diffList.size() - 1;
599 } else if (last.command == Diff::Delete) {
600 if (noNewLineInDelete >= 0)
601 break;
602 noNewLineInDelete = diffList.size() - 1;
603 } else if (last.command == Diff::Insert) {
604 if (noNewLineInInsert >= 0)
605 break;
606 noNewLineInInsert = diffList.size() - 1;
607 }
608 }
609 } else {
610 Diff::Command command = Diff::Equal;
611 if (firstCharacter == ' ') { // common line
612 command = Diff::Equal;
613 } else if (firstCharacter == '-') { // deleted line
614 command = Diff::Delete;
615 } else if (firstCharacter == '+') { // inserted line
616 command = Diff::Insert;
617 } else { // no other character may exist as the first character
618 if (lastChunk)
619 i = lines.size(); // pretend as we've read all the lines (we just ignore the rest)
620 break;
621 }
622
623 Diff diffToBeAdded(command, line.mid(1).toString() + newLine);
624
625 if (!diffList.isEmpty() && diffList.last().command == command)
626 diffList.last().text.append(diffToBeAdded.text);
627 else
628 diffList.append(diffToBeAdded);
629
630 if (command == Diff::Equal) // common line
631 lastEqual = diffList.size() - 1;
632 else if (command == Diff::Delete) // deleted line
633 lastDelete = diffList.size() - 1;
634 else if (command == Diff::Insert) // inserted line
635 lastInsert = diffList.size() - 1;
636 }
637 }
638
639 if (i < lines.size() // we broke before
640 // or we have noNewLine in some equal line and in either delete or insert line
641 || (noNewLineInEqual >= 0 && (noNewLineInDelete >= 0 || noNewLineInInsert >= 0))
642 // or we have noNewLine in not the last equal line
643 || (noNewLineInEqual >= 0 && noNewLineInEqual != lastEqual)
644 // or we have noNewLine in not the last delete line or there is a equal line after the noNewLine for delete
645 || (noNewLineInDelete >= 0 && (noNewLineInDelete != lastDelete || lastEqual > lastDelete))
646 // or we have noNewLine in not the last insert line or there is a equal line after the noNewLine for insert
647 || (noNewLineInInsert >= 0 && (noNewLineInInsert != lastInsert || lastEqual > lastInsert))) {
648 if (ok)
649 *ok = false;
650 return QList<RowData>();
651 }
652
653 if (ok)
654 *ok = true;
655
656 bool removeNewLineFromLastEqual = false;
657 bool removeNewLineFromLastDelete = false;
658 bool removeNewLineFromLastInsert = false;
659 bool prependNewLineAfterLastEqual = false;
660
661 if (noNewLineInDelete >= 0 || noNewLineInInsert >= 0) {
662 if (noNewLineInDelete >= 0)
663 removeNewLineFromLastDelete = true;
664 if (noNewLineInInsert >= 0)
665 removeNewLineFromLastInsert = true;
666 } else {
667 if (noNewLineInEqual >= 0) {
668 removeNewLineFromLastEqual = true;
669 } else {
670 if (lastEqual > lastDelete && lastEqual > lastInsert) {
671 removeNewLineFromLastEqual = true;
672 } else if (lastDelete > lastEqual && lastDelete > lastInsert) {
673 if (lastInsert > lastEqual) {
674 removeNewLineFromLastDelete = true;
675 removeNewLineFromLastInsert = true;
676 } else if (lastEqual > lastInsert) {
677 removeNewLineFromLastEqual = true;
678 removeNewLineFromLastDelete = true;
679 prependNewLineAfterLastEqual = true;
680 }
681 } else if (lastInsert > lastEqual && lastInsert > lastDelete) {
682 if (lastDelete > lastEqual) {
683 removeNewLineFromLastDelete = true;
684 removeNewLineFromLastInsert = true;
685 } else if (lastEqual > lastDelete) {
686 removeNewLineFromLastEqual = true;
687 removeNewLineFromLastInsert = true;
688 prependNewLineAfterLastEqual = true;
689 }
690 }
691 }
692 }
693
694
695 if (removeNewLineFromLastEqual) {
696 Diff &diff = diffList[lastEqual];
697 diff.text = diff.text.left(diff.text.size() - 1);
698 }
699 if (removeNewLineFromLastDelete) {
700 Diff &diff = diffList[lastDelete];
701 diff.text = diff.text.left(diff.text.size() - 1);
702 }
703 if (removeNewLineFromLastInsert) {
704 Diff &diff = diffList[lastInsert];
705 diff.text = diff.text.left(diff.text.size() - 1);
706 }
707 if (prependNewLineAfterLastEqual) {
708 Diff &diff = diffList[lastEqual + 1];
709 diff.text = newLine + diff.text;
710 }
711
712 if (lastChunkAtTheEndOfFile) {
713 *lastChunkAtTheEndOfFile = noNewLineInEqual >= 0
714 || noNewLineInDelete >= 0|| noNewLineInInsert >= 0;
715 }
716
717 // diffList = Differ::merge(diffList);
718 QList<Diff> leftDiffList;
719 QList<Diff> rightDiffList;
720 Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList);
721 QList<Diff> outputLeftDiffList;
722 QList<Diff> outputRightDiffList;
723
724 Differ::diffBetweenEqualities(leftDiffList,
725 rightDiffList,
726 &outputLeftDiffList,
727 &outputRightDiffList);
728
729 return DiffUtils::calculateOriginalData(outputLeftDiffList,
730 outputRightDiffList).rows;
731 }
732
readLine(StringView text,StringView * remainingText,bool * hasNewLine)733 static StringView readLine(StringView text, StringView *remainingText, bool *hasNewLine)
734 {
735 const QChar newLine('\n');
736 const int indexOfFirstNewLine = text.indexOf(newLine);
737 if (indexOfFirstNewLine < 0) {
738 if (remainingText)
739 *remainingText = StringView();
740 if (hasNewLine)
741 *hasNewLine = false;
742 return text;
743 }
744
745 if (hasNewLine)
746 *hasNewLine = true;
747
748 if (remainingText)
749 *remainingText = text.mid(indexOfFirstNewLine + 1);
750
751 return text.left(indexOfFirstNewLine);
752 }
753
detectChunkData(StringView chunkDiff,ChunkData * chunkData,StringView * remainingPatch)754 static bool detectChunkData(StringView chunkDiff, ChunkData *chunkData, StringView *remainingPatch)
755 {
756 bool hasNewLine;
757 const StringView chunkLine = readLine(chunkDiff, remainingPatch, &hasNewLine);
758
759 const QLatin1String leftPosMarker("@@ -");
760 const QLatin1String rightPosMarker(" +");
761 const QLatin1String optionalHintMarker(" @@");
762
763 const int leftPosIndex = chunkLine.indexOf(leftPosMarker);
764 if (leftPosIndex != 0)
765 return false;
766
767 const int rightPosIndex = chunkLine.indexOf(rightPosMarker, leftPosIndex + leftPosMarker.size());
768 if (rightPosIndex < 0)
769 return false;
770
771 const int optionalHintIndex = chunkLine.indexOf(optionalHintMarker, rightPosIndex + rightPosMarker.size());
772 if (optionalHintIndex < 0)
773 return false;
774
775 const int leftPosStart = leftPosIndex + leftPosMarker.size();
776 const int leftPosLength = rightPosIndex - leftPosStart;
777 StringView leftPos = chunkLine.mid(leftPosStart, leftPosLength);
778
779 const int rightPosStart = rightPosIndex + rightPosMarker.size();
780 const int rightPosLength = optionalHintIndex - rightPosStart;
781 StringView rightPos = chunkLine.mid(rightPosStart, rightPosLength);
782
783 const int optionalHintStart = optionalHintIndex + optionalHintMarker.size();
784 const int optionalHintLength = chunkLine.size() - optionalHintStart;
785 const StringView optionalHint = chunkLine.mid(optionalHintStart, optionalHintLength);
786
787 const QChar comma(',');
788 bool ok;
789
790 const int leftCommaIndex = leftPos.indexOf(comma);
791 if (leftCommaIndex >= 0)
792 leftPos = leftPos.left(leftCommaIndex);
793 const int leftLineNumber = leftPos.toString().toInt(&ok);
794 if (!ok)
795 return false;
796
797 const int rightCommaIndex = rightPos.indexOf(comma);
798 if (rightCommaIndex >= 0)
799 rightPos = rightPos.left(rightCommaIndex);
800 const int rightLineNumber = rightPos.toString().toInt(&ok);
801 if (!ok)
802 return false;
803
804 chunkData->leftStartingLineNumber = leftLineNumber - 1;
805 chunkData->rightStartingLineNumber = rightLineNumber - 1;
806 chunkData->contextInfo = optionalHint.toString();
807
808 return true;
809 }
810
readChunks(StringView patch,bool * lastChunkAtTheEndOfFile,bool * ok)811 static QList<ChunkData> readChunks(StringView patch, bool *lastChunkAtTheEndOfFile, bool *ok)
812 {
813 QList<ChunkData> chunkDataList;
814 int position = -1;
815
816 QVector<int> startingPositions; // store starting positions of @@
817 if (patch.startsWith(QStringLiteral("@@ -")))
818 startingPositions.append(position + 1);
819
820 while ((position = patch.indexOf(QStringLiteral("\n@@ -"), position + 1)) >= 0)
821 startingPositions.append(position + 1);
822
823 const QChar newLine('\n');
824 bool readOk = true;
825
826 const int count = startingPositions.size();
827 for (int i = 0; i < count; i++) {
828 const int chunkStart = startingPositions.at(i);
829 const int chunkEnd = (i < count - 1)
830 // drop the newline before the next chunk start
831 ? startingPositions.at(i + 1) - 1
832 // drop the possible newline by the end of patch
833 : (patch.at(patch.size() - 1) == newLine ? patch.size() - 1
834 : patch.size());
835
836 // extract just one chunk
837 const StringView chunkDiff = patch.mid(chunkStart, chunkEnd - chunkStart);
838
839 ChunkData chunkData;
840 StringView lines;
841 readOk = detectChunkData(chunkDiff, &chunkData, &lines);
842
843 if (!readOk)
844 break;
845
846 chunkData.rows = readLines(lines, i == (startingPositions.size() - 1),
847 lastChunkAtTheEndOfFile, &readOk);
848 if (!readOk)
849 break;
850
851 chunkDataList.append(chunkData);
852 }
853
854 if (ok)
855 *ok = readOk;
856
857 return chunkDataList;
858 }
859
readDiffHeaderAndChunks(StringView headerAndChunks,bool * ok)860 static FileData readDiffHeaderAndChunks(StringView headerAndChunks, bool *ok)
861 {
862 StringView patch = headerAndChunks;
863 FileData fileData;
864 bool readOk = false;
865
866 const QRegularExpression leftFileRegExp(
867 "(?:\\n|^)-{3} " // "--- "
868 "([^\\t\\n]+)" // "fileName1"
869 "(?:\\t[^\\n]*)*\\n"); // optionally followed by: \t anything \t anything ...)
870 const QRegularExpression rightFileRegExp(
871 "^\\+{3} " // "+++ "
872 "([^\\t\\n]+)" // "fileName2"
873 "(?:\\t[^\\n]*)*\\n"); // optionally followed by: \t anything \t anything ...)
874 const QRegularExpression binaryRegExp(
875 "^Binary files ([^\\t\\n]+) and ([^\\t\\n]+) differ$");
876
877 // followed either by leftFileRegExp
878 const QRegularExpressionMatch leftMatch = leftFileRegExp.match(patch);
879 if (leftMatch.hasMatch() && leftMatch.capturedStart() == 0) {
880 patch = patch.mid(leftMatch.capturedEnd());
881 fileData.leftFileInfo.fileName = leftMatch.captured(1);
882
883 // followed by rightFileRegExp
884 const QRegularExpressionMatch rightMatch = rightFileRegExp.match(patch);
885 if (rightMatch.hasMatch() && rightMatch.capturedStart() == 0) {
886 patch = patch.mid(rightMatch.capturedEnd());
887 fileData.rightFileInfo.fileName = rightMatch.captured(1);
888
889 fileData.chunks = readChunks(patch,
890 &fileData.lastChunkAtTheEndOfFile,
891 &readOk);
892 }
893 } else {
894 // or by binaryRegExp
895 const QRegularExpressionMatch binaryMatch = binaryRegExp.match(patch);
896 if (binaryMatch.hasMatch() && binaryMatch.capturedStart() == 0) {
897 fileData.leftFileInfo.fileName = binaryMatch.captured(1);
898 fileData.rightFileInfo.fileName = binaryMatch.captured(2);
899 fileData.binaryFiles = true;
900 readOk = true;
901 }
902 }
903
904 if (ok)
905 *ok = readOk;
906
907 if (!readOk)
908 return FileData();
909
910 return fileData;
911
912 }
913
readDiffPatch(StringView patch,bool * ok,QFutureInterfaceBase * jobController)914 static QList<FileData> readDiffPatch(StringView patch, bool *ok, QFutureInterfaceBase *jobController)
915 {
916 const QRegularExpression diffRegExp("(?:\\n|^)" // new line of the beginning of a patch
917 "(" // either
918 "-{3} " // ---
919 "[^\\t\\n]+" // filename1
920 "(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ...
921 "\\+{3} " // +++
922 "[^\\t\\n]+" // filename2
923 "(?:\\t[^\\n]*)*\\n" // optionally followed by: \t anything \t anything ...
924 "|" // or
925 "Binary files "
926 "[^\\t\\n]+" // filename1
927 " and "
928 "[^\\t\\n]+" // filename2
929 " differ"
930 ")"); // end of or
931
932 bool readOk = false;
933
934 QList<FileData> fileDataList;
935
936 QRegularExpressionMatch diffMatch = diffRegExp.match(patch);
937 if (diffMatch.hasMatch()) {
938 readOk = true;
939 int lastPos = -1;
940 do {
941 if (jobController && jobController->isCanceled())
942 return QList<FileData>();
943
944 int pos = diffMatch.capturedStart();
945 if (lastPos >= 0) {
946 StringView headerAndChunks = patch.mid(lastPos, pos - lastPos);
947
948 const FileData fileData = readDiffHeaderAndChunks(headerAndChunks,
949 &readOk);
950
951 if (!readOk)
952 break;
953
954 fileDataList.append(fileData);
955 }
956 lastPos = pos;
957 pos = diffMatch.capturedEnd();
958 diffMatch = diffRegExp.match(patch, pos);
959 } while (diffMatch.hasMatch());
960
961 if (readOk) {
962 StringView headerAndChunks = patch.mid(lastPos, patch.size() - lastPos - 1);
963
964 const FileData fileData = readDiffHeaderAndChunks(headerAndChunks,
965 &readOk);
966
967 if (readOk)
968 fileDataList.append(fileData);
969 }
970 }
971
972 if (ok)
973 *ok = readOk;
974
975 if (!readOk)
976 return QList<FileData>();
977
978 return fileDataList;
979 }
980
981 // The git diff patch format (ChangeFile, NewFile, DeleteFile)
982 // 0. <some text lines to skip, e.g. show description>\n
983 // 1. diff --git a/[fileName] b/[fileName]\n
984 // 2a. new file mode [fileModeNumber]\n
985 // 2b. deleted file mode [fileModeNumber]\n
986 // 2c. old mode [oldFileModeNumber]\n
987 // new mode [newFileModeNumber]\n
988 // 2d. <Nothing, only in case of ChangeFile>
989 // 3a. index [leftIndexSha]..[rightIndexSha] <optionally: octalNumber>
990 // 3b. <Nothing, only in case of ChangeFile, "Dirty submodule" case>
991 // 4a. <Nothing more, only possible in case of NewFile or DeleteFile> ???
992 // 4b. \nBinary files [leftFileNameOrDevNull] and [rightFileNameOrDevNull] differ
993 // 4c. --- [leftFileNameOrDevNull]\n
994 // +++ [rightFileNameOrDevNull]\n
995 // <Chunks>
996
997 // The git diff patch format (CopyFile, RenameFile)
998 // 0. [some text lines to skip, e.g. show description]\n
999 // 1. diff --git a/[leftFileName] b/[rightFileName]\n
1000 // 2. [dis]similarity index [0-100]%\n
1001 // [copy / rename] from [leftFileName]\n
1002 // [copy / rename] to [rightFileName]
1003 // 3a. <Nothing more, only when similarity index was 100%>
1004 // 3b. index [leftIndexSha]..[rightIndexSha] <optionally: octalNumber>
1005 // 4. --- [leftFileNameOrDevNull]\n
1006 // +++ [rightFileNameOrDevNull]\n
1007 // <Chunks>
1008
detectIndexAndBinary(StringView patch,FileData * fileData,StringView * remainingPatch)1009 static bool detectIndexAndBinary(StringView patch, FileData *fileData, StringView *remainingPatch)
1010 {
1011 bool hasNewLine;
1012 *remainingPatch = patch;
1013
1014 if (remainingPatch->isEmpty()) {
1015 switch (fileData->fileOperation) {
1016 case FileData::CopyFile:
1017 case FileData::RenameFile:
1018 case FileData::ChangeMode:
1019 // in case of 100% similarity we don't have more lines in the patch
1020 return true;
1021 default:
1022 break;
1023 }
1024 }
1025
1026 StringView afterNextLine;
1027 // index [leftIndexSha]..[rightIndexSha] <optionally: octalNumber>
1028 const StringView nextLine = readLine(patch, &afterNextLine, &hasNewLine);
1029
1030 const QLatin1String indexHeader("index ");
1031
1032 if (nextLine.startsWith(indexHeader)) {
1033 const StringView indices = nextLine.mid(indexHeader.size());
1034 const int dotsPosition = indices.indexOf(QStringLiteral(".."));
1035 if (dotsPosition < 0)
1036 return false;
1037 fileData->leftFileInfo.typeInfo = indices.left(dotsPosition).toString();
1038
1039 // if there is no space we take the remaining string
1040 const int spacePosition = indices.indexOf(QChar::Space, dotsPosition + 2);
1041 const int length = spacePosition < 0 ? -1 : spacePosition - dotsPosition - 2;
1042 fileData->rightFileInfo.typeInfo = indices.mid(dotsPosition + 2, length).toString();
1043
1044 *remainingPatch = afterNextLine;
1045 } else if (fileData->fileOperation != FileData::ChangeFile) {
1046 // no index only in case of ChangeFile,
1047 // the dirty submodule diff case, see "Dirty Submodule" test:
1048 return false;
1049 }
1050
1051 if (remainingPatch->isEmpty() && (fileData->fileOperation == FileData::NewFile
1052 || fileData->fileOperation == FileData::DeleteFile)) {
1053 // OK in case of empty file
1054 return true;
1055 }
1056
1057 const QString devNull("/dev/null");
1058 const QString leftFileName = fileData->fileOperation == FileData::NewFile
1059 ? devNull : QLatin1String("a/") + fileData->leftFileInfo.fileName;
1060 const QString rightFileName = fileData->fileOperation == FileData::DeleteFile
1061 ? devNull : QLatin1String("b/") + fileData->rightFileInfo.fileName;
1062
1063 const QString binaryLine = "Binary files "
1064 + leftFileName + " and "
1065 + rightFileName + " differ";
1066
1067 if (*remainingPatch == binaryLine) {
1068 fileData->binaryFiles = true;
1069 *remainingPatch = StringView();
1070 return true;
1071 }
1072
1073 const QString leftStart = "--- " + leftFileName;
1074 StringView afterMinuses;
1075 // --- leftFileName
1076 const StringView minuses = readLine(*remainingPatch, &afterMinuses, &hasNewLine);
1077 if (!hasNewLine)
1078 return false; // we need to have at least one more line
1079
1080 if (!minuses.startsWith(leftStart))
1081 return false;
1082
1083 const QString rightStart = "+++ " + rightFileName;
1084 StringView afterPluses;
1085 // +++ rightFileName
1086 const StringView pluses = readLine(afterMinuses, &afterPluses, &hasNewLine);
1087 if (!hasNewLine)
1088 return false; // we need to have at least one more line
1089
1090 if (!pluses.startsWith(rightStart))
1091 return false;
1092
1093 *remainingPatch = afterPluses;
1094 return true;
1095 }
1096
extractCommonFileName(StringView fileNames,StringView * fileName)1097 static bool extractCommonFileName(StringView fileNames, StringView *fileName)
1098 {
1099 // we should have 1 space between filenames
1100 if (fileNames.size() % 2 == 0)
1101 return false;
1102
1103 if (!fileNames.startsWith(QLatin1String("a/")))
1104 return false;
1105
1106 // drop the space in between
1107 const int fileNameSize = fileNames.size() / 2;
1108 if (!fileNames.mid(fileNameSize).startsWith(QLatin1String(" b/")))
1109 return false;
1110
1111 // drop "a/"
1112 const StringView leftFileName = fileNames.mid(2, fileNameSize - 2);
1113
1114 // drop the first filename + " b/"
1115 const StringView rightFileName = fileNames.mid(fileNameSize + 3, fileNameSize - 2);
1116
1117 if (leftFileName != rightFileName)
1118 return false;
1119
1120 *fileName = leftFileName;
1121 return true;
1122 }
1123
detectFileData(StringView patch,FileData * fileData,StringView * remainingPatch)1124 static bool detectFileData(StringView patch, FileData *fileData, StringView *remainingPatch)
1125 {
1126 bool hasNewLine;
1127
1128 StringView afterDiffGit;
1129 // diff --git a/leftFileName b/rightFileName
1130 const StringView diffGit = readLine(patch, &afterDiffGit, &hasNewLine);
1131 if (!hasNewLine)
1132 return false; // we need to have at least one more line
1133
1134 const QLatin1String gitHeader("diff --git ");
1135 const StringView fileNames = diffGit.mid(gitHeader.size());
1136 StringView commonFileName;
1137 if (extractCommonFileName(fileNames, &commonFileName)) {
1138 // change / new / delete
1139
1140 fileData->fileOperation = FileData::ChangeFile;
1141 fileData->leftFileInfo.fileName = fileData->rightFileInfo.fileName = commonFileName.toString();
1142
1143 StringView afterSecondLine;
1144 const StringView secondLine = readLine(afterDiffGit, &afterSecondLine, &hasNewLine);
1145
1146 if (secondLine.startsWith(QStringLiteral("new file mode "))) {
1147 fileData->fileOperation = FileData::NewFile;
1148 *remainingPatch = afterSecondLine;
1149 } else if (secondLine.startsWith(QStringLiteral("deleted file mode "))) {
1150 fileData->fileOperation = FileData::DeleteFile;
1151 *remainingPatch = afterSecondLine;
1152 } else if (secondLine.startsWith(QStringLiteral("old mode "))) {
1153 StringView afterThirdLine;
1154 // new mode
1155 readLine(afterSecondLine, &afterThirdLine, &hasNewLine);
1156 if (!hasNewLine)
1157 fileData->fileOperation = FileData::ChangeMode;
1158
1159 // TODO: validate new mode line
1160 *remainingPatch = afterThirdLine;
1161 } else {
1162 *remainingPatch = afterDiffGit;
1163 }
1164
1165 } else {
1166 // copy / rename
1167
1168 StringView afterSimilarity;
1169 // (dis)similarity index [0-100]%
1170 readLine(afterDiffGit, &afterSimilarity, &hasNewLine);
1171 if (!hasNewLine)
1172 return false; // we need to have at least one more line
1173
1174 // TODO: validate similarity line
1175
1176 StringView afterCopyRenameFrom;
1177 // [copy / rename] from leftFileName
1178 const StringView copyRenameFrom = readLine(afterSimilarity, &afterCopyRenameFrom, &hasNewLine);
1179 if (!hasNewLine)
1180 return false; // we need to have at least one more line
1181
1182 const QLatin1String copyFrom("copy from ");
1183 const QLatin1String renameFrom("rename from ");
1184 if (copyRenameFrom.startsWith(copyFrom)) {
1185 fileData->fileOperation = FileData::CopyFile;
1186 fileData->leftFileInfo.fileName = copyRenameFrom.mid(copyFrom.size()).toString();
1187 } else if (copyRenameFrom.startsWith(renameFrom)) {
1188 fileData->fileOperation = FileData::RenameFile;
1189 fileData->leftFileInfo.fileName = copyRenameFrom.mid(renameFrom.size()).toString();
1190 } else {
1191 return false;
1192 }
1193
1194 StringView afterCopyRenameTo;
1195 // [copy / rename] to rightFileName
1196 const StringView copyRenameTo = readLine(afterCopyRenameFrom, &afterCopyRenameTo, &hasNewLine);
1197
1198 // if (dis)similarity index is 100% we don't have more lines
1199
1200 const QLatin1String copyTo("copy to ");
1201 const QLatin1String renameTo("rename to ");
1202 if (fileData->fileOperation == FileData::CopyFile && copyRenameTo.startsWith(copyTo)) {
1203 fileData->rightFileInfo.fileName = copyRenameTo.mid(copyTo.size()).toString();
1204 } else if (fileData->fileOperation == FileData::RenameFile && copyRenameTo.startsWith(renameTo)) {
1205 fileData->rightFileInfo.fileName = copyRenameTo.mid(renameTo.size()).toString();
1206 } else {
1207 return false;
1208 }
1209
1210 *remainingPatch = afterCopyRenameTo;
1211 }
1212 return detectIndexAndBinary(*remainingPatch, fileData, remainingPatch);
1213 }
1214
readGitPatch(StringView patch,bool * ok,QFutureInterfaceBase * jobController)1215 static QList<FileData> readGitPatch(StringView patch, bool *ok, QFutureInterfaceBase *jobController)
1216 {
1217 int position = -1;
1218
1219 QVector<int> startingPositions; // store starting positions of git headers
1220 if (patch.startsWith(QStringLiteral("diff --git ")))
1221 startingPositions.append(position + 1);
1222
1223 while ((position = patch.indexOf(QStringLiteral("\ndiff --git "), position + 1)) >= 0)
1224 startingPositions.append(position + 1);
1225
1226 class PatchInfo {
1227 public:
1228 StringView patch;
1229 FileData fileData;
1230 };
1231
1232 const QChar newLine('\n');
1233 bool readOk = true;
1234
1235 QVector<PatchInfo> patches;
1236 const int count = startingPositions.size();
1237 for (int i = 0; i < count; i++) {
1238 if (jobController && jobController->isCanceled())
1239 return QList<FileData>();
1240
1241 const int diffStart = startingPositions.at(i);
1242 const int diffEnd = (i < count - 1)
1243 // drop the newline before the next header start
1244 ? startingPositions.at(i + 1) - 1
1245 // drop the possible newline by the end of file
1246 : (patch.at(patch.size() - 1) == newLine ? patch.size() - 1
1247 : patch.size());
1248
1249 // extract the patch for just one file
1250 const StringView fileDiff = patch.mid(diffStart, diffEnd - diffStart);
1251
1252 FileData fileData;
1253 StringView remainingFileDiff;
1254 readOk = detectFileData(fileDiff, &fileData, &remainingFileDiff);
1255
1256 if (!readOk)
1257 break;
1258
1259 patches.append(PatchInfo { remainingFileDiff, fileData });
1260 }
1261
1262 if (!readOk) {
1263 if (ok)
1264 *ok = readOk;
1265 return QList<FileData>();
1266 }
1267
1268 if (jobController)
1269 jobController->setProgressRange(0, patches.size());
1270
1271 QList<FileData> fileDataList;
1272 readOk = false;
1273 int i = 0;
1274 for (const auto &patchInfo : qAsConst(patches)) {
1275 if (jobController) {
1276 if (jobController->isCanceled())
1277 return QList<FileData>();
1278 jobController->setProgressValue(i++);
1279 }
1280
1281 FileData fileData = patchInfo.fileData;
1282 if (!patchInfo.patch.isEmpty() || fileData.fileOperation == FileData::ChangeFile)
1283 fileData.chunks = readChunks(patchInfo.patch, &fileData.lastChunkAtTheEndOfFile, &readOk);
1284 else
1285 readOk = true;
1286
1287 if (!readOk)
1288 break;
1289
1290 fileDataList.append(fileData);
1291 }
1292
1293 if (ok)
1294 *ok = readOk;
1295
1296 if (!readOk)
1297 return QList<FileData>();
1298
1299 return fileDataList;
1300 }
1301
readPatch(const QString & patch,bool * ok,QFutureInterfaceBase * jobController)1302 QList<FileData> DiffUtils::readPatch(const QString &patch, bool *ok,
1303 QFutureInterfaceBase *jobController)
1304 {
1305 bool readOk = false;
1306
1307 QList<FileData> fileDataList;
1308
1309 if (jobController) {
1310 jobController->setProgressRange(0, 1);
1311 jobController->setProgressValue(0);
1312 }
1313 StringView croppedPatch = make_stringview(patch);
1314 // Crop e.g. "-- \n2.10.2.windows.1\n\n" at end of file
1315 const QRegularExpression formatPatchEndingRegExp("(\\n-- \\n\\S*\\n\\n$)");
1316 const QRegularExpressionMatch match = formatPatchEndingRegExp.match(croppedPatch);
1317 if (match.hasMatch())
1318 croppedPatch = croppedPatch.left(match.capturedStart() + 1);
1319
1320 fileDataList = readGitPatch(croppedPatch, &readOk, jobController);
1321 if (!readOk)
1322 fileDataList = readDiffPatch(croppedPatch, &readOk, jobController);
1323
1324 if (ok)
1325 *ok = readOk;
1326
1327 return fileDataList;
1328 }
1329
1330 } // namespace DiffEditor
1331