File indexing completed on 2024-12-01 05:11:57
0001 // clang-format off 0002 /* 0003 * KDiff3 - Text Diff And Merge Tool 0004 * 0005 * SPDX-FileCopyrightText: 2002-2011 Joachim Eibl, joachim.eibl at gmx.de 0006 * SPDX-FileCopyrightText: 2018-2020 Michael Reeves reeves.87@gmail.com 0007 * SPDX-License-Identifier: GPL-2.0-or-later 0008 */ 0009 // clang-format on 0010 0011 #include "MergeFileInfos.h" 0012 0013 #include "DirectoryInfo.h" 0014 #include "directorymergewindow.h" 0015 #include "fileaccess.h" 0016 #include "Logging.h" 0017 #include "progress.h" 0018 0019 #include <map> 0020 #include <vector> 0021 0022 #include <QString> 0023 0024 #include <KLocalizedString> 0025 0026 extern std::unique_ptr<Options> gOptions; 0027 0028 MergeFileInfos::MergeFileInfos() = default; 0029 0030 MergeFileInfos::~MergeFileInfos() 0031 { 0032 m_children.clear(); 0033 } 0034 0035 void MergeFileInfos::updateParents() 0036 { 0037 MergeFileInfos* current = parent(); 0038 while(current != nullptr) 0039 { 0040 bool bChange = false; 0041 if(!isEqualAB() && current->isEqualAB()) 0042 { 0043 current->m_bEqualAB = false; 0044 bChange = true; 0045 } 0046 if(!isEqualAC() && current->isEqualAC()) 0047 { 0048 current->m_bEqualAC = false; 0049 bChange = true; 0050 } 0051 if(!isEqualBC() && current->isEqualBC()) 0052 { 0053 current->m_bEqualBC = false; 0054 bChange = true; 0055 } 0056 0057 if(bChange) 0058 current->updateAge(); 0059 else 0060 break; 0061 0062 current = current->parent(); 0063 } 0064 } 0065 /* 0066 Check for directories or links marked as not equal and mark them equal. 0067 */ 0068 void MergeFileInfos::updateDirectoryOrLink() 0069 { 0070 bool bChange = false; 0071 if(!isEqualAB() && isDirA() == isDirB() && isLinkA() == isLinkB()) 0072 { 0073 m_bEqualAB = true; 0074 bChange = true; 0075 } 0076 if(!isEqualBC() && isDirC() == isDirB() && isLinkC() == isLinkB()) 0077 { 0078 m_bEqualBC = true; 0079 bChange = true; 0080 } 0081 if(!isEqualAC() && isDirA() == isDirC() && isLinkA() == isLinkC()) 0082 { 0083 m_bEqualAC = true; 0084 bChange = true; 0085 } 0086 0087 if(bChange) 0088 updateAge(); 0089 } 0090 0091 QString MergeFileInfos::subPath() const 0092 { 0093 if(m_pFileInfoA != nullptr && m_pFileInfoA->exists()) 0094 return m_pFileInfoA->fileRelPath(); 0095 else if(m_pFileInfoB != nullptr && m_pFileInfoB->exists()) 0096 return m_pFileInfoB->fileRelPath(); 0097 else if(m_pFileInfoC != nullptr && m_pFileInfoC->exists()) 0098 return m_pFileInfoC->fileRelPath(); 0099 return QString(""); 0100 } 0101 0102 QString MergeFileInfos::fileName() const 0103 { 0104 if(m_pFileInfoA != nullptr && m_pFileInfoA->exists()) 0105 return m_pFileInfoA->fileName(); 0106 else if(m_pFileInfoB != nullptr && m_pFileInfoB->exists()) 0107 return m_pFileInfoB->fileName(); 0108 else if(m_pFileInfoC != nullptr && m_pFileInfoC->exists()) 0109 return m_pFileInfoC->fileName(); 0110 return QString(""); 0111 } 0112 0113 bool MergeFileInfos::conflictingFileTypes() const 0114 { 0115 if((m_pFileInfoA != nullptr && !m_pFileInfoA->isNormal()) || (m_pFileInfoB != nullptr && !m_pFileInfoB->isNormal()) || (m_pFileInfoC != nullptr && !m_pFileInfoC->isNormal())) 0116 return true; 0117 // Now check if file/dir-types fit. 0118 if(isLinkA() || isLinkB() || isLinkC()) 0119 { 0120 if((existsInA() && !isLinkA()) || 0121 (existsInB() && !isLinkB()) || 0122 (existsInC() && !isLinkC())) 0123 { 0124 return true; 0125 } 0126 } 0127 0128 if(isDirA() || isDirB() || isDirC()) 0129 { 0130 if((existsInA() && !isDirA()) || 0131 (existsInB() && !isDirB()) || 0132 (existsInC() && !isDirC())) 0133 { 0134 return true; 0135 } 0136 } 0137 return false; 0138 } 0139 0140 QString MergeFileInfos::fullNameA() const 0141 { 0142 if(existsInA()) 0143 return getFileInfoA()->absoluteFilePath(); 0144 0145 return gDirInfo->dirA().absoluteFilePath() + '/' + subPath(); 0146 } 0147 0148 QString MergeFileInfos::fullNameB() const 0149 { 0150 if(existsInB()) 0151 return getFileInfoB()->absoluteFilePath(); 0152 0153 return gDirInfo->dirB().absoluteFilePath() + '/' + subPath(); 0154 } 0155 0156 QString MergeFileInfos::fullNameC() const 0157 { 0158 if(existsInC()) 0159 return getFileInfoC()->absoluteFilePath(); 0160 0161 return gDirInfo->dirC().absoluteFilePath() + '/' + subPath(); 0162 } 0163 0164 void MergeFileInfos::sort(Qt::SortOrder order) 0165 { 0166 std::sort(m_children.begin(), m_children.end(), MfiCompare(order)); 0167 0168 for(qint32 i = 0; i < m_children.count(); ++i) 0169 m_children[i]->sort(order); 0170 } 0171 0172 QString MergeFileInfos::fullNameDest() const 0173 { 0174 if(gDirInfo->destDir().prettyAbsPath() == gDirInfo->dirC().prettyAbsPath()) 0175 return fullNameC(); 0176 else if(gDirInfo->destDir().prettyAbsPath() == gDirInfo->dirB().prettyAbsPath()) 0177 return fullNameB(); 0178 else 0179 return gDirInfo->destDir().absoluteFilePath() + '/' + subPath(); 0180 } 0181 0182 bool MergeFileInfos::compareFilesAndCalcAges(QStringList& errors, DirectoryMergeWindow* pDMW) 0183 { 0184 enum class FileIndex 0185 { 0186 a, 0187 b, 0188 c 0189 }; 0190 0191 std::map<QDateTime, FileIndex> dateMap; 0192 0193 if(existsInA()) 0194 { 0195 dateMap[getFileInfoA()->lastModified()] = FileIndex::a; 0196 } 0197 if(existsInB()) 0198 { 0199 dateMap[getFileInfoB()->lastModified()] = FileIndex::b; 0200 } 0201 if(existsInC()) 0202 { 0203 dateMap[getFileInfoC()->lastModified()] = FileIndex::c; 0204 } 0205 0206 if(gOptions->m_bDmFullAnalysis) 0207 { 0208 if((existsInA() && isDirA()) || (existsInB() && isDirB()) || (existsInC() && isDirC())) 0209 { 0210 // If any input is a directory, don't start any comparison. 0211 m_bEqualAB = existsInA() && existsInB(); 0212 m_bEqualAC = existsInA() && existsInC(); 0213 m_bEqualBC = existsInB() && existsInC(); 0214 } 0215 else 0216 { 0217 Q_EMIT pDMW->startDiffMerge(errors, 0218 existsInA() ? getFileInfoA()->absoluteFilePath() : QString(""), 0219 existsInB() ? getFileInfoB()->absoluteFilePath() : QString(""), 0220 existsInC() ? getFileInfoC()->absoluteFilePath() : QString(""), 0221 "", 0222 "", "", "", &diffStatus()); 0223 qint32 nofNonwhiteConflicts = diffStatus().getNonWhitespaceConflicts(); 0224 0225 if(gOptions->m_bDmWhiteSpaceEqual && nofNonwhiteConflicts == 0) 0226 { 0227 m_bEqualAB = existsInA() && existsInB(); 0228 m_bEqualAC = existsInA() && existsInC(); 0229 m_bEqualBC = existsInB() && existsInC(); 0230 } 0231 else 0232 { 0233 m_bEqualAB = diffStatus().isBinaryEqualAB(); 0234 m_bEqualBC = diffStatus().isBinaryEqualBC(); 0235 m_bEqualAC = diffStatus().isBinaryEqualAC(); 0236 } 0237 0238 //Limit size of error list in memory. 0239 if(errors.size() >= 30) 0240 { 0241 //Bail out something is very wrong. 0242 return false; 0243 } 0244 } 0245 } 0246 else 0247 { 0248 bool bError = false; 0249 QString eqStatus; 0250 if(existsInA() && existsInB()) 0251 { 0252 if(isDirA()) 0253 m_bEqualAB = true; 0254 else 0255 m_bEqualAB = fastFileComparison(*getFileInfoA(), *getFileInfoB(), bError, eqStatus); 0256 } 0257 if(existsInA() && existsInC()) 0258 { 0259 if(isDirA()) 0260 m_bEqualAC = true; 0261 else 0262 m_bEqualAC = fastFileComparison(*getFileInfoA(), *getFileInfoC(), bError, eqStatus); 0263 } 0264 if(existsInB() && existsInC()) 0265 { 0266 if((m_bEqualAB && m_bEqualAC) || isDirB()) 0267 m_bEqualBC = true; 0268 else 0269 { 0270 m_bEqualBC = fastFileComparison(*getFileInfoB(), *getFileInfoC(), bError, eqStatus); 0271 } 0272 } 0273 if(bError) 0274 { 0275 //Limit size of error list in memory. 0276 if(errors.size() < 30) 0277 errors.append(eqStatus); 0278 return false; 0279 } 0280 } 0281 0282 if(isLinkA() != isLinkB()) m_bEqualAB = false; 0283 if(isLinkA() != isLinkC()) m_bEqualAC = false; 0284 if(isLinkB() != isLinkC()) m_bEqualBC = false; 0285 0286 if(isDirA() != isDirB()) m_bEqualAB = false; 0287 if(isDirA() != isDirC()) m_bEqualAC = false; 0288 if(isDirB() != isDirC()) m_bEqualBC = false; 0289 0290 assert(eNew == 0 && eMiddle == 1 && eOld == 2); 0291 0292 // The map automatically sorts the keys. 0293 e_Age age = eNew; 0294 std::map<QDateTime, FileIndex>::reverse_iterator i; 0295 assert(dateMap.size() <= 3); 0296 for(i = dateMap.rbegin(); i != dateMap.rend(); ++i) 0297 { 0298 FileIndex n = i->second; 0299 0300 if(n == FileIndex::a && getAgeA() == eNotThere) 0301 { 0302 setAgeA(age); 0303 age = nextAgeValue(age); 0304 if(m_bEqualAB) 0305 { 0306 setAgeB(getAgeA()); 0307 age = nextAgeValue(age); 0308 } 0309 if(m_bEqualAC) 0310 { 0311 setAgeC(getAgeA()); 0312 age = nextAgeValue(age); 0313 } 0314 } 0315 else if(n == FileIndex::b && getAgeB() == eNotThere) 0316 { 0317 setAgeB(age); 0318 age = nextAgeValue(age); 0319 if(m_bEqualAB) 0320 { 0321 setAgeA(getAgeB()); 0322 age = nextAgeValue(age); 0323 } 0324 if(m_bEqualBC) 0325 { 0326 setAgeC(getAgeB()); 0327 age = nextAgeValue(age); 0328 } 0329 } 0330 else if(n == FileIndex::c && getAgeC() == eNotThere) 0331 { 0332 setAgeC(age); 0333 age = nextAgeValue(age); 0334 if(m_bEqualAC) 0335 { 0336 setAgeA(getAgeC()); 0337 age = nextAgeValue(age); 0338 } 0339 if(m_bEqualBC) 0340 { 0341 setAgeB(getAgeC()); 0342 age = nextAgeValue(age); 0343 } 0344 } 0345 } 0346 0347 // The checks below are necessary when the dates of the file are equal but the 0348 // files are not. One wouldn't expect this to happen, yet it happens sometimes. 0349 if(existsInC() && getAgeC() == eNotThere) 0350 { 0351 setAgeC(age); 0352 age = nextAgeValue(age); 0353 m_bConflictingAges = true; 0354 } 0355 if(existsInB() && getAgeB() == eNotThere) 0356 { 0357 setAgeB(age); 0358 age = nextAgeValue(age); 0359 m_bConflictingAges = true; 0360 } 0361 if(existsInA() && getAgeA() == eNotThere) 0362 { 0363 setAgeA(age); 0364 //unneeded as long as this is the last condition checked. 0365 //age = nextAgeValue(age); 0366 m_bConflictingAges = true; 0367 } 0368 0369 if(getAgeA() != eOld && getAgeB() != eOld && getAgeC() != eOld) 0370 { 0371 if(getAgeA() == eMiddle) setAgeA(eOld); 0372 if(getAgeB() == eMiddle) setAgeB(eOld); 0373 if(getAgeC() == eMiddle) setAgeC(eOld); 0374 } 0375 0376 return true; 0377 } 0378 0379 bool MergeFileInfos::fastFileComparison( 0380 FileAccess& fi1, FileAccess& fi2, 0381 bool& bError, QString& status) 0382 { 0383 ProgressScope pp; 0384 bool bEqual = false; 0385 0386 status = ""; 0387 bError = true; 0388 0389 qCDebug(kdiffMergeFileInfo) << "Entering MergeFileInfos::fastFileComparison"; 0390 if(fi1.isNormal() != fi2.isNormal()) 0391 { 0392 qCDebug(kdiffMergeFileInfo) << "Have: \'" << fi2.fileName() << "\' , isNormal = " << fi2.isNormal(); 0393 0394 status = i18n("Unable to compare non-normal file with normal file."); 0395 return false; 0396 } 0397 0398 if(!fi1.isNormal()) 0399 { 0400 qCInfo(kdiffMergeFileInfo) << "Skipping not a normal file."; 0401 bError = false; 0402 return false; 0403 } 0404 0405 if(!gOptions->m_bDmFollowFileLinks) 0406 { 0407 qCInfo(kdiffMergeFileInfo) << "Have: \'" << fi2.fileName() << "\' , isSymLink = " << fi2.isSymLink(); 0408 /* 0409 "git difftool --dir-diff" 0410 sets up directory comparsions with symlinks to the current work tree being compared with real files. 0411 0412 m_bAllowMismatch is a compatibility work around. 0413 */ 0414 if(fi1.isSymLink() != fi2.isSymLink()) 0415 { 0416 qCDebug(kdiffMergeFileInfo) << "Rejecting comparison of link to file. With the same relitive path."; 0417 status = i18n("Mix of links and normal files."); 0418 return bEqual; 0419 } 0420 else if(fi1.isSymLink() && fi2.isSymLink()) 0421 { 0422 qCDebug(kdiffMergeFileInfo) << "Comparison of link to link. OK."; 0423 bError = false; 0424 bEqual = fi1.readLink() == fi2.readLink(); 0425 status = i18n("Link: "); 0426 return bEqual; 0427 } 0428 } 0429 0430 if(fi1.size() != fi2.size()) 0431 { 0432 qCInfo(kdiffMergeFileInfo) << "Sizes differ."; 0433 bError = false; 0434 bEqual = false; 0435 status = i18n("Size. "); 0436 return bEqual; 0437 } 0438 else if(gOptions->m_bDmTrustSize) 0439 { 0440 qCInfo(kdiffMergeFileInfo) << "Same size. Trusting result."; 0441 0442 bEqual = true; 0443 bError = false; 0444 return bEqual; 0445 } 0446 0447 if(gOptions->m_bDmTrustDate) 0448 { 0449 bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); 0450 bError = false; 0451 status = i18n("Date & Size: "); 0452 return bEqual; 0453 } 0454 0455 if(gOptions->m_bDmTrustDateFallbackToBinary) 0456 { 0457 bEqual = (fi1.lastModified() == fi2.lastModified() && fi1.size() == fi2.size()); 0458 if(bEqual) 0459 { 0460 bError = false; 0461 status = i18n("Date & Size: "); 0462 return bEqual; 0463 } 0464 } 0465 0466 std::vector<char> buf1(100000); 0467 std::vector<char> buf2(buf1.size()); 0468 0469 if(!fi1.open(QIODevice::ReadOnly)) 0470 { 0471 status = fi1.errorString(); 0472 return bEqual; 0473 } 0474 0475 if(!fi2.open(QIODevice::ReadOnly)) 0476 { 0477 fi1.close(); 0478 status = fi2.errorString(); 0479 return bEqual; 0480 } 0481 qCInfo(kdiffMergeFileInfo) << "Comparing files..."; 0482 ProgressProxy::setInformation(i18nc("Status message", "Comparing file..."), 0, false); 0483 typedef qint64 t_FileSize; 0484 t_FileSize fullSize = fi1.size(); 0485 t_FileSize sizeLeft = fullSize; 0486 0487 ProgressProxy::setMaxNofSteps(fullSize / buf1.size()); 0488 0489 while(sizeLeft > 0 && !ProgressProxy::wasCancelled()) 0490 { 0491 qint64 len = std::min(sizeLeft, (t_FileSize)buf1.size()); 0492 if(len != fi1.read(&buf1[0], len)) 0493 { 0494 status = fi1.errorString(); 0495 fi1.close(); 0496 fi2.close(); 0497 return bEqual; 0498 } 0499 0500 if(len != fi2.read(&buf2[0], len)) 0501 { 0502 status = fi2.errorString(); 0503 fi1.close(); 0504 fi2.close(); 0505 return bEqual; 0506 } 0507 0508 if(memcmp(&buf1[0], &buf2[0], len) != 0) 0509 { 0510 bError = false; 0511 fi1.close(); 0512 fi2.close(); 0513 return bEqual; 0514 } 0515 sizeLeft -= len; 0516 //ProgressProxy::setCurrent(double(fullSize-sizeLeft)/fullSize, false ); 0517 ProgressProxy::step(); 0518 } 0519 fi1.close(); 0520 fi2.close(); 0521 0522 // If the program really arrives here, then the files are really equal. 0523 bError = false; 0524 bEqual = true; 0525 return bEqual; 0526 } 0527 0528 void MergeFileInfos::updateAge() 0529 { 0530 if(isDirA() || isDirB() || isDirC()) 0531 { 0532 setAgeA(eNotThere); 0533 setAgeB(eNotThere); 0534 setAgeC(eNotThere); 0535 e_Age age = eNew; 0536 if(existsInC()) 0537 { 0538 setAgeC(age); 0539 if(m_bEqualAC) setAgeA(age); 0540 if(m_bEqualBC) setAgeB(age); 0541 age = eMiddle; 0542 } 0543 if(existsInB() && getAgeB() == eNotThere) 0544 { 0545 setAgeB(age); 0546 if(m_bEqualAB) setAgeA(age); 0547 age = eOld; 0548 } 0549 if(existsInA() && getAgeA() == eNotThere) 0550 { 0551 setAgeA(age); 0552 } 0553 if(getAgeA() != eOld && getAgeB() != eOld && getAgeC() != eOld) 0554 { 0555 if(getAgeA() == eMiddle) setAgeA(eOld); 0556 if(getAgeB() == eMiddle) setAgeB(eOld); 0557 if(getAgeC() == eMiddle) setAgeC(eOld); 0558 } 0559 } 0560 } 0561 0562 QTextStream& operator<<(QTextStream& ts, MergeFileInfos& mfi) 0563 { 0564 ts << "{\n"; 0565 ValueMap vm; 0566 vm.writeEntry("SubPath", mfi.subPath()); 0567 vm.writeEntry("ExistsInA", mfi.existsInA()); 0568 vm.writeEntry("ExistsInB", mfi.existsInB()); 0569 vm.writeEntry("ExistsInC", mfi.existsInC()); 0570 vm.writeEntry("EqualAB", mfi.isEqualAB()); 0571 vm.writeEntry("EqualAC", mfi.isEqualAC()); 0572 vm.writeEntry("EqualBC", mfi.isEqualBC()); 0573 0574 vm.writeEntry("MergeOperation", (qint32)mfi.getOperation()); 0575 vm.writeEntry("DirA", mfi.isDirA()); 0576 vm.writeEntry("DirB", mfi.isDirB()); 0577 vm.writeEntry("DirC", mfi.isDirC()); 0578 vm.writeEntry("LinkA", mfi.isLinkA()); 0579 vm.writeEntry("LinkB", mfi.isLinkB()); 0580 vm.writeEntry("LinkC", mfi.isLinkC()); 0581 vm.writeEntry("OperationComplete", !mfi.isOperationRunning()); 0582 0583 vm.writeEntry("AgeA", (qint32)mfi.getAgeA()); 0584 vm.writeEntry("AgeB", (qint32)mfi.getAgeB()); 0585 vm.writeEntry("AgeC", (qint32)mfi.getAgeC()); 0586 vm.writeEntry("ConflictingAges", mfi.conflictingAges()); // Equal age but files are not! 0587 0588 vm.save(ts); 0589 0590 ts << "}\n"; 0591 0592 return ts; 0593 }