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 "diffeditorplugin.h"
27 #include "diffeditor.h"
28 #include "diffeditorconstants.h"
29 #include "diffeditorcontroller.h"
30 #include "diffeditordocument.h"
31 #include "diffeditorfactory.h"
32
33 #include <QAction>
34 #include <QFileDialog>
35 #include <QFutureWatcher>
36 #include <QMenu>
37 #include <QTextCodec>
38
39 #include <coreplugin/actionmanager/actioncontainer.h>
40 #include <coreplugin/actionmanager/actionmanager.h>
41 #include <coreplugin/coreconstants.h>
42 #include <coreplugin/editormanager/documentmodel.h>
43 #include <coreplugin/editormanager/editormanager.h>
44 #include <coreplugin/icore.h>
45 #include <coreplugin/progressmanager/progressmanager.h>
46
47 #include <texteditor/textdocument.h>
48 #include <texteditor/texteditor.h>
49 #include "texteditor/texteditoractionhandler.h"
50
51 #include <utils/algorithm.h>
52 #include <utils/differ.h>
53 #include <utils/mapreduce.h>
54 #include <utils/qtcassert.h>
55
56 using namespace Core;
57 using namespace TextEditor;
58 using namespace Utils;
59
60 namespace DiffEditor {
61 namespace Internal {
62
63 class ReloadInput {
64 public:
65 QString leftText;
66 QString rightText;
67 DiffFileInfo leftFileInfo;
68 DiffFileInfo rightFileInfo;
69 FileData::FileOperation fileOperation = FileData::ChangeFile;
70 bool binaryFiles = false;
71 };
72
73 class DiffFile
74 {
75 public:
DiffFile(bool ignoreWhitespace,int contextLineCount)76 DiffFile(bool ignoreWhitespace, int contextLineCount)
77 : m_contextLineCount(contextLineCount),
78 m_ignoreWhitespace(ignoreWhitespace)
79 {}
80
operator ()(QFutureInterface<FileData> & futureInterface,const ReloadInput & reloadInfo) const81 void operator()(QFutureInterface<FileData> &futureInterface,
82 const ReloadInput &reloadInfo) const
83 {
84 if (reloadInfo.leftText == reloadInfo.rightText)
85 return; // We show "No difference" in this case, regardless if it's binary or not
86
87 Differ differ(&futureInterface);
88
89 FileData fileData;
90 if (!reloadInfo.binaryFiles) {
91 const QList<Diff> diffList = Differ::cleanupSemantics(
92 differ.diff(reloadInfo.leftText, reloadInfo.rightText));
93
94 QList<Diff> leftDiffList;
95 QList<Diff> rightDiffList;
96 Differ::splitDiffList(diffList, &leftDiffList, &rightDiffList);
97 QList<Diff> outputLeftDiffList;
98 QList<Diff> outputRightDiffList;
99
100 if (m_ignoreWhitespace) {
101 const QList<Diff> leftIntermediate
102 = Differ::moveWhitespaceIntoEqualities(leftDiffList);
103 const QList<Diff> rightIntermediate
104 = Differ::moveWhitespaceIntoEqualities(rightDiffList);
105 Differ::ignoreWhitespaceBetweenEqualities(leftIntermediate, rightIntermediate,
106 &outputLeftDiffList, &outputRightDiffList);
107 } else {
108 outputLeftDiffList = leftDiffList;
109 outputRightDiffList = rightDiffList;
110 }
111
112 const ChunkData chunkData = DiffUtils::calculateOriginalData(
113 outputLeftDiffList, outputRightDiffList);
114 fileData = DiffUtils::calculateContextData(chunkData, m_contextLineCount, 0);
115 }
116 fileData.leftFileInfo = reloadInfo.leftFileInfo;
117 fileData.rightFileInfo = reloadInfo.rightFileInfo;
118 fileData.fileOperation = reloadInfo.fileOperation;
119 fileData.binaryFiles = reloadInfo.binaryFiles;
120 futureInterface.reportResult(fileData);
121 }
122
123 private:
124 const int m_contextLineCount;
125 const bool m_ignoreWhitespace;
126 };
127
128 class DiffFilesController : public DiffEditorController
129 {
130 Q_OBJECT
131 public:
132 DiffFilesController(IDocument *document);
~DiffFilesController()133 ~DiffFilesController() override { cancelReload(); }
134
135 protected:
136 virtual QList<ReloadInput> reloadInputList() const = 0;
137
138 private:
139 void reloaded();
140 void cancelReload();
141
142 QFutureWatcher<FileData> m_futureWatcher;
143 };
144
DiffFilesController(IDocument * document)145 DiffFilesController::DiffFilesController(IDocument *document)
146 : DiffEditorController(document)
147 {
148 connect(&m_futureWatcher, &QFutureWatcher<FileData>::finished,
149 this, &DiffFilesController::reloaded);
150
151 setReloader([this] {
152 cancelReload();
153 m_futureWatcher.setFuture(Utils::map(reloadInputList(),
154 DiffFile(ignoreWhitespace(),
155 contextLineCount())));
156
157 Core::ProgressManager::addTask(m_futureWatcher.future(),
158 tr("Calculating diff"), "DiffEditor");
159 });
160 }
161
reloaded()162 void DiffFilesController::reloaded()
163 {
164 const bool success = !m_futureWatcher.future().isCanceled();
165 const QList<FileData> fileDataList = success
166 ? m_futureWatcher.future().results() : QList<FileData>();
167
168 setDiffFiles(fileDataList);
169 reloadFinished(success);
170 }
171
cancelReload()172 void DiffFilesController::cancelReload()
173 {
174 if (m_futureWatcher.future().isRunning()) {
175 m_futureWatcher.future().cancel();
176 m_futureWatcher.setFuture(QFuture<FileData>());
177 }
178 }
179
180 class DiffCurrentFileController : public DiffFilesController
181 {
182 Q_OBJECT
183 public:
184 DiffCurrentFileController(IDocument *document, const QString &fileName);
185
186 protected:
187 QList<ReloadInput> reloadInputList() const final;
188
189 private:
190 const QString m_fileName;
191 };
192
DiffCurrentFileController(IDocument * document,const QString & fileName)193 DiffCurrentFileController::DiffCurrentFileController(IDocument *document, const QString &fileName) :
194 DiffFilesController(document), m_fileName(fileName)
195 { }
196
reloadInputList() const197 QList<ReloadInput> DiffCurrentFileController::reloadInputList() const
198 {
199 QList<ReloadInput> result;
200
201 auto textDocument = qobject_cast<TextEditor::TextDocument *>(
202 DocumentModel::documentForFilePath(FilePath::fromString(m_fileName)));
203
204 if (textDocument && textDocument->isModified()) {
205 QString errorString;
206 Utils::TextFileFormat format = textDocument->format();
207
208 QString leftText;
209 const Utils::TextFileFormat::ReadResult leftResult
210 = Utils::TextFileFormat::readFile(FilePath::fromString(m_fileName), format.codec,
211 &leftText, &format, &errorString);
212
213 const QString rightText = textDocument->plainText();
214
215 ReloadInput reloadInput;
216 reloadInput.leftText = leftText;
217 reloadInput.rightText = rightText;
218 reloadInput.leftFileInfo.fileName = m_fileName;
219 reloadInput.rightFileInfo.fileName = m_fileName;
220 reloadInput.leftFileInfo.typeInfo = tr("Saved");
221 reloadInput.rightFileInfo.typeInfo = tr("Modified");
222 reloadInput.rightFileInfo.patchBehaviour = DiffFileInfo::PatchEditor;
223 reloadInput.binaryFiles = (leftResult == Utils::TextFileFormat::ReadEncodingError);
224
225 if (leftResult == Utils::TextFileFormat::ReadIOError)
226 reloadInput.fileOperation = FileData::NewFile;
227
228 result << reloadInput;
229 }
230
231 return result;
232 }
233
234 /////////////////
235
236 class DiffOpenFilesController : public DiffFilesController
237 {
238 Q_OBJECT
239 public:
240 DiffOpenFilesController(IDocument *document);
241
242 protected:
243 QList<ReloadInput> reloadInputList() const final;
244 };
245
DiffOpenFilesController(IDocument * document)246 DiffOpenFilesController::DiffOpenFilesController(IDocument *document) :
247 DiffFilesController(document)
248 { }
249
reloadInputList() const250 QList<ReloadInput> DiffOpenFilesController::reloadInputList() const
251 {
252 QList<ReloadInput> result;
253
254 const QList<IDocument *> openedDocuments = DocumentModel::openedDocuments();
255
256 for (IDocument *doc : openedDocuments) {
257 auto textDocument = qobject_cast<TextEditor::TextDocument *>(doc);
258
259 if (textDocument && textDocument->isModified()) {
260 QString errorString;
261 Utils::TextFileFormat format = textDocument->format();
262
263 QString leftText;
264 const QString fileName = textDocument->filePath().toString();
265 const Utils::TextFileFormat::ReadResult leftResult = Utils::TextFileFormat::readFile(
266 FilePath::fromString(fileName), format.codec, &leftText, &format, &errorString);
267
268 const QString rightText = textDocument->plainText();
269
270 ReloadInput reloadInput;
271 reloadInput.leftText = leftText;
272 reloadInput.rightText = rightText;
273 reloadInput.leftFileInfo.fileName = fileName;
274 reloadInput.rightFileInfo.fileName = fileName;
275 reloadInput.leftFileInfo.typeInfo = tr("Saved");
276 reloadInput.rightFileInfo.typeInfo = tr("Modified");
277 reloadInput.rightFileInfo.patchBehaviour = DiffFileInfo::PatchEditor;
278 reloadInput.binaryFiles = (leftResult == Utils::TextFileFormat::ReadEncodingError);
279
280 if (leftResult == Utils::TextFileFormat::ReadIOError)
281 reloadInput.fileOperation = FileData::NewFile;
282
283 result << reloadInput;
284 }
285 }
286
287 return result;
288 }
289
290 /////////////////
291
292 class DiffModifiedFilesController : public DiffFilesController
293 {
294 Q_OBJECT
295 public:
296 DiffModifiedFilesController(IDocument *document, const QStringList &fileNames);
297
298 protected:
299 QList<ReloadInput> reloadInputList() const final;
300
301 private:
302 const QStringList m_fileNames;
303 };
304
DiffModifiedFilesController(IDocument * document,const QStringList & fileNames)305 DiffModifiedFilesController::DiffModifiedFilesController(IDocument *document, const QStringList &fileNames) :
306 DiffFilesController(document), m_fileNames(fileNames)
307 { }
308
reloadInputList() const309 QList<ReloadInput> DiffModifiedFilesController::reloadInputList() const
310 {
311 QList<ReloadInput> result;
312
313 for (const QString &fileName : m_fileNames) {
314 auto textDocument = qobject_cast<TextEditor::TextDocument *>(
315 DocumentModel::documentForFilePath(Utils::FilePath::fromString(fileName)));
316
317 if (textDocument && textDocument->isModified()) {
318 QString errorString;
319 Utils::TextFileFormat format = textDocument->format();
320
321 QString leftText;
322 const QString fileName = textDocument->filePath().toString();
323 const Utils::TextFileFormat::ReadResult leftResult = Utils::TextFileFormat::readFile(
324 FilePath::fromString(fileName), format.codec, &leftText, &format, &errorString);
325
326 const QString rightText = textDocument->plainText();
327
328 ReloadInput reloadInput;
329 reloadInput.leftText = leftText;
330 reloadInput.rightText = rightText;
331 reloadInput.leftFileInfo.fileName = fileName;
332 reloadInput.rightFileInfo.fileName = fileName;
333 reloadInput.leftFileInfo.typeInfo = tr("Saved");
334 reloadInput.rightFileInfo.typeInfo = tr("Modified");
335 reloadInput.rightFileInfo.patchBehaviour = DiffFileInfo::PatchEditor;
336 reloadInput.binaryFiles = (leftResult == Utils::TextFileFormat::ReadEncodingError);
337
338 if (leftResult == Utils::TextFileFormat::ReadIOError)
339 reloadInput.fileOperation = FileData::NewFile;
340
341 result << reloadInput;
342 }
343 }
344
345 return result;
346 }
347
348 /////////////////
349
350 class DiffExternalFilesController : public DiffFilesController
351 {
352 Q_OBJECT
353 public:
354 DiffExternalFilesController(IDocument *document, const QString &leftFileName,
355 const QString &rightFileName);
356
357 protected:
358 QList<ReloadInput> reloadInputList() const final;
359
360 private:
361 const QString m_leftFileName;
362 const QString m_rightFileName;
363 };
364
DiffExternalFilesController(IDocument * document,const QString & leftFileName,const QString & rightFileName)365 DiffExternalFilesController::DiffExternalFilesController(IDocument *document, const QString &leftFileName,
366 const QString &rightFileName) :
367 DiffFilesController(document), m_leftFileName(leftFileName), m_rightFileName(rightFileName)
368 { }
369
reloadInputList() const370 QList<ReloadInput> DiffExternalFilesController::reloadInputList() const
371 {
372 QString errorString;
373 Utils::TextFileFormat format;
374 format.codec = EditorManager::defaultTextCodec();
375
376 QString leftText;
377 QString rightText;
378
379 const Utils::TextFileFormat::ReadResult leftResult = Utils::TextFileFormat::readFile(
380 FilePath::fromString(m_leftFileName), format.codec, &leftText, &format, &errorString);
381 const Utils::TextFileFormat::ReadResult rightResult = Utils::TextFileFormat::readFile(
382 FilePath::fromString(m_rightFileName), format.codec, &rightText, &format, &errorString);
383
384 ReloadInput reloadInput;
385 reloadInput.leftText = leftText;
386 reloadInput.rightText = rightText;
387 reloadInput.leftFileInfo.fileName = m_leftFileName;
388 reloadInput.rightFileInfo.fileName = m_rightFileName;
389 reloadInput.binaryFiles = (leftResult == Utils::TextFileFormat::ReadEncodingError
390 || rightResult == Utils::TextFileFormat::ReadEncodingError);
391
392 const bool leftFileExists = (leftResult != Utils::TextFileFormat::ReadIOError);
393 const bool rightFileExists = (rightResult != Utils::TextFileFormat::ReadIOError);
394 if (!leftFileExists && rightFileExists)
395 reloadInput.fileOperation = FileData::NewFile;
396 else if (leftFileExists && !rightFileExists)
397 reloadInput.fileOperation = FileData::DeleteFile;
398
399 QList<ReloadInput> result;
400 if (leftFileExists || rightFileExists)
401 result << reloadInput;
402
403 return result;
404 }
405
406 /////////////////
407
408
currentTextDocument()409 static TextEditor::TextDocument *currentTextDocument()
410 {
411 return qobject_cast<TextEditor::TextDocument *>(
412 EditorManager::currentDocument());
413 }
414
415 DiffEditorServiceImpl::DiffEditorServiceImpl() = default;
416
diffFiles(const QString & leftFileName,const QString & rightFileName)417 void DiffEditorServiceImpl::diffFiles(const QString &leftFileName, const QString &rightFileName)
418 {
419 const QString documentId = Constants::DIFF_EDITOR_PLUGIN
420 + QLatin1String(".DiffFiles.") + leftFileName + QLatin1Char('.') + rightFileName;
421 const QString title = tr("Diff Files");
422 auto const document = qobject_cast<DiffEditorDocument *>(
423 DiffEditorController::findOrCreateDocument(documentId, title));
424 if (!document)
425 return;
426
427 if (!DiffEditorController::controller(document))
428 new DiffExternalFilesController(document, leftFileName, rightFileName);
429 EditorManager::activateEditorForDocument(document);
430 document->reload();
431 }
432
diffModifiedFiles(const QStringList & fileNames)433 void DiffEditorServiceImpl::diffModifiedFiles(const QStringList &fileNames)
434 {
435 const QString documentId = Constants::DIFF_EDITOR_PLUGIN
436 + QLatin1String(".DiffModifiedFiles");
437 const QString title = tr("Diff Modified Files");
438 auto const document = qobject_cast<DiffEditorDocument *>(
439 DiffEditorController::findOrCreateDocument(documentId, title));
440 if (!document)
441 return;
442
443 if (!DiffEditorController::controller(document))
444 new DiffModifiedFilesController(document, fileNames);
445 EditorManager::activateEditorForDocument(document);
446 document->reload();
447 }
448
449 class DiffEditorPluginPrivate : public QObject
450 {
451 Q_DECLARE_TR_FUNCTIONS(DiffEditor::Internal::DiffEditorPlugin)
452
453 public:
454 DiffEditorPluginPrivate();
455
456 void updateDiffCurrentFileAction();
457 void updateDiffOpenFilesAction();
458 void diffCurrentFile();
459 void diffOpenFiles();
460 void diffExternalFiles();
461
462 QAction *m_diffCurrentFileAction = nullptr;
463 QAction *m_diffOpenFilesAction = nullptr;
464
465 DiffEditorFactory editorFactory;
466 DiffEditorServiceImpl service;
467 };
468
DiffEditorPluginPrivate()469 DiffEditorPluginPrivate::DiffEditorPluginPrivate()
470 {
471 //register actions
472 ActionContainer *toolsContainer
473 = ActionManager::actionContainer(Core::Constants::M_TOOLS);
474 toolsContainer->insertGroup(Core::Constants::G_TOOLS_OPTIONS, Constants::G_TOOLS_DIFF);
475 ActionContainer *diffContainer = ActionManager::createMenu("Diff");
476 diffContainer->menu()->setTitle(tr("&Diff"));
477 toolsContainer->addMenu(diffContainer, Constants::G_TOOLS_DIFF);
478
479 m_diffCurrentFileAction = new QAction(tr("Diff Current File"), this);
480 Command *diffCurrentFileCommand = ActionManager::registerAction(m_diffCurrentFileAction, "DiffEditor.DiffCurrentFile");
481 diffCurrentFileCommand->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+H") : tr("Ctrl+H")));
482 connect(m_diffCurrentFileAction, &QAction::triggered, this, &DiffEditorPluginPrivate::diffCurrentFile);
483 diffContainer->addAction(diffCurrentFileCommand);
484
485 m_diffOpenFilesAction = new QAction(tr("Diff Open Files"), this);
486 Command *diffOpenFilesCommand = ActionManager::registerAction(m_diffOpenFilesAction, "DiffEditor.DiffOpenFiles");
487 diffOpenFilesCommand->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+Shift+H") : tr("Ctrl+Shift+H")));
488 connect(m_diffOpenFilesAction, &QAction::triggered, this, &DiffEditorPluginPrivate::diffOpenFiles);
489 diffContainer->addAction(diffOpenFilesCommand);
490
491 QAction *diffExternalFilesAction = new QAction(tr("Diff External Files..."), this);
492 Command *diffExternalFilesCommand = ActionManager::registerAction(diffExternalFilesAction, "DiffEditor.DiffExternalFiles");
493 connect(diffExternalFilesAction, &QAction::triggered, this, &DiffEditorPluginPrivate::diffExternalFiles);
494 diffContainer->addAction(diffExternalFilesCommand);
495
496 connect(EditorManager::instance(), &EditorManager::currentEditorChanged,
497 this, &DiffEditorPluginPrivate::updateDiffCurrentFileAction);
498 connect(EditorManager::instance(), &EditorManager::currentDocumentStateChanged,
499 this, &DiffEditorPluginPrivate::updateDiffCurrentFileAction);
500 connect(EditorManager::instance(), &EditorManager::editorOpened,
501 this, &DiffEditorPluginPrivate::updateDiffOpenFilesAction);
502 connect(EditorManager::instance(), &EditorManager::editorsClosed,
503 this, &DiffEditorPluginPrivate::updateDiffOpenFilesAction);
504 connect(EditorManager::instance(), &EditorManager::documentStateChanged,
505 this, &DiffEditorPluginPrivate::updateDiffOpenFilesAction);
506
507 updateDiffCurrentFileAction();
508 updateDiffOpenFilesAction();
509 }
510
updateDiffCurrentFileAction()511 void DiffEditorPluginPrivate::updateDiffCurrentFileAction()
512 {
513 auto textDocument = currentTextDocument();
514 const bool enabled = textDocument && textDocument->isModified();
515 m_diffCurrentFileAction->setEnabled(enabled);
516 }
517
updateDiffOpenFilesAction()518 void DiffEditorPluginPrivate::updateDiffOpenFilesAction()
519 {
520 const bool enabled = Utils::anyOf(DocumentModel::openedDocuments(), [](IDocument *doc) {
521 return doc->isModified() && qobject_cast<TextEditor::TextDocument *>(doc);
522 });
523 m_diffOpenFilesAction->setEnabled(enabled);
524 }
525
diffCurrentFile()526 void DiffEditorPluginPrivate::diffCurrentFile()
527 {
528 auto textDocument = currentTextDocument();
529 if (!textDocument)
530 return;
531
532 const QString fileName = textDocument->filePath().toString();
533
534 if (fileName.isEmpty())
535 return;
536
537 const QString documentId = Constants::DIFF_EDITOR_PLUGIN
538 + QLatin1String(".Diff.") + fileName;
539 const QString title = tr("Diff \"%1\"").arg(fileName);
540 auto const document = qobject_cast<DiffEditorDocument *>(
541 DiffEditorController::findOrCreateDocument(documentId, title));
542 if (!document)
543 return;
544
545 if (!DiffEditorController::controller(document))
546 new DiffCurrentFileController(document, fileName);
547 EditorManager::activateEditorForDocument(document);
548 document->reload();
549 }
550
diffOpenFiles()551 void DiffEditorPluginPrivate::diffOpenFiles()
552 {
553 const QString documentId = Constants::DIFF_EDITOR_PLUGIN
554 + QLatin1String(".DiffOpenFiles");
555 const QString title = tr("Diff Open Files");
556 auto const document = qobject_cast<DiffEditorDocument *>(
557 DiffEditorController::findOrCreateDocument(documentId, title));
558 if (!document)
559 return;
560
561 if (!DiffEditorController::controller(document))
562 new DiffOpenFilesController(document);
563 EditorManager::activateEditorForDocument(document);
564 document->reload();
565 }
566
diffExternalFiles()567 void DiffEditorPluginPrivate::diffExternalFiles()
568 {
569 const QString fileName1 = QFileDialog::getOpenFileName(ICore::dialogParent(),
570 tr("Select First File for Diff"),
571 QString());
572 if (fileName1.isNull())
573 return;
574 if (EditorManager::skipOpeningBigTextFile(FilePath::fromString(fileName1)))
575 return;
576
577 const QString fileName2 = QFileDialog::getOpenFileName(ICore::dialogParent(),
578 tr("Select Second File for Diff"),
579 QString());
580 if (fileName2.isNull())
581 return;
582 if (EditorManager::skipOpeningBigTextFile(FilePath::fromString(fileName2)))
583 return;
584
585 const QString documentId = Constants::DIFF_EDITOR_PLUGIN
586 + QLatin1String(".DiffExternalFiles.") + fileName1 + QLatin1Char('.') + fileName2;
587 const QString title = tr("Diff \"%1\", \"%2\"").arg(fileName1, fileName2);
588 auto const document = qobject_cast<DiffEditorDocument *>(
589 DiffEditorController::findOrCreateDocument(documentId, title));
590 if (!document)
591 return;
592
593 if (!DiffEditorController::controller(document))
594 new DiffExternalFilesController(document, fileName1, fileName2);
595 EditorManager::activateEditorForDocument(document);
596 document->reload();
597 }
598
~DiffEditorPlugin()599 DiffEditorPlugin::~DiffEditorPlugin()
600 {
601 delete d;
602 }
603
initialize(const QStringList & arguments,QString * errorMessage)604 bool DiffEditorPlugin::initialize(const QStringList &arguments, QString *errorMessage)
605 {
606 d = new DiffEditorPluginPrivate;
607
608 Q_UNUSED(arguments)
609 Q_UNUSED(errorMessage)
610
611 return true;
612 }
613
614 } // namespace Internal
615 } // namespace DiffEditor
616
617 #ifdef WITH_TESTS
618
619 #include <QTest>
620
621 #include "diffutils.h"
622
623 Q_DECLARE_METATYPE(DiffEditor::ChunkData)
Q_DECLARE_METATYPE(DiffEditor::FileData)624 Q_DECLARE_METATYPE(DiffEditor::FileData)
625 Q_DECLARE_METATYPE(DiffEditor::ChunkSelection)
626
627 static inline QString _(const char *string) { return QString::fromLatin1(string); }
628
testMakePatch_data()629 void DiffEditor::Internal::DiffEditorPlugin::testMakePatch_data()
630 {
631 QTest::addColumn<ChunkData>("sourceChunk");
632 QTest::addColumn<bool>("lastChunk");
633 QTest::addColumn<QString>("patchText");
634
635 const QString fileName = "a.txt";
636 const QString header = "--- " + fileName + "\n+++ " + fileName + '\n';
637
638 QList<RowData> rows;
639 rows << RowData(_("ABCD"), TextLineData::Separator);
640 rows << RowData(_("EFGH"));
641 ChunkData chunk;
642 chunk.rows = rows;
643 QString patchText = header + "@@ -1,2 +1,1 @@\n"
644 "-ABCD\n"
645 " EFGH\n";
646 QTest::newRow("Simple not a last chunk") << chunk
647 << false
648 << patchText;
649
650 ///////////
651
652 // chunk the same here
653 patchText = header + "@@ -1,2 +1,1 @@\n"
654 "-ABCD\n"
655 " EFGH\n"
656 "\\ No newline at end of file\n";
657
658 QTest::newRow("Simple last chunk") << chunk
659 << true
660 << patchText;
661
662 ///////////
663
664 rows.clear();
665 rows << RowData(_("ABCD"));
666 rows << RowData(_(""), TextLineData::Separator);
667 chunk.rows = rows;
668 patchText = header + "@@ -1,1 +1,1 @@\n"
669 "-ABCD\n"
670 "+ABCD\n"
671 "\\ No newline at end of file\n";
672
673 QTest::newRow("EOL in last line removed") << chunk
674 << true
675 << patchText;
676
677 ///////////
678
679 // chunk the same here
680 patchText = header + "@@ -1,2 +1,1 @@\n"
681 " ABCD\n"
682 "-\n";
683
684 QTest::newRow("Last empty line removed") << chunk
685 << false
686 << patchText;
687
688 ///////////
689
690 rows.clear();
691 rows << RowData(_("ABCD"));
692 rows << RowData(_(""), TextLineData::Separator);
693 rows << RowData(_(""), TextLineData::Separator);
694 chunk.rows = rows;
695 patchText = header + "@@ -1,2 +1,1 @@\n"
696 "-ABCD\n"
697 "-\n"
698 "+ABCD\n"
699 "\\ No newline at end of file\n";
700
701 QTest::newRow("Two last EOLs removed") << chunk
702 << true
703 << patchText;
704
705 ///////////
706
707 rows.clear();
708 rows << RowData(_("ABCD"));
709 rows << RowData(TextLineData::Separator, _(""));
710 chunk.rows = rows;
711 patchText = header + "@@ -1,1 +1,1 @@\n"
712 "-ABCD\n"
713 "\\ No newline at end of file\n"
714 "+ABCD\n";
715
716 QTest::newRow("EOL to last line added") << chunk
717 << true
718 << patchText;
719
720 ///////////
721
722 // chunk the same here
723 patchText = header + "@@ -1,1 +1,2 @@\n"
724 " ABCD\n"
725 "+\n";
726
727 QTest::newRow("Last empty line added") << chunk
728 << false
729 << patchText;
730
731 ///////////
732
733 rows.clear();
734 rows << RowData(_("ABCD"), _("EFGH"));
735 chunk.rows = rows;
736 patchText = header + "@@ -1,1 +1,1 @@\n"
737 "-ABCD\n"
738 "+EFGH\n";
739
740 QTest::newRow("Last line with a newline modified") << chunk
741 << false
742 << patchText;
743
744 ///////////
745
746 rows.clear();
747 rows << RowData(_("ABCD"), _("EFGH"));
748 rows << RowData(_(""));
749 chunk.rows = rows;
750 patchText = header + "@@ -1,2 +1,2 @@\n"
751 "-ABCD\n"
752 "+EFGH\n"
753 " \n";
754 QTest::newRow("Not a last line with a newline modified") << chunk
755 << false
756 << patchText;
757
758 ///////////
759
760 rows.clear();
761 rows << RowData(_("ABCD"), _("EFGH"));
762 chunk.rows = rows;
763 patchText = header + "@@ -1,1 +1,1 @@\n"
764 "-ABCD\n"
765 "\\ No newline at end of file\n"
766 "+EFGH\n"
767 "\\ No newline at end of file\n";
768
769 QTest::newRow("Last line without a newline modified") << chunk
770 << true
771 << patchText;
772
773 ///////////
774
775 // chunk the same here
776 patchText = header + "@@ -1,1 +1,1 @@\n"
777 "-ABCD\n"
778 "+EFGH\n";
779 QTest::newRow("Not a last line without a newline modified") << chunk
780 << false
781 << patchText;
782
783 ///////////
784
785 rows.clear();
786 rows << RowData(_("ABCD"), _("EFGH"));
787 rows << RowData(_("IJKL"));
788 chunk.rows = rows;
789 patchText = header + "@@ -1,2 +1,2 @@\n"
790 "-ABCD\n"
791 "+EFGH\n"
792 " IJKL\n"
793 "\\ No newline at end of file\n";
794
795 QTest::newRow("Last but one line modified, last line without a newline")
796 << chunk
797 << true
798 << patchText;
799
800 ///////////
801
802 // chunk the same here
803 patchText = header + "@@ -1,2 +1,2 @@\n"
804 "-ABCD\n"
805 "+EFGH\n"
806 " IJKL\n";
807
808 QTest::newRow("Last but one line modified, last line with a newline")
809 << chunk
810 << false
811 << patchText;
812
813 ///////////
814
815 rows.clear();
816 rows << RowData(_("ABCD"));
817 rows << RowData(TextLineData::Separator, _(""));
818 rows << RowData(_(""), _("EFGH"));
819 chunk.rows = rows;
820 patchText = header + "@@ -1,1 +1,3 @@\n"
821 " ABCD\n"
822 "+\n"
823 "+EFGH\n"
824 "\\ No newline at end of file\n";
825
826 QTest::newRow("Blank line followed by No newline")
827 << chunk
828 << true
829 << patchText;
830 }
831
testMakePatch()832 void DiffEditor::Internal::DiffEditorPlugin::testMakePatch()
833 {
834 QFETCH(ChunkData, sourceChunk);
835 QFETCH(bool, lastChunk);
836 QFETCH(QString, patchText);
837
838 const QString fileName = "a.txt";
839 const QString result = DiffUtils::makePatch(sourceChunk, fileName, fileName, lastChunk);
840
841 QCOMPARE(result, patchText);
842
843 bool ok;
844 QList<FileData> resultList = DiffUtils::readPatch(result, &ok);
845
846 QVERIFY(ok);
847 QCOMPARE(resultList.count(), 1);
848 for (int i = 0; i < resultList.count(); i++) {
849 const FileData &resultFileData = resultList.at(i);
850 QCOMPARE(resultFileData.leftFileInfo.fileName, fileName);
851 QCOMPARE(resultFileData.rightFileInfo.fileName, fileName);
852 QCOMPARE(resultFileData.chunks.count(), 1);
853 for (int j = 0; j < resultFileData.chunks.count(); j++) {
854 const ChunkData &resultChunkData = resultFileData.chunks.at(j);
855 QCOMPARE(resultChunkData.leftStartingLineNumber, sourceChunk.leftStartingLineNumber);
856 QCOMPARE(resultChunkData.rightStartingLineNumber, sourceChunk.rightStartingLineNumber);
857 QCOMPARE(resultChunkData.contextChunk, sourceChunk.contextChunk);
858 QCOMPARE(resultChunkData.rows.count(), sourceChunk.rows.count());
859 for (int k = 0; k < sourceChunk.rows.count(); k++) {
860 const RowData &sourceRowData = sourceChunk.rows.at(k);
861 const RowData &resultRowData = resultChunkData.rows.at(k);
862 QCOMPARE(resultRowData.equal, sourceRowData.equal);
863 QCOMPARE(resultRowData.leftLine.text, sourceRowData.leftLine.text);
864 QCOMPARE(resultRowData.leftLine.textLineType, sourceRowData.leftLine.textLineType);
865 QCOMPARE(resultRowData.rightLine.text, sourceRowData.rightLine.text);
866 QCOMPARE(resultRowData.rightLine.textLineType, sourceRowData.rightLine.textLineType);
867 }
868 }
869 }
870 }
871
testReadPatch_data()872 void DiffEditor::Internal::DiffEditorPlugin::testReadPatch_data()
873 {
874 QTest::addColumn<QString>("sourcePatch");
875 QTest::addColumn<QList<FileData> >("fileDataList");
876
877 QString patch = "diff --git a/src/plugins/diffeditor/diffeditor.cpp b/src/plugins/diffeditor/diffeditor.cpp\n"
878 "index eab9e9b..082c135 100644\n"
879 "--- a/src/plugins/diffeditor/diffeditor.cpp\n"
880 "+++ b/src/plugins/diffeditor/diffeditor.cpp\n"
881 "@@ -187,9 +187,6 @@ void DiffEditor::ctor()\n"
882 " m_controller = m_document->controller();\n"
883 " m_guiController = new DiffEditorGuiController(m_controller, this);\n"
884 " \n"
885 "-// m_sideBySideEditor->setDiffEditorGuiController(m_guiController);\n"
886 "-// m_unifiedEditor->setDiffEditorGuiController(m_guiController);\n"
887 "-\n"
888 " connect(m_controller, SIGNAL(cleared(QString)),\n"
889 " this, SLOT(slotCleared(QString)));\n"
890 " connect(m_controller, SIGNAL(diffContentsChanged(QList<DiffEditorController::DiffFilesContents>,QString)),\n"
891 "diff --git a/src/plugins/diffeditor/diffutils.cpp b/src/plugins/diffeditor/diffutils.cpp\n"
892 "index 2f641c9..f8ff795 100644\n"
893 "--- a/src/plugins/diffeditor/diffutils.cpp\n"
894 "+++ b/src/plugins/diffeditor/diffutils.cpp\n"
895 "@@ -464,5 +464,12 @@ QString DiffUtils::makePatch(const ChunkData &chunkData,\n"
896 " return diffText;\n"
897 " }\n"
898 " \n"
899 "+FileData DiffUtils::makeFileData(const QString &patch)\n"
900 "+{\n"
901 "+ FileData fileData;\n"
902 "+\n"
903 "+ return fileData;\n"
904 "+}\n"
905 "+\n"
906 " } // namespace Internal\n"
907 " } // namespace DiffEditor\n"
908 "diff --git a/new b/new\n"
909 "new file mode 100644\n"
910 "index 0000000..257cc56\n"
911 "--- /dev/null\n"
912 "+++ b/new\n"
913 "@@ -0,0 +1 @@\n"
914 "+foo\n"
915 "diff --git a/deleted b/deleted\n"
916 "deleted file mode 100644\n"
917 "index 257cc56..0000000\n"
918 "--- a/deleted\n"
919 "+++ /dev/null\n"
920 "@@ -1 +0,0 @@\n"
921 "-foo\n"
922 "diff --git a/empty b/empty\n"
923 "new file mode 100644\n"
924 "index 0000000..e69de29\n"
925 "diff --git a/empty b/empty\n"
926 "deleted file mode 100644\n"
927 "index e69de29..0000000\n"
928 "diff --git a/file a.txt b/file b.txt\n"
929 "similarity index 99%\n"
930 "copy from file a.txt\n"
931 "copy to file b.txt\n"
932 "index 1234567..9876543\n"
933 "--- a/file a.txt\n"
934 "+++ b/file b.txt\n"
935 "@@ -20,3 +20,3 @@\n"
936 " A\n"
937 "-B\n"
938 "+C\n"
939 " D\n"
940 "diff --git a/file a.txt b/file b.txt\n"
941 "similarity index 99%\n"
942 "rename from file a.txt\n"
943 "rename to file b.txt\n"
944 "diff --git a/file.txt b/file.txt\n"
945 "old mode 100644\n"
946 "new mode 100755\n"
947 "index 1234567..9876543\n"
948 "--- a/file.txt\n"
949 "+++ b/file.txt\n"
950 "@@ -20,3 +20,3 @@\n"
951 " A\n"
952 "-B\n"
953 "+C\n"
954 " D\n"
955 ;
956
957 FileData fileData1;
958 fileData1.leftFileInfo = DiffFileInfo("src/plugins/diffeditor/diffeditor.cpp", "eab9e9b");
959 fileData1.rightFileInfo = DiffFileInfo("src/plugins/diffeditor/diffeditor.cpp", "082c135");
960 ChunkData chunkData1;
961 chunkData1.leftStartingLineNumber = 186;
962 chunkData1.rightStartingLineNumber = 186;
963 QList<RowData> rows1;
964 rows1 << RowData(_(" m_controller = m_document->controller();"));
965 rows1 << RowData(_(" m_guiController = new DiffEditorGuiController(m_controller, this);"));
966 rows1 << RowData(_(""));
967 rows1 << RowData(_("// m_sideBySideEditor->setDiffEditorGuiController(m_guiController);"), TextLineData::Separator);
968 rows1 << RowData(_("// m_unifiedEditor->setDiffEditorGuiController(m_guiController);"), TextLineData::Separator);
969 rows1 << RowData(_(""), TextLineData::Separator);
970 rows1 << RowData(_(" connect(m_controller, SIGNAL(cleared(QString)),"));
971 rows1 << RowData(_(" this, SLOT(slotCleared(QString)));"));
972 rows1 << RowData(_(" connect(m_controller, SIGNAL(diffContentsChanged(QList<DiffEditorController::DiffFilesContents>,QString)),"));
973 chunkData1.rows = rows1;
974 fileData1.chunks << chunkData1;
975
976 FileData fileData2;
977 fileData2.leftFileInfo = DiffFileInfo(_("src/plugins/diffeditor/diffutils.cpp"), _("2f641c9"));
978 fileData2.rightFileInfo = DiffFileInfo(_("src/plugins/diffeditor/diffutils.cpp"), _("f8ff795"));
979 ChunkData chunkData2;
980 chunkData2.leftStartingLineNumber = 463;
981 chunkData2.rightStartingLineNumber = 463;
982 QList<RowData> rows2;
983 rows2 << RowData(_(" return diffText;"));
984 rows2 << RowData(_("}"));
985 rows2 << RowData(_(""));
986 rows2 << RowData(TextLineData::Separator, _("FileData DiffUtils::makeFileData(const QString &patch)"));
987 rows2 << RowData(TextLineData::Separator, _("{"));
988 rows2 << RowData(TextLineData::Separator, _(" FileData fileData;"));
989 rows2 << RowData(TextLineData::Separator, _(""));
990 rows2 << RowData(TextLineData::Separator, _(" return fileData;"));
991 rows2 << RowData(TextLineData::Separator, _("}"));
992 rows2 << RowData(TextLineData::Separator, _(""));
993 rows2 << RowData(_("} // namespace Internal"));
994 rows2 << RowData(_("} // namespace DiffEditor"));
995 chunkData2.rows = rows2;
996 fileData2.chunks << chunkData2;
997
998 FileData fileData3;
999 fileData3.leftFileInfo = DiffFileInfo("new", "0000000");
1000 fileData3.rightFileInfo = DiffFileInfo("new", "257cc56");
1001 fileData3.fileOperation = FileData::NewFile;
1002 ChunkData chunkData3;
1003 chunkData3.leftStartingLineNumber = -1;
1004 chunkData3.rightStartingLineNumber = 0;
1005 QList<RowData> rows3;
1006 rows3 << RowData(TextLineData::Separator, _("foo"));
1007 TextLineData textLineData3(TextLineData::TextLine);
1008 rows3 << RowData(TextLineData::Separator, textLineData3);
1009 chunkData3.rows = rows3;
1010 fileData3.chunks << chunkData3;
1011
1012 FileData fileData4;
1013 fileData4.leftFileInfo = DiffFileInfo("deleted", "257cc56");
1014 fileData4.rightFileInfo = DiffFileInfo("deleted", "0000000");
1015 fileData4.fileOperation = FileData::DeleteFile;
1016 ChunkData chunkData4;
1017 chunkData4.leftStartingLineNumber = 0;
1018 chunkData4.rightStartingLineNumber = -1;
1019 QList<RowData> rows4;
1020 rows4 << RowData(_("foo"), TextLineData::Separator);
1021 TextLineData textLineData4(TextLineData::TextLine);
1022 rows4 << RowData(textLineData4, TextLineData::Separator);
1023 chunkData4.rows = rows4;
1024 fileData4.chunks << chunkData4;
1025
1026 FileData fileData5;
1027 fileData5.leftFileInfo = DiffFileInfo("empty", "0000000");
1028 fileData5.rightFileInfo = DiffFileInfo("empty", "e69de29");
1029 fileData5.fileOperation = FileData::NewFile;
1030
1031 FileData fileData6;
1032 fileData6.leftFileInfo = DiffFileInfo("empty", "e69de29");
1033 fileData6.rightFileInfo = DiffFileInfo("empty", "0000000");
1034 fileData6.fileOperation = FileData::DeleteFile;
1035
1036 FileData fileData7;
1037 fileData7.leftFileInfo = DiffFileInfo("file a.txt", "1234567");
1038 fileData7.rightFileInfo = DiffFileInfo("file b.txt", "9876543");
1039 fileData7.fileOperation = FileData::CopyFile;
1040 ChunkData chunkData7;
1041 chunkData7.leftStartingLineNumber = 19;
1042 chunkData7.rightStartingLineNumber = 19;
1043 QList<RowData> rows7;
1044 rows7 << RowData(_("A"));
1045 rows7 << RowData(_("B"), _("C"));
1046 rows7 << RowData(_("D"));
1047 chunkData7.rows = rows7;
1048 fileData7.chunks << chunkData7;
1049
1050 FileData fileData8;
1051 fileData8.leftFileInfo = DiffFileInfo("file a.txt");
1052 fileData8.rightFileInfo = DiffFileInfo("file b.txt");
1053 fileData8.fileOperation = FileData::RenameFile;
1054
1055 FileData fileData9;
1056 fileData9.leftFileInfo = DiffFileInfo("file.txt", "1234567");
1057 fileData9.rightFileInfo = DiffFileInfo("file.txt", "9876543");
1058 fileData9.chunks << chunkData7;
1059 QList<FileData> fileDataList1;
1060 fileDataList1 << fileData1 << fileData2 << fileData3 << fileData4 << fileData5
1061 << fileData6 << fileData7 << fileData8 << fileData9;
1062
1063 QTest::newRow("Git patch") << patch
1064 << fileDataList1;
1065
1066 //////////////
1067
1068 patch = "diff --git a/file foo.txt b/file foo.txt\n"
1069 "index 1234567..9876543 100644\n"
1070 "--- a/file foo.txt\n"
1071 "+++ b/file foo.txt\n"
1072 "@@ -50,4 +50,5 @@ void DiffEditor::ctor()\n"
1073 " A\n"
1074 " B\n"
1075 " C\n"
1076 "+\n";
1077
1078 fileData1.leftFileInfo = DiffFileInfo("file foo.txt", "1234567");
1079 fileData1.rightFileInfo = DiffFileInfo("file foo.txt", "9876543");
1080 fileData1.fileOperation = FileData::ChangeFile;
1081 chunkData1.leftStartingLineNumber = 49;
1082 chunkData1.rightStartingLineNumber = 49;
1083 rows1.clear();
1084 rows1 << RowData(_("A"));
1085 rows1 << RowData(_("B"));
1086 rows1 << RowData(_("C"));
1087 rows1 << RowData(TextLineData::Separator, _(""));
1088 chunkData1.rows = rows1;
1089 fileData1.chunks.clear();
1090 fileData1.chunks << chunkData1;
1091
1092 QList<FileData> fileDataList2;
1093 fileDataList2 << fileData1;
1094
1095 QTest::newRow("Added line") << patch
1096 << fileDataList2;
1097
1098 //////////////
1099
1100 patch = "diff --git a/file foo.txt b/file foo.txt\n"
1101 "index 1234567..9876543 100644\n"
1102 "--- a/file foo.txt\n"
1103 "+++ b/file foo.txt\n"
1104 "@@ -1,1 +1,1 @@\n"
1105 "-ABCD\n"
1106 "\\ No newline at end of file\n"
1107 "+ABCD\n";
1108
1109 fileData1.leftFileInfo = DiffFileInfo("file foo.txt", "1234567");
1110 fileData1.rightFileInfo = DiffFileInfo("file foo.txt", "9876543");
1111 fileData1.fileOperation = FileData::ChangeFile;
1112 chunkData1.leftStartingLineNumber = 0;
1113 chunkData1.rightStartingLineNumber = 0;
1114 rows1.clear();
1115 rows1 << RowData(_("ABCD"));
1116 rows1 << RowData(TextLineData::Separator, _(""));
1117 chunkData1.rows = rows1;
1118 fileData1.chunks.clear();
1119 fileData1.chunks << chunkData1;
1120
1121 QList<FileData> fileDataList3;
1122 fileDataList3 << fileData1;
1123
1124 QTest::newRow("Last newline added to a line without newline") << patch
1125 << fileDataList3;
1126
1127 patch = "diff --git a/difftest.txt b/difftest.txt\n"
1128 "index 1234567..9876543 100644\n"
1129 "--- a/difftest.txt\n"
1130 "+++ b/difftest.txt\n"
1131 "@@ -2,5 +2,5 @@ void func()\n"
1132 " A\n"
1133 " B\n"
1134 "-C\n"
1135 "+Z\n"
1136 " D\n"
1137 " \n"
1138 "@@ -9,2 +9,4 @@ void OtherFunc()\n"
1139 " \n"
1140 " D\n"
1141 "+E\n"
1142 "+F\n"
1143 ;
1144
1145 fileData1.leftFileInfo = DiffFileInfo("difftest.txt", "1234567");
1146 fileData1.rightFileInfo = DiffFileInfo("difftest.txt", "9876543");
1147 fileData1.fileOperation = FileData::ChangeFile;
1148 chunkData1.leftStartingLineNumber = 1;
1149 chunkData1.rightStartingLineNumber = 1;
1150 rows1.clear();
1151 rows1 << RowData(_("A"));
1152 rows1 << RowData(_("B"));
1153 rows1 << RowData(_("C"), _("Z"));
1154 rows1 << RowData(_("D"));
1155 rows1 << RowData(_(""));
1156 chunkData1.rows = rows1;
1157
1158 chunkData2.leftStartingLineNumber = 8;
1159 chunkData2.rightStartingLineNumber = 8;
1160 rows2.clear();
1161 rows2 << RowData(_(""));
1162 rows2 << RowData(_("D"));
1163 rows2 << RowData(TextLineData::Separator, _("E"));
1164 rows2 << RowData(TextLineData::Separator, _("F"));
1165 chunkData2.rows = rows2;
1166 fileData1.chunks.clear();
1167 fileData1.chunks << chunkData1;
1168 fileData1.chunks << chunkData2;
1169
1170 QList<FileData> fileDataList4;
1171 fileDataList4 << fileData1;
1172
1173 QTest::newRow("2 chunks - first ends with blank line") << patch
1174 << fileDataList4;
1175
1176 //////////////
1177
1178 patch = "diff --git a/file foo.txt b/file foo.txt\n"
1179 "index 1234567..9876543 100644\n"
1180 "--- a/file foo.txt\n"
1181 "+++ b/file foo.txt\n"
1182 "@@ -1,1 +1,3 @@ void DiffEditor::ctor()\n"
1183 " ABCD\n"
1184 "+\n"
1185 "+EFGH\n"
1186 "\\ No newline at end of file\n";
1187
1188 fileData1.leftFileInfo = DiffFileInfo("file foo.txt", "1234567");
1189 fileData1.rightFileInfo = DiffFileInfo("file foo.txt", "9876543");
1190 fileData1.fileOperation = FileData::ChangeFile;
1191 chunkData1.leftStartingLineNumber = 0;
1192 chunkData1.rightStartingLineNumber = 0;
1193 rows1.clear();
1194 rows1 << RowData(_("ABCD"));
1195 rows1 << RowData(TextLineData::Separator, _(""));
1196 rows1 << RowData(_(""), _("EFGH"));
1197 chunkData1.rows = rows1;
1198 fileData1.chunks.clear();
1199 fileData1.chunks << chunkData1;
1200
1201 QList<FileData> fileDataList5;
1202 fileDataList5 << fileData1;
1203
1204 QTest::newRow("Blank line followed by No newline") << patch
1205 << fileDataList5;
1206
1207 //////////////
1208
1209 // Based on 953cdb97
1210 patch = "diff --git a/src/plugins/texteditor/basetextdocument.h b/src/plugins/texteditor/textdocument.h\n"
1211 "similarity index 100%\n"
1212 "rename from src/plugins/texteditor/basetextdocument.h\n"
1213 "rename to src/plugins/texteditor/textdocument.h\n"
1214 "diff --git a/src/plugins/texteditor/basetextdocumentlayout.cpp b/src/plugins/texteditor/textdocumentlayout.cpp\n"
1215 "similarity index 79%\n"
1216 "rename from src/plugins/texteditor/basetextdocumentlayout.cpp\n"
1217 "rename to src/plugins/texteditor/textdocumentlayout.cpp\n"
1218 "index 0121933..01cc3a0 100644\n"
1219 "--- a/src/plugins/texteditor/basetextdocumentlayout.cpp\n"
1220 "+++ b/src/plugins/texteditor/textdocumentlayout.cpp\n"
1221 "@@ -2,5 +2,5 @@ void func()\n"
1222 " A\n"
1223 " B\n"
1224 "-C\n"
1225 "+Z\n"
1226 " D\n"
1227 " \n"
1228 ;
1229
1230 fileData1 = FileData();
1231 fileData1.leftFileInfo = DiffFileInfo("src/plugins/texteditor/basetextdocument.h");
1232 fileData1.rightFileInfo = DiffFileInfo("src/plugins/texteditor/textdocument.h");
1233 fileData1.fileOperation = FileData::RenameFile;
1234 fileData2 = FileData();
1235 fileData2.leftFileInfo = DiffFileInfo("src/plugins/texteditor/basetextdocumentlayout.cpp", "0121933");
1236 fileData2.rightFileInfo = DiffFileInfo("src/plugins/texteditor/textdocumentlayout.cpp", "01cc3a0");
1237 fileData2.fileOperation = FileData::RenameFile;
1238 chunkData2.leftStartingLineNumber = 1;
1239 chunkData2.rightStartingLineNumber = 1;
1240 rows2.clear();
1241 rows2 << RowData(_("A"));
1242 rows2 << RowData(_("B"));
1243 rows2 << RowData(_("C"), _("Z"));
1244 rows2 << RowData(_("D"));
1245 rows2 << RowData(_(""));
1246 chunkData2.rows = rows2;
1247 fileData2.chunks.clear();
1248 fileData2.chunks << chunkData2;
1249
1250 QList<FileData> fileDataList6;
1251 fileDataList6 << fileData1 << fileData2;
1252
1253 QTest::newRow("Multiple renames") << patch
1254 << fileDataList6;
1255
1256 //////////////
1257
1258 // Dirty submodule
1259 patch = "diff --git a/src/shared/qbs b/src/shared/qbs\n"
1260 "--- a/src/shared/qbs\n"
1261 "+++ b/src/shared/qbs\n"
1262 "@@ -1 +1 @@\n"
1263 "-Subproject commit eda76354077a427d692fee05479910de31040d3f\n"
1264 "+Subproject commit eda76354077a427d692fee05479910de31040d3f-dirty\n"
1265 ;
1266 fileData1 = FileData();
1267 fileData1.leftFileInfo = DiffFileInfo("src/shared/qbs");
1268 fileData1.rightFileInfo = DiffFileInfo("src/shared/qbs");
1269 chunkData1.leftStartingLineNumber = 0;
1270 chunkData1.rightStartingLineNumber = 0;
1271 rows1.clear();
1272 rows1 << RowData(_("Subproject commit eda76354077a427d692fee05479910de31040d3f"),
1273 _("Subproject commit eda76354077a427d692fee05479910de31040d3f-dirty"));
1274 chunkData1.rows = rows1;
1275 fileData1.chunks.clear();
1276 fileData1.chunks << chunkData1;
1277
1278 QList<FileData> fileDataList7;
1279 fileDataList7 << fileData1;
1280 QTest::newRow("Dirty submodule") << patch
1281 << fileDataList7;
1282
1283 //////////////
1284 patch = "diff --git a/demos/arthurplugin/arthurplugin.pro b/demos/arthurplugin/arthurplugin.pro\n"
1285 "new file mode 100644\n"
1286 "index 0000000..c5132b4\n"
1287 "--- /dev/null\n"
1288 "+++ b/demos/arthurplugin/arthurplugin.pro\n"
1289 "@@ -0,0 +1 @@\n"
1290 "+XXX\n"
1291 "diff --git a/demos/arthurplugin/bg1.jpg b/demos/arthurplugin/bg1.jpg\n"
1292 "new file mode 100644\n"
1293 "index 0000000..dfc7cee\n"
1294 "Binary files /dev/null and b/demos/arthurplugin/bg1.jpg differ\n"
1295 "diff --git a/demos/arthurplugin/flower.jpg b/demos/arthurplugin/flower.jpg\n"
1296 "new file mode 100644\n"
1297 "index 0000000..f8e022c\n"
1298 "Binary files /dev/null and b/demos/arthurplugin/flower.jpg differ\n"
1299 ;
1300
1301 fileData1 = FileData();
1302 fileData1.leftFileInfo = DiffFileInfo("demos/arthurplugin/arthurplugin.pro", "0000000");
1303 fileData1.rightFileInfo = DiffFileInfo("demos/arthurplugin/arthurplugin.pro", "c5132b4");
1304 fileData1.fileOperation = FileData::NewFile;
1305 chunkData1 = ChunkData();
1306 chunkData1.leftStartingLineNumber = -1;
1307 chunkData1.rightStartingLineNumber = 0;
1308 rows1.clear();
1309 rows1 << RowData(TextLineData::Separator, _("XXX"));
1310 rows1 << RowData(TextLineData::Separator, TextLineData(TextLineData::TextLine));
1311 chunkData1.rows = rows1;
1312 fileData1.chunks << chunkData1;
1313 fileData2 = FileData();
1314 fileData2.leftFileInfo = DiffFileInfo("demos/arthurplugin/bg1.jpg", "0000000");
1315 fileData2.rightFileInfo = DiffFileInfo("demos/arthurplugin/bg1.jpg", "dfc7cee");
1316 fileData2.fileOperation = FileData::NewFile;
1317 fileData2.binaryFiles = true;
1318 fileData3 = FileData();
1319 fileData3.leftFileInfo = DiffFileInfo("demos/arthurplugin/flower.jpg", "0000000");
1320 fileData3.rightFileInfo = DiffFileInfo("demos/arthurplugin/flower.jpg", "f8e022c");
1321 fileData3.fileOperation = FileData::NewFile;
1322 fileData3.binaryFiles = true;
1323
1324 QList<FileData> fileDataList8;
1325 fileDataList8 << fileData1 << fileData2 << fileData3;
1326
1327 QTest::newRow("Binary files") << patch
1328 << fileDataList8;
1329
1330 //////////////
1331 patch = "diff --git a/script.sh b/script.sh\n"
1332 "old mode 100644\n"
1333 "new mode 100755\n"
1334 ;
1335
1336 fileData1 = FileData();
1337 fileData1.leftFileInfo = DiffFileInfo("script.sh");
1338 fileData1.rightFileInfo = DiffFileInfo("script.sh");
1339 fileData1.fileOperation = FileData::ChangeMode;
1340
1341 QList<FileData> fileDataList9;
1342 fileDataList9 << fileData1;
1343
1344 QTest::newRow("Mode change") << patch << fileDataList9;
1345
1346 //////////////
1347
1348 // Subversion New
1349 patch = "Index: src/plugins/subversion/subversioneditor.cpp\n"
1350 "===================================================================\n"
1351 "--- src/plugins/subversion/subversioneditor.cpp\t(revision 0)\n"
1352 "+++ src/plugins/subversion/subversioneditor.cpp\t(revision 0)\n"
1353 "@@ -0,0 +125 @@\n\n";
1354 fileData1 = FileData();
1355 fileData1.leftFileInfo = DiffFileInfo("src/plugins/subversion/subversioneditor.cpp");
1356 fileData1.rightFileInfo = DiffFileInfo("src/plugins/subversion/subversioneditor.cpp");
1357 chunkData1 = ChunkData();
1358 chunkData1.leftStartingLineNumber = -1;
1359 chunkData1.rightStartingLineNumber = 124;
1360 fileData1.chunks << chunkData1;
1361 QList<FileData> fileDataList21;
1362 fileDataList21 << fileData1;
1363 QTest::newRow("Subversion New") << patch
1364 << fileDataList21;
1365
1366 //////////////
1367
1368 // Subversion Deleted
1369 patch = "Index: src/plugins/subversion/subversioneditor.cpp\n"
1370 "===================================================================\n"
1371 "--- src/plugins/subversion/subversioneditor.cpp\t(revision 42)\n"
1372 "+++ src/plugins/subversion/subversioneditor.cpp\t(working copy)\n"
1373 "@@ -1,125 +0,0 @@\n\n";
1374 fileData1 = FileData();
1375 fileData1.leftFileInfo = DiffFileInfo("src/plugins/subversion/subversioneditor.cpp");
1376 fileData1.rightFileInfo = DiffFileInfo("src/plugins/subversion/subversioneditor.cpp");
1377 chunkData1 = ChunkData();
1378 chunkData1.leftStartingLineNumber = 0;
1379 chunkData1.rightStartingLineNumber = -1;
1380 fileData1.chunks << chunkData1;
1381 QList<FileData> fileDataList22;
1382 fileDataList22 << fileData1;
1383 QTest::newRow("Subversion Deleted") << patch
1384 << fileDataList22;
1385
1386 //////////////
1387
1388 // Subversion Normal
1389 patch = "Index: src/plugins/subversion/subversioneditor.cpp\n"
1390 "===================================================================\n"
1391 "--- src/plugins/subversion/subversioneditor.cpp\t(revision 42)\n"
1392 "+++ src/plugins/subversion/subversioneditor.cpp\t(working copy)\n"
1393 "@@ -120,7 +120,7 @@\n\n";
1394 fileData1 = FileData();
1395 fileData1.leftFileInfo = DiffFileInfo("src/plugins/subversion/subversioneditor.cpp");
1396 fileData1.rightFileInfo = DiffFileInfo("src/plugins/subversion/subversioneditor.cpp");
1397 chunkData1 = ChunkData();
1398 chunkData1.leftStartingLineNumber = 119;
1399 chunkData1.rightStartingLineNumber = 119;
1400 fileData1.chunks << chunkData1;
1401 QList<FileData> fileDataList23;
1402 fileDataList23 << fileData1;
1403 QTest::newRow("Subversion Normal") << patch
1404 << fileDataList23;
1405 }
1406
testReadPatch()1407 void DiffEditor::Internal::DiffEditorPlugin::testReadPatch()
1408 {
1409 QFETCH(QString, sourcePatch);
1410 QFETCH(QList<FileData>, fileDataList);
1411
1412 bool ok;
1413 const QList<FileData> &result = DiffUtils::readPatch(sourcePatch, &ok);
1414
1415 QVERIFY(ok);
1416 QCOMPARE(result.count(), fileDataList.count());
1417 for (int i = 0; i < fileDataList.count(); i++) {
1418 const FileData &origFileData = fileDataList.at(i);
1419 const FileData &resultFileData = result.at(i);
1420 QCOMPARE(resultFileData.leftFileInfo.fileName, origFileData.leftFileInfo.fileName);
1421 QCOMPARE(resultFileData.leftFileInfo.typeInfo, origFileData.leftFileInfo.typeInfo);
1422 QCOMPARE(resultFileData.rightFileInfo.fileName, origFileData.rightFileInfo.fileName);
1423 QCOMPARE(resultFileData.rightFileInfo.typeInfo, origFileData.rightFileInfo.typeInfo);
1424 QCOMPARE(resultFileData.chunks.count(), origFileData.chunks.count());
1425 QCOMPARE(resultFileData.fileOperation, origFileData.fileOperation);
1426 for (int j = 0; j < origFileData.chunks.count(); j++) {
1427 const ChunkData &origChunkData = origFileData.chunks.at(j);
1428 const ChunkData &resultChunkData = resultFileData.chunks.at(j);
1429 QCOMPARE(resultChunkData.leftStartingLineNumber, origChunkData.leftStartingLineNumber);
1430 QCOMPARE(resultChunkData.rightStartingLineNumber, origChunkData.rightStartingLineNumber);
1431 QCOMPARE(resultChunkData.contextChunk, origChunkData.contextChunk);
1432 QCOMPARE(resultChunkData.rows.count(), origChunkData.rows.count());
1433 for (int k = 0; k < origChunkData.rows.count(); k++) {
1434 const RowData &origRowData = origChunkData.rows.at(k);
1435 const RowData &resultRowData = resultChunkData.rows.at(k);
1436 QCOMPARE(resultRowData.equal, origRowData.equal);
1437 QCOMPARE(resultRowData.leftLine.text, origRowData.leftLine.text);
1438 QCOMPARE(resultRowData.leftLine.textLineType, origRowData.leftLine.textLineType);
1439 QCOMPARE(resultRowData.rightLine.text, origRowData.rightLine.text);
1440 QCOMPARE(resultRowData.rightLine.textLineType, origRowData.rightLine.textLineType);
1441 }
1442 }
1443 }
1444 }
1445
1446 using ListOfStringPairs = QList<QPair<QString, QString>>;
1447
testFilterPatch_data()1448 void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch_data()
1449 {
1450 QTest::addColumn<ChunkData>("chunk");
1451 QTest::addColumn<ListOfStringPairs>("rows");
1452 QTest::addColumn<ChunkSelection>("selection");
1453 QTest::addColumn<bool>("revert");
1454
1455 auto createChunk = []() {
1456 ChunkData chunk;
1457 chunk.contextInfo = "void DiffEditor::ctor()";
1458 chunk.contextChunk = false;
1459 chunk.leftStartingLineNumber = 49;
1460 chunk.rightStartingLineNumber = 49;
1461 return chunk;
1462 };
1463 auto appendRow = [](ChunkData *chunk, const QString &left, const QString &right) {
1464 RowData row;
1465 row.equal = (left == right);
1466 row.leftLine.text = left;
1467 row.leftLine.textLineType = left.isEmpty() ? TextLineData::Separator : TextLineData::TextLine;
1468 row.rightLine.text = right;
1469 row.rightLine.textLineType = right.isEmpty() ? TextLineData::Separator : TextLineData::TextLine;
1470 chunk->rows.append(row);
1471 };
1472 ChunkData chunk;
1473 ListOfStringPairs rows;
1474
1475 chunk = createChunk();
1476 appendRow(&chunk, "A", "A"); // 50
1477 appendRow(&chunk, "", "B"); // 51 +
1478 appendRow(&chunk, "C", "C"); // 52
1479 rows = ListOfStringPairs {
1480 {"A", "A"},
1481 {"", "B"},
1482 {"C", "C"}
1483 };
1484 QTest::newRow("one added") << chunk << rows << ChunkSelection() << false;
1485
1486 chunk = createChunk();
1487 appendRow(&chunk, "A", "A"); // 50
1488 appendRow(&chunk, "B", ""); // 51 -
1489 appendRow(&chunk, "C", "C"); // 52
1490 rows = ListOfStringPairs {
1491 {"A", "A"},
1492 {"B", ""},
1493 {"C", "C"}
1494 };
1495 QTest::newRow("one removed") << chunk << rows << ChunkSelection() << false;
1496
1497 chunk = createChunk();
1498 appendRow(&chunk, "A", "A"); // 50
1499 appendRow(&chunk, "", "B"); // 51
1500 appendRow(&chunk, "", "C"); // 52 +
1501 appendRow(&chunk, "", "D"); // 53 +
1502 appendRow(&chunk, "", "E"); // 54
1503 appendRow(&chunk, "F", "F"); // 55
1504 rows = ListOfStringPairs {
1505 {"A", "A"},
1506 {"", "C"},
1507 {"", "D"},
1508 {"F", "F"}
1509 };
1510 QTest::newRow("stage selected added") << chunk << rows << ChunkSelection({2, 3}, {2, 3}) << false;
1511
1512 chunk = createChunk();
1513 appendRow(&chunk, "A", "A"); // 50
1514 appendRow(&chunk, "", "B"); // 51 +
1515 appendRow(&chunk, "C", "D"); // 52
1516 appendRow(&chunk, "E", "E"); // 53
1517 rows = ListOfStringPairs {
1518 {"A", "A"},
1519 {"", "B"},
1520 {"C", "C"},
1521 {"E", "E"}
1522 };
1523 QTest::newRow("stage selected added keep changed") << chunk << rows << ChunkSelection({1}, {1}) << false;
1524
1525 chunk = createChunk();
1526 appendRow(&chunk, "A", "A"); // 50
1527 appendRow(&chunk, "B", ""); // 51
1528 appendRow(&chunk, "C", ""); // 52 -
1529 appendRow(&chunk, "D", ""); // 53 -
1530 appendRow(&chunk, "E", ""); // 54
1531 appendRow(&chunk, "F", "F"); // 55
1532 rows = ListOfStringPairs {
1533 {"A", "A"},
1534 {"B", "B"},
1535 {"C", ""},
1536 {"D", ""},
1537 {"E", "E"},
1538 {"F", "F"}
1539 };
1540 QTest::newRow("stage selected removed") << chunk << rows << ChunkSelection({2, 3}, {2, 3}) << false;
1541
1542 chunk = createChunk();
1543 appendRow(&chunk, "A", "A"); // 50
1544 appendRow(&chunk, "B", ""); // 51
1545 appendRow(&chunk, "C", ""); // 52 -
1546 appendRow(&chunk, "", "D"); // 53 +
1547 appendRow(&chunk, "", "E"); // 54
1548 appendRow(&chunk, "F", "F"); // 55
1549 rows = ListOfStringPairs {
1550 {"A", "A"},
1551 {"B", "B"},
1552 {"C", ""},
1553 {"", "D"},
1554 {"F", "F"}
1555 };
1556 QTest::newRow("stage selected added/removed") << chunk << rows << ChunkSelection({2, 3}, {2, 3}) << false;
1557
1558 chunk = createChunk();
1559 appendRow(&chunk, "A", "A"); // 50
1560 appendRow(&chunk, "B", "C"); // 51 -/+
1561 appendRow(&chunk, "D", "D"); // 52
1562 rows = ListOfStringPairs {
1563 {"A", "A"},
1564 {"B", "C"},
1565 {"D", "D"}
1566 };
1567 QTest::newRow("stage modified row") << chunk << rows << ChunkSelection({1}, {1}) << false;
1568
1569 chunk = createChunk();
1570 appendRow(&chunk, "A", "A"); // 50
1571 appendRow(&chunk, "B", "C"); // 51 -/+
1572 appendRow(&chunk, "D", "D"); // 52
1573 rows = ListOfStringPairs {
1574 {"A", "A"},
1575 {"B", "C"},
1576 {"D", "D"}
1577 };
1578 QTest::newRow("stage modified and unmodified rows") << chunk << rows << ChunkSelection({0, 1, 2}, {0, 1, 2}) << false;
1579
1580 chunk = createChunk();
1581 appendRow(&chunk, "A", "A"); // 50
1582 appendRow(&chunk, "B", "C"); // 51 -/+
1583 appendRow(&chunk, "D", "D"); // 52
1584 rows = ListOfStringPairs {
1585 {"A", "A"},
1586 {"B", "C"},
1587 {"D", "D"}
1588 };
1589 QTest::newRow("stage unmodified left rows") << chunk << rows << ChunkSelection({0, 1, 2}, {1}) << false;
1590
1591 chunk = createChunk();
1592 appendRow(&chunk, "A", "A"); // 50
1593 appendRow(&chunk, "B", "C"); // 51 -/+
1594 appendRow(&chunk, "D", "D"); // 52
1595 rows = ListOfStringPairs {
1596 {"A", "A"},
1597 {"B", "C"},
1598 {"D", "D"}
1599 };
1600 QTest::newRow("stage unmodified right rows") << chunk << rows << ChunkSelection({1}, {0, 1, 2}) << false;
1601
1602 chunk = createChunk();
1603 appendRow(&chunk, "A", "A"); // 50
1604 appendRow(&chunk, "B", "C"); // 51 -/+
1605 appendRow(&chunk, "D", "D"); // 52
1606 rows = ListOfStringPairs {
1607 {"A", "A"},
1608 {"B", ""},
1609 {"D", "D"}
1610 };
1611 QTest::newRow("stage left only") << chunk << rows << ChunkSelection({1}, {}) << false;
1612
1613 chunk = createChunk();
1614 appendRow(&chunk, "A", "A"); // 50
1615 appendRow(&chunk, "B", "C"); // 51 -/+
1616 appendRow(&chunk, "D", "D"); // 52
1617 rows = ListOfStringPairs {
1618 {"A", "A"},
1619 {"B", "B"},
1620 {"", "C"},
1621 {"D", "D"}
1622 };
1623 QTest::newRow("stage right only") << chunk << rows << ChunkSelection({}, {1}) << false;
1624
1625 chunk = createChunk();
1626 appendRow(&chunk, "A", "A"); // 50
1627 appendRow(&chunk, "B", "C"); // 51 -/+
1628 appendRow(&chunk, "D", "D"); // 52
1629 rows = ListOfStringPairs {
1630 {"A", "A"},
1631 {"B", "C"},
1632 {"D", "D"}
1633 };
1634 QTest::newRow("stage modified row and revert") << chunk << rows << ChunkSelection({1}, {1}) << true;
1635
1636 chunk = createChunk();
1637 appendRow(&chunk, "A", "A"); // 50
1638 appendRow(&chunk, "B", "C"); // 51 -/+
1639 appendRow(&chunk, "D", "D"); // 52
1640 rows = ListOfStringPairs {
1641 {"A", "A"},
1642 {"B", ""},
1643 {"C", "C"},
1644 {"D", "D"}
1645 };
1646 // symmetric to: "stage right only"
1647 QTest::newRow("stage left only and revert") << chunk << rows << ChunkSelection({1}, {}) << true;
1648
1649 chunk = createChunk();
1650 appendRow(&chunk, "A", "A"); // 50
1651 appendRow(&chunk, "B", "C"); // 51 -/+
1652 appendRow(&chunk, "D", "D"); // 52
1653 rows = ListOfStringPairs {
1654 {"A", "A"},
1655 {"", "C"},
1656 {"D", "D"}
1657 };
1658 // symmetric to: "stage left only"
1659 QTest::newRow("stage right only and revert") << chunk << rows << ChunkSelection({}, {1}) << true;
1660
1661 }
1662
testFilterPatch()1663 void DiffEditor::Internal::DiffEditorPlugin::testFilterPatch()
1664 {
1665 QFETCH(ChunkData, chunk);
1666 QFETCH(ListOfStringPairs, rows);
1667 QFETCH(ChunkSelection, selection);
1668 QFETCH(bool, revert);
1669
1670 const ChunkData result = DiffEditorDocument::filterChunk(chunk, selection, revert);
1671 QCOMPARE(result.rows.size(), rows.size());
1672 for (int i = 0; i < rows.size(); ++i) {
1673 QCOMPARE(result.rows.at(i).leftLine.text, rows.at(i).first);
1674 QCOMPARE(result.rows.at(i).rightLine.text, rows.at(i).second);
1675 }
1676 }
1677
1678 #endif // WITH_TESTS
1679
1680 #include "diffeditorplugin.moc"
1681