File indexing completed on 2024-04-21 05:44:08

0001 /*
0002     SPDX-FileCopyrightText: 2001-2009 Otto Bruggeman <bruggie@gmail.com>
0003     SPDX-FileCopyrightText: 2001-2003 John Firebaugh <jfirebaugh@kde.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "diffmodel.h"
0009 #include "diffmodel_p.h"
0010 
0011 // lib
0012 #include "difference.h"
0013 #include "levenshteintable.h"
0014 #include "parserbase.h"
0015 #include "stringlistpair.h"
0016 #include <komparediff2_logging.h>
0017 // Std
0018 #include <algorithm>
0019 
0020 using namespace KompareDiff2;
0021 
0022 /**  */
0023 DiffModel::DiffModel(const QString &source, const QString &destination)
0024     : d_ptr(new DiffModelPrivate(source, destination))
0025 {
0026     Q_D(DiffModel);
0027 
0028     d->splitSourceInPathAndFileName();
0029     d->splitDestinationInPathAndFileName();
0030 }
0031 
0032 DiffModel::DiffModel()
0033     : d_ptr(new DiffModelPrivate())
0034 {
0035 }
0036 
0037 DiffModel::~DiffModel() = default;
0038 
0039 DiffModel &DiffModel::operator=(const DiffModel &model)
0040 {
0041     Q_D(DiffModel);
0042 
0043     if (&model != this) // Guard from self-assignment
0044     {
0045         *d = *model.d_ptr;
0046     }
0047 
0048     return *this;
0049 }
0050 
0051 bool DiffModel::operator<(const DiffModel &model) const
0052 {
0053     if (localeAwareCompareSource(model) < 0)
0054         return true;
0055     return false;
0056 }
0057 
0058 int DiffModel::hunkCount() const
0059 {
0060     Q_D(const DiffModel);
0061 
0062     return d->hunks.count();
0063 }
0064 
0065 int DiffModel::differenceCount() const
0066 {
0067     Q_D(const DiffModel);
0068 
0069     return d->differences.count();
0070 }
0071 
0072 int DiffModel::appliedCount() const
0073 {
0074     Q_D(const DiffModel);
0075 
0076     return d->appliedCount;
0077 }
0078 
0079 DiffHunk *DiffModel::hunkAt(int i)
0080 {
0081     Q_D(DiffModel);
0082 
0083     return (d->hunks.at(i));
0084 }
0085 
0086 const Difference *DiffModel::differenceAt(int i) const
0087 {
0088     Q_D(const DiffModel);
0089 
0090     return (d->differences.at(i));
0091 }
0092 
0093 Difference *DiffModel::differenceAt(int i)
0094 {
0095     Q_D(DiffModel);
0096 
0097     return (d->differences.at(i));
0098 }
0099 
0100 DiffHunkList *DiffModel::hunks()
0101 {
0102     Q_D(DiffModel);
0103 
0104     return &d->hunks;
0105 }
0106 
0107 const DiffHunkList *DiffModel::hunks() const
0108 {
0109     Q_D(const DiffModel);
0110 
0111     return &d->hunks;
0112 }
0113 
0114 DifferenceList *DiffModel::differences()
0115 {
0116     Q_D(DiffModel);
0117 
0118     return &d->differences;
0119 }
0120 
0121 const DifferenceList *DiffModel::differences() const
0122 {
0123     Q_D(const DiffModel);
0124 
0125     return &d->differences;
0126 }
0127 
0128 int DiffModel::findDifference(Difference *diff) const
0129 {
0130     Q_D(const DiffModel);
0131 
0132     return d->differences.indexOf(diff);
0133 }
0134 
0135 int DiffModel::localeAwareCompareSource(const DiffModel &model) const
0136 {
0137     Q_D(const DiffModel);
0138 
0139     qCDebug(KOMPAREDIFF2_LOG) << "Path: " << model.d_ptr->sourcePath;
0140     qCDebug(KOMPAREDIFF2_LOG) << "File: " << model.d_ptr->sourceFile;
0141 
0142     int result = d->sourcePath.localeAwareCompare(model.d_ptr->sourcePath);
0143 
0144     if (result == 0)
0145         return d->sourceFile.localeAwareCompare(model.d_ptr->sourceFile);
0146 
0147     return result;
0148 }
0149 
0150 QString DiffModel::recreateDiff() const
0151 {
0152     Q_D(const DiffModel);
0153 
0154     // For now we'll always return a diff in the diff format
0155     QString diff;
0156 
0157     // recreate header
0158     const QChar tab = QLatin1Char('\t');
0159     const QChar nl = QLatin1Char('\n');
0160     diff += QStringLiteral("--- %1\t%2").arg(ParserBase::escapePath(d->source), d->sourceTimestamp);
0161     if (!d->sourceRevision.isEmpty())
0162         diff += tab + d->sourceRevision;
0163     diff += nl;
0164     diff += QStringLiteral("+++ %1\t%2").arg(ParserBase::escapePath(d->destination), d->destinationTimestamp);
0165     if (!d->destinationRevision.isEmpty())
0166         diff += tab + d->destinationRevision;
0167     diff += nl;
0168 
0169     // recreate body by iterating over the hunks
0170     for (const DiffHunk *hunk : d->hunks) {
0171         if (hunk->type() != DiffHunk::AddedByBlend) {
0172             diff += hunk->recreateHunk();
0173         }
0174     }
0175 
0176     return diff;
0177 }
0178 
0179 Difference *DiffModel::firstDifference()
0180 {
0181     Q_D(DiffModel);
0182 
0183     qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::firstDifference()";
0184     d->diffIndex = 0;
0185     qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
0186 
0187     d->selectedDifference = d->differences[d->diffIndex];
0188 
0189     return d->selectedDifference;
0190 }
0191 
0192 Difference *DiffModel::lastDifference()
0193 {
0194     Q_D(DiffModel);
0195 
0196     qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::lastDifference()";
0197     d->diffIndex = d->differences.count() - 1;
0198     qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
0199 
0200     d->selectedDifference = d->differences[d->diffIndex];
0201 
0202     return d->selectedDifference;
0203 }
0204 
0205 Difference *DiffModel::prevDifference()
0206 {
0207     Q_D(DiffModel);
0208 
0209     qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::prevDifference()";
0210     if (d->diffIndex > 0 && --d->diffIndex < d->differences.count()) {
0211         qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
0212         d->selectedDifference = d->differences[d->diffIndex];
0213     } else {
0214         d->selectedDifference = nullptr;
0215         d->diffIndex = 0;
0216         qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
0217     }
0218 
0219     return d->selectedDifference;
0220 }
0221 
0222 Difference *DiffModel::nextDifference()
0223 {
0224     Q_D(DiffModel);
0225 
0226     qCDebug(KOMPAREDIFF2_LOG) << "DiffModel::nextDifference()";
0227     if (++d->diffIndex < d->differences.count()) {
0228         qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
0229         d->selectedDifference = d->differences[d->diffIndex];
0230     } else {
0231         d->selectedDifference = nullptr;
0232         d->diffIndex = 0; // just for safety...
0233         qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
0234     }
0235 
0236     return d->selectedDifference;
0237 }
0238 
0239 QString DiffModel::source() const
0240 {
0241     Q_D(const DiffModel);
0242 
0243     return d->source;
0244 }
0245 
0246 QString DiffModel::destination() const
0247 {
0248     Q_D(const DiffModel);
0249 
0250     return d->destination;
0251 }
0252 
0253 QString DiffModel::sourceFile() const
0254 {
0255     Q_D(const DiffModel);
0256 
0257     return d->sourceFile;
0258 }
0259 
0260 QString DiffModel::destinationFile() const
0261 {
0262     Q_D(const DiffModel);
0263 
0264     return d->destinationFile;
0265 }
0266 
0267 QString DiffModel::sourcePath() const
0268 {
0269     Q_D(const DiffModel);
0270 
0271     return d->sourcePath;
0272 }
0273 
0274 QString DiffModel::destinationPath() const
0275 {
0276     Q_D(const DiffModel);
0277 
0278     return d->destinationPath;
0279 }
0280 
0281 QString DiffModel::sourceTimestamp() const
0282 {
0283     Q_D(const DiffModel);
0284 
0285     return d->sourceTimestamp;
0286 }
0287 
0288 QString DiffModel::destinationTimestamp() const
0289 {
0290     Q_D(const DiffModel);
0291 
0292     return d->destinationTimestamp;
0293 }
0294 
0295 QString DiffModel::sourceRevision() const
0296 {
0297     Q_D(const DiffModel);
0298 
0299     return d->sourceRevision;
0300 }
0301 
0302 QString DiffModel::destinationRevision() const
0303 {
0304     Q_D(const DiffModel);
0305 
0306     return d->destinationRevision;
0307 }
0308 
0309 void DiffModel::setSourceFile(const QString &path)
0310 {
0311     Q_D(DiffModel);
0312 
0313     d->source = path;
0314     d->splitSourceInPathAndFileName();
0315 }
0316 
0317 void DiffModel::setDestinationFile(const QString &path)
0318 {
0319     Q_D(DiffModel);
0320 
0321     d->destination = path;
0322     d->splitDestinationInPathAndFileName();
0323 }
0324 
0325 void DiffModel::setSourceTimestamp(const QString &timestamp)
0326 {
0327     Q_D(DiffModel);
0328 
0329     d->sourceTimestamp = timestamp;
0330 }
0331 
0332 void DiffModel::setDestinationTimestamp(const QString &timestamp)
0333 {
0334     Q_D(DiffModel);
0335 
0336     d->destinationTimestamp = timestamp;
0337 }
0338 
0339 void DiffModel::setSourceRevision(const QString &revision)
0340 {
0341     Q_D(DiffModel);
0342 
0343     d->sourceRevision = revision;
0344 }
0345 
0346 void DiffModel::setDestinationRevision(const QString &revision)
0347 {
0348     Q_D(DiffModel);
0349 
0350     d->destinationRevision = revision;
0351 }
0352 
0353 bool DiffModel::isBlended() const
0354 {
0355     Q_D(const DiffModel);
0356 
0357     return d->blended;
0358 }
0359 
0360 void DiffModel::setBlended(bool blended)
0361 {
0362     Q_D(DiffModel);
0363 
0364     d->blended = blended;
0365 }
0366 
0367 void DiffModel::addHunk(DiffHunk *hunk)
0368 {
0369     Q_D(DiffModel);
0370 
0371     d->hunks.append(hunk);
0372 }
0373 
0374 void DiffModel::addDiff(Difference *diff)
0375 {
0376     Q_D(DiffModel);
0377 
0378     d->differences.append(diff);
0379     connect(diff, &Difference::differenceApplied, this, &DiffModel::slotDifferenceApplied);
0380 }
0381 
0382 int DiffModel::diffIndex() const
0383 {
0384     Q_D(const DiffModel);
0385 
0386     return d->diffIndex;
0387 }
0388 
0389 void DiffModel::setDiffIndex(int diffIndex)
0390 {
0391     Q_D(DiffModel);
0392 
0393     d->diffIndex = diffIndex;
0394 }
0395 
0396 bool DiffModel::hasUnsavedChanges() const
0397 {
0398     Q_D(const DiffModel);
0399 
0400     return std::any_of(d->differences.constBegin(), d->differences.constEnd(), [] (const Difference* diff) {
0401         return diff->isUnsaved();
0402     });
0403 }
0404 
0405 void DiffModel::applyDifference(bool apply)
0406 {
0407     Q_D(DiffModel);
0408 
0409     bool appliedState = d->selectedDifference->applied();
0410     if (appliedState == apply) {
0411         return;
0412     }
0413     if (apply && !d->selectedDifference->applied())
0414         ++d->appliedCount;
0415     else if (!apply && d->selectedDifference->applied())
0416         --d->appliedCount;
0417 
0418     d->selectedDifference->apply(apply);
0419 }
0420 
0421 static int GetDifferenceDelta(Difference *diff)
0422 {
0423     int delta = diff->destinationLineCount() - diff->sourceLineCount();
0424     if (!diff->applied()) {
0425         delta = -delta;
0426     }
0427     return delta;
0428 }
0429 
0430 void DiffModel::slotDifferenceApplied(Difference *diff)
0431 {
0432     Q_D(DiffModel);
0433 
0434     int delta = GetDifferenceDelta(diff);
0435     for (Difference *current : std::as_const(d->differences)) {
0436         if (current->destinationLineNumber() > diff->destinationLineNumber()) {
0437             current->setTrackingDestinationLineNumber(current->trackingDestinationLineNumber() + delta);
0438         }
0439     }
0440 }
0441 
0442 void DiffModel::applyAllDifferences(bool apply)
0443 {
0444     Q_D(DiffModel);
0445 
0446     if (apply) {
0447         d->appliedCount = d->differences.count();
0448     } else {
0449         d->appliedCount = 0;
0450     }
0451 
0452     int totalDelta = 0;
0453 
0454     for (Difference *diff : std::as_const(d->differences)) {
0455         diff->setTrackingDestinationLineNumber(diff->trackingDestinationLineNumber() + totalDelta);
0456         const bool appliedState = diff->applied();
0457         if (appliedState == apply) {
0458             continue;
0459         }
0460         diff->applyQuietly(apply);
0461         const int currentDelta = GetDifferenceDelta(diff);
0462         totalDelta += currentDelta;
0463     }
0464 }
0465 
0466 bool DiffModel::setSelectedDifference(Difference *diff)
0467 {
0468     Q_D(DiffModel);
0469 
0470     qCDebug(KOMPAREDIFF2_LOG) << "diff = " << diff;
0471     qCDebug(KOMPAREDIFF2_LOG) << "d->selectedDifference = " << d->selectedDifference;
0472 
0473     if (diff != d->selectedDifference) {
0474         if ((d->differences.indexOf(diff)) == -1)
0475             return false;
0476         // Do not set d->diffIndex if it cant be found
0477         d->diffIndex = d->differences.indexOf(diff);
0478         qCDebug(KOMPAREDIFF2_LOG) << "d->diffIndex = " << d->diffIndex;
0479         d->selectedDifference = diff;
0480     }
0481 
0482     return true;
0483 }
0484 
0485 QPair<QList<Difference *>, QList<Difference *>> DiffModel::linesChanged(const QStringList &oldLines, const QStringList &newLines, int editLineNumber)
0486 {
0487     Q_D(DiffModel);
0488 
0489     // These two will be returned as the function result
0490     QList<Difference *> inserted;
0491     QList<Difference *> removed;
0492     if (oldLines.size() == 0 && newLines.size() == 0) {
0493         return qMakePair(QList<Difference *>(), QList<Difference *>());
0494     }
0495     int editLineEnd = editLineNumber + oldLines.size();
0496     // Find the range of differences [iterBegin, iterEnd) that should be updated
0497     // TODO: assume that differences are ordered by starting line. Check that this is always the case
0498     DifferenceList applied;
0499     DifferenceListIterator iterBegin; // first diff ending a line before editLineNo or later
0500     for (iterBegin = d->differences.begin(); iterBegin != d->differences.end(); ++iterBegin) {
0501         // If the difference ends a line before the edit starts, they should be merged if this difference is applied.
0502         // Also it should be merged if it starts on editLineNumber, otherwise there will be two markers for the same line
0503         int lineAfterLast = (*iterBegin)->trackingDestinationLineEnd();
0504         if (lineAfterLast > editLineNumber
0505             || (lineAfterLast == editLineNumber && ((*iterBegin)->applied() || (*iterBegin)->trackingDestinationLineNumber() == editLineNumber))) {
0506             break;
0507         }
0508     }
0509     DifferenceListIterator iterEnd;
0510     for (iterEnd = iterBegin; iterEnd != d->differences.end(); ++iterEnd) {
0511         // If the difference starts a line after the edit ends, it should still be merged if it is applied
0512         int firstLine = (*iterEnd)->trackingDestinationLineNumber();
0513         if (firstLine > editLineEnd || (!(*iterEnd)->applied() && firstLine == editLineEnd)) {
0514             break;
0515         }
0516         if ((*iterEnd)->applied()) {
0517             applied.append(*iterEnd);
0518         }
0519     }
0520 
0521     // Compute line numbers in source and destination to which the for diff line sequences (will be created later)
0522     int sourceLineNumber;
0523     int destinationLineNumber;
0524     if (iterBegin == d->differences.end()) { // All existing diffs are after the change
0525         destinationLineNumber = editLineNumber;
0526         if (!d->differences.isEmpty()) {
0527             sourceLineNumber = d->differences.last()->sourceLineEnd() - (d->differences.last()->trackingDestinationLineEnd() - editLineNumber);
0528         } else {
0529             sourceLineNumber = destinationLineNumber;
0530         }
0531     } else if (!(*iterBegin)->applied() || (*iterBegin)->trackingDestinationLineNumber() >= editLineNumber) {
0532         destinationLineNumber = editLineNumber;
0533         sourceLineNumber = (*iterBegin)->sourceLineNumber() - ((*iterBegin)->trackingDestinationLineNumber() - editLineNumber);
0534     } else {
0535         sourceLineNumber = (*iterBegin)->sourceLineNumber();
0536         destinationLineNumber = (*iterBegin)->trackingDestinationLineNumber();
0537     }
0538 
0539     // Only the applied differences are of interest, unapplied can be safely removed
0540     DifferenceListConstIterator appliedBegin = applied.constBegin();
0541     DifferenceListConstIterator appliedEnd = applied.constEnd();
0542 
0543     // Now create a sequence of lines for the destination file and the corresponding lines in source
0544     QStringList sourceLines;
0545     QStringList destinationLines;
0546     DifferenceListIterator insertPosition; // where to insert the created diffs
0547     if (appliedBegin == appliedEnd) {
0548         destinationLines = newLines;
0549         sourceLines = oldLines;
0550     } else {
0551         // Create the destination line sequence
0552         int firstDestinationLineNumber = (*appliedBegin)->trackingDestinationLineNumber();
0553         for (int lineNumber = firstDestinationLineNumber; lineNumber < editLineNumber; ++lineNumber) {
0554             destinationLines.append((*appliedBegin)->destinationLineAt(lineNumber - firstDestinationLineNumber)->string());
0555         }
0556         for (const QString &line : newLines) {
0557             destinationLines.append(line);
0558         }
0559         DifferenceListConstIterator appliedLast = appliedEnd;
0560         --appliedLast;
0561         int lastDestinationLineNumber = (*appliedLast)->trackingDestinationLineNumber();
0562         for (int lineNumber = editLineEnd; lineNumber < (*appliedLast)->trackingDestinationLineEnd(); ++lineNumber) {
0563             destinationLines.append((*appliedLast)->destinationLineAt(lineNumber - lastDestinationLineNumber)->string());
0564         }
0565 
0566         // Create the source line sequence
0567         if ((*appliedBegin)->trackingDestinationLineNumber() >= editLineNumber) {
0568             for (int i = editLineNumber; i < (*appliedBegin)->trackingDestinationLineNumber(); ++i) {
0569                 sourceLines.append(oldLines.at(i - editLineNumber));
0570             }
0571         }
0572 
0573         for (DifferenceListConstIterator iter = appliedBegin; iter != appliedEnd;) {
0574             int startPos = (*iter)->trackingDestinationLineNumber();
0575             if ((*iter)->applied()) {
0576                 for (int i = 0; i < (*iter)->sourceLineCount(); ++i) {
0577                     sourceLines.append((*iter)->sourceLineAt(i)->string());
0578                 }
0579                 startPos = (*iter)->trackingDestinationLineEnd();
0580             } else if (startPos < editLineNumber) {
0581                 startPos = editLineNumber;
0582             }
0583             ++iter;
0584             int endPos = (iter == appliedEnd) ? editLineEnd : (*iter)->trackingDestinationLineNumber();
0585             for (int i = startPos; i < endPos; ++i) {
0586                 sourceLines.append(oldLines.at(i - editLineNumber));
0587             }
0588         }
0589     }
0590 
0591     for (DifferenceListIterator iter = iterBegin; iter != iterEnd; ++iter) {
0592         removed << *iter;
0593     }
0594     insertPosition = d->differences.erase(iterBegin, iterEnd);
0595 
0596     // Compute the Levenshtein table for two line sequences and construct the shortest possible edit script
0597     StringListPair *pair = new StringListPair(sourceLines, destinationLines);
0598     LevenshteinTable<StringListPair> table;
0599     table.createTable(pair);
0600     table.createListsOfMarkers();
0601     MarkerList sourceMarkers = pair->markerListFirst();
0602     MarkerList destinationMarkers = pair->markerListSecond();
0603 
0604     int currentSourceListLine = 0;
0605     int currentDestinationListLine = 0;
0606     MarkerListConstIterator sourceMarkerIter = sourceMarkers.constBegin();
0607     MarkerListConstIterator destinationMarkerIter = destinationMarkers.constBegin();
0608     const int terminatorLineNumber = sourceLines.size() + destinationLines.size() + 1; // A virtual offset for simpler computation - stands for infinity
0609 
0610     // Process marker lists, converting pairs of Start-End markers into differences.
0611     // Marker in source list only stands for deletion, in source and destination lists - for change, in destination list only - for insertion.
0612     while (sourceMarkerIter != sourceMarkers.constEnd() || destinationMarkerIter != destinationMarkers.constEnd()) {
0613         int nextSourceListLine = sourceMarkerIter != sourceMarkers.constEnd() ? (*sourceMarkerIter)->offset() : terminatorLineNumber;
0614         int nextDestinationListLine = destinationMarkerIter != destinationMarkers.constEnd() ? (*destinationMarkerIter)->offset() : terminatorLineNumber;
0615 
0616         // Advance to the nearest marker
0617         int linesToSkip = qMin(nextDestinationListLine - currentDestinationListLine, nextSourceListLine - currentSourceListLine);
0618         currentSourceListLine += linesToSkip;
0619         currentDestinationListLine += linesToSkip;
0620         Difference *diff = new Difference(sourceLineNumber + currentSourceListLine, destinationLineNumber + currentDestinationListLine);
0621         if (nextSourceListLine == currentSourceListLine) {
0622             DiffModelPrivate::processStartMarker(diff, sourceLines, sourceMarkerIter, currentSourceListLine, true);
0623         }
0624         if (nextDestinationListLine == currentDestinationListLine) {
0625             DiffModelPrivate::processStartMarker(diff, destinationLines, destinationMarkerIter, currentDestinationListLine, false);
0626         }
0627         DiffModelPrivate::computeDiffStats(diff);
0628         Q_ASSERT(diff->type() != Difference::Unchanged);
0629         diff->applyQuietly(true);
0630         diff->setTrackingDestinationLineNumber(diff->destinationLineNumber());
0631         insertPosition = d->differences.insert(insertPosition, diff);
0632         ++insertPosition;
0633         inserted << diff;
0634     }
0635     // Update line numbers for differences that are after the edit
0636     for (; insertPosition != d->differences.end(); ++insertPosition) {
0637         (*insertPosition)->setTrackingDestinationLineNumber((*insertPosition)->trackingDestinationLineNumber() + (newLines.size() - oldLines.size()));
0638     }
0639     return qMakePair(inserted, removed);
0640 }
0641 
0642 #include "moc_diffmodel.cpp"