00001 
00002 
00003 
00004 
00005 
00006 
00007 
00008 
00009 #include <qapplication.h>
00010 #include "git.h"
00011 #include "annotate.h"
00012 
00013 #define MAX_AUTHOR_LEN 16
00014 
00015 Annotate::Annotate(Git* parent, QObject* guiObj) : QObject(parent) {
00016 
00017         EM_INIT(exAnnCanceled, "Canceling annotation");
00018 
00019         git = parent;
00020         gui = guiObj;
00021         cancelingAnnotate = annotateRunning = annotateActivity = false;
00022         valid = canceled = false;
00023 
00024         patchProcBuf.reserve(1000000); 
00025 
00026         processingTime.start();
00027 
00028         connect(&patchProc, SIGNAL(readyReadStdout()), this, SLOT(on_patchProc_readFromStdout()));
00029         connect(&patchProc, SIGNAL(processExited()), this, SLOT(on_patchProc_processExited()));
00030 }
00031 
00032 const FileAnnotation* Annotate::lookupAnnotation(SCRef sha, SCRef fn) {
00033 
00034         if (!valid || fileName != fn)
00035                 return NULL;
00036 
00037         AnnotateHistory::const_iterator it = ah.find(sha);
00038         if (it != ah.constEnd())
00039                 return &(it.data());
00040 
00041         
00042         int shaIdx;
00043         const QString ancestorSha = getAncestor(sha, fileName, &shaIdx);
00044         if (!ancestorSha.isEmpty()) {
00045                 it = ah.find(ancestorSha);
00046                 if (it != ah.constEnd())
00047                         return &(it.data());
00048         }
00049         return NULL;
00050 }
00051 
00052 bool Annotate::startPatchProc(SCRef buf, SCRef fileName) {
00053 
00054         QString cmd("git diff-tree --no-color -r -m --patch-with-raw --no-commit-id --stdin --");
00055         QStringList args(QStringList::split(' ', cmd));
00056         args.append(fileName); 
00057         patchProc.setArguments(args);
00058         patchProc.setWorkingDirectory(git->workDir);
00059         patchProcBuf = "";
00060         return patchProc.launch(buf);
00061 }
00062 
00063 void Annotate::on_patchProc_readFromStdout() {
00064 
00065         const QString tmp(patchProc.readStdout());
00066         annFilesNum += tmp.contains("diff --git ");
00067         if (annFilesNum > (int)histRevOrder.count())
00068                 annFilesNum = histRevOrder.count();
00069         patchProcBuf.append(tmp);
00070 }
00071 
00072 void Annotate::deleteWhenDone() {
00073 
00074         if (!EM_IS_PENDING(exAnnCanceled))
00075                 EM_RAISE(exAnnCanceled);
00076 
00077         if (annotateRunning)
00078                 cancelingAnnotate = true;
00079 
00080         if (patchProc.isRunning())
00081                 patchProc.tryTerminate();
00082 
00083         on_deleteWhenDone();
00084 }
00085 
00086 void Annotate::on_deleteWhenDone() {
00087 
00088         if (!(annotateRunning || EM_IS_PENDING(exAnnCanceled)))
00089                 deleteLater();
00090         else
00091                 QTimer::singleShot(20, this, SLOT(on_deleteWhenDone()));
00092 }
00093 
00094 void Annotate::on_progressTimer_timeout() {
00095 
00096         if (!cancelingAnnotate && !isError) {
00097                 const QString n(QString::number(annFilesNum));
00098                 QApplication::postEvent(gui, new AnnotateProgressEvent(n));
00099         }
00100 }
00101 
00102 bool Annotate::start(const FileHistory* _fh) {
00103 
00104         
00105         fh = _fh;
00106         fileName = fh->fileName;
00107         histRevOrder = fh->revOrder;
00108 
00109         if (histRevOrder.isEmpty()) {
00110                 valid = false;
00111                 return false;
00112         }
00113         annotateRunning = true;
00114 
00115         
00116         annFilesNum = 0;
00117         annId = histRevOrder.count();
00118         annNumLen = QString::number(histRevOrder.count()).length();
00119         StrVect::const_iterator it(histRevOrder.constBegin());
00120         do
00121                 ah.insert(*it, FileAnnotation(annId--));
00122         while (++it != histRevOrder.constEnd());
00123 
00124         
00125         
00126         
00127         isError = false;
00128         annotateFileHistory(fileName, true);
00129 
00130         if (isError || cancelingAnnotate) {
00131                 slotComputeDiffs(); 
00132                 return false;
00133         }
00134         connect(&progressTimer, SIGNAL(timeout()), this, SLOT(on_progressTimer_timeout()));
00135         progressTimer.start(500);
00136 
00137         
00138         return startPatchProc(patchScript, fileName);
00139 }
00140 
00141 void Annotate::on_patchProc_processExited() {
00142 
00143         
00144         QTimer::singleShot(1, this, SLOT(slotComputeDiffs()));
00145         progressTimer.stop();
00146 }
00147 
00148 void Annotate::slotComputeDiffs() {
00149 
00150         
00151         
00152         if (!cancelingAnnotate) {
00153 
00154                 diffMap.clear();
00155                 AnnotateHistory::iterator it(ah.begin());
00156                 do
00157                         (*it).isValid = false; 
00158                 while (++it != ah.end());
00159 
00160                 
00161                 int first = patchProcBuf.find(':');
00162                 if (first != -1) {
00163                         nextFileSha = patchProcBuf.mid(first + 56, 40);
00164                         patchProcBuf.remove(0, first + 100);
00165                 }
00166                 annotateFileHistory(fileName, false); 
00167         }
00168         valid = !(isError || cancelingAnnotate);
00169         canceled = cancelingAnnotate;
00170         cancelingAnnotate = annotateRunning = false;
00171         if (canceled)
00172                 deleteWhenDone();
00173         else
00174                 git->annotateExited(this);
00175 
00176 
00177 
00178 
00179 
00180 }
00181 
00182 void Annotate::annotateFileHistory(SCRef fileName, bool buildPatchScript) {
00183 
00184         
00185         
00186         StrVect::const_iterator it(histRevOrder.constEnd());
00187         do {
00188                 --it;
00189                 doAnnotate(fileName, *it, buildPatchScript);
00190         } while (it != histRevOrder.constBegin() && !isError && !cancelingAnnotate);
00191 }
00192 
00193 void Annotate::doAnnotate(SCRef fileName, SCRef sha, bool buildPatchScript) {
00194 
00195 
00196         FileAnnotation* fa = getFileAnnotation(sha);
00197         if (fa == NULL || fa->isValid || isError || cancelingAnnotate)
00198                 return;
00199 
00200         const Rev* r = git->revLookup(sha, fh); 
00201         if (r == NULL) {
00202                 dbp("ASSERT doAnnotate: no revision %1", sha);
00203                 isError = true;
00204                 return;
00205         }
00206         if (r->parentsCount() == 0) { 
00207                 if (!buildPatchScript)
00208                         setInitialAnnotation(fileName, sha, fa); 
00209                 fa->isValid = true;
00210                 return;
00211         }
00212         
00213         const QStringList parents(r->parents());
00214         const QString& parSha = parents.first();
00215         FileAnnotation* pa = getFileAnnotation(parSha);
00216 
00217         if (!(pa && pa->isValid)) {
00218                 dbp("ASSERT in doAnnotate: annotation for %1 not valid", parSha);
00219                 isError = true;
00220                 return;
00221         }
00222         if (buildPatchScript) 
00223                 updatePatchScript(sha, parSha);
00224         else {
00225                 const QString diff(getNextPatch(patchProcBuf, fileName, sha));
00226                 const QString author(setupAuthor(r->author(), fa->annId));
00227                 setAnnotation(diff, author, pa->lines, fa->lines);
00228         }
00229         
00230         QStringList::const_iterator it(parents.constBegin());
00231         ++it;
00232         while (it != parents.constEnd()) {
00233 
00234                 FileAnnotation* pa = getFileAnnotation(*it);
00235 
00236                 if (buildPatchScript) { 
00237                         updatePatchScript(sha, *it);
00238                         ++it;
00239                         continue;
00240                 }
00241                 const QString diff(getNextPatch(patchProcBuf, fileName, sha));
00242                 QStringList tmpAnn;
00243                 setAnnotation(diff, "Merge", pa->lines, tmpAnn);
00244 
00245                 
00246                 if (fa->lines.count() != tmpAnn.count()) {
00247                         qDebug("ASSERT: merging annotations of different length\n"
00248                                " merging %s in %s", (*it).latin1(), sha.latin1());
00249                         isError = true;
00250                         return;
00251                 }
00252                 
00253                 unify(tmpAnn, fa->lines);
00254                 fa->lines = tmpAnn;
00255                 ++it;
00256         }
00257         fa->isValid = true;
00258 }
00259 
00260 FileAnnotation* Annotate::getFileAnnotation(SCRef sha) {
00261 
00262         AnnotateHistory::iterator it(ah.find(sha));
00263         if (it == ah.end()) {
00264                 dbp("ASSERT getFileAnnotation: no revision %1", sha);
00265                 isError = true;
00266                 return NULL;
00267         }
00268         return &(*it);
00269 }
00270 
00271 void Annotate::setInitialAnnotation(SCRef fileName, SCRef sha, FileAnnotation* fa) {
00272 
00273         QString fileSha;
00274         QByteArray fileData;
00275         git->getFile(fileName, sha, NULL, &fileData, &fileSha); 
00276         if (cancelingAnnotate)
00277                 return;
00278 
00279         if (fileSha.isEmpty()) {
00280                 dbp("ASSERT in setInitialAnnotation: empty file of initial rev %1", sha);
00281                 isError = true;
00282                 return;
00283         }
00284         ah[sha].fileSha = fileSha;
00285         QString fileTxt(fileData);
00286         int lineNum = fileTxt.contains('\n');
00287         if (!fileTxt.endsWith("\n")) 
00288                 lineNum++;
00289 
00290         fa->lines.insert(fa->lines.end(), lineNum, "");
00291 }
00292 
00293 const QString Annotate::setupAuthor(SCRef origAuthor, int annId) {
00294 
00295         QString author(QString("%1.").arg(annId, annNumLen)); 
00296         QString tmp(origAuthor.section('<', 0, 0).stripWhiteSpace()); 
00297         if (tmp.isEmpty()) { 
00298                 tmp = origAuthor;
00299                 tmp.remove('<').remove('>');
00300                 tmp = tmp.stripWhiteSpace();
00301                 tmp.truncate(MAX_AUTHOR_LEN);
00302         }
00303         
00304         if (tmp.length() > MAX_AUTHOR_LEN) {
00305                 SCRef firstName(tmp.section(' ', 0, 0));
00306                 SCRef surname(tmp.section(' ', 1));
00307                 if (!firstName.isEmpty() && !surname.isEmpty())
00308                         tmp = firstName.left(1) + ". " + surname;
00309                 tmp.truncate(MAX_AUTHOR_LEN);
00310         }
00311         author.append(tmp);
00312         return author;
00313 }
00314 
00315 void Annotate::unify(SList dst, SCList src) {
00316 
00317         QStringList::Iterator itd(dst.begin());
00318         QStringList::const_iterator its(src.constBegin());
00319         for ( ; itd != dst.end(); ++itd, ++its)
00320                 if (*itd == "Merge")
00321                         *itd = *its;
00322 }
00323 
00324 void Annotate::setAnnotation(SCRef diff, SCRef author, SCList prevAnn,
00325                              QStringList& newAnn, int ofs) {
00326         newAnn = prevAnn;
00327         QStringList::iterator cur(newAnn.begin());
00328         QString line;
00329         int idx = 0, num, lineNumStart, lineNumEnd;
00330         while (getNextSection(diff, idx, line, "\n")) {
00331                 char firstChar = line[0].latin1();
00332                 switch (firstChar) {
00333                 case '@':
00334                         
00335                         
00336                         
00337                         
00338                         
00339                         lineNumStart = line.find('+') + 1;
00340                         lineNumEnd = line.find(',', lineNumStart);
00341                         if (lineNumEnd == -1) 
00342                                 lineNumEnd = line.find(' ', lineNumStart);
00343 
00344                         num = line.mid(lineNumStart, lineNumEnd - lineNumStart).toInt();
00345                         num -= ofs; 
00346 
00347                         
00348                         
00349                         if (num <= 0) { 
00350                                 if (num < 0)
00351                                         dbp("ASSERT processDiff: start line number is %1", num);
00352                                 newAnn.clear();
00353                                 return;
00354                         }
00355                         cur = newAnn.at(num - 1);
00356                         break;
00357                 case '+':
00358                         if (cur != newAnn.end()) {
00359                                 cur = newAnn.insert(cur, author);
00360                                 ++cur;
00361                         } else {
00362                                 newAnn.append(author);
00363                                 cur = newAnn.end();
00364                         }
00365                         break;
00366                 case '-':
00367                         if (!newAnn.isEmpty()) {
00368                                 if (cur != newAnn.end())
00369                                         cur = newAnn.remove(cur);
00370                                 else {
00371                                         dbp("ASSERT processDiff: remove end of "
00372                                             "file, diff is %1", diff);
00373                                         isError = true;
00374                                         return;
00375                                 }
00376                         } else {
00377                                 dbp("ASSERT processDiff: remove line from "
00378                                     "empty annotation, diff is %1", diff);
00379                                 isError = true;
00380                                 return;
00381                         }
00382                         break;
00383                 case '\\':
00384                         
00385                         
00386                         if (line[1] == ' ')
00387                                 break;
00388 
00389                         
00390                 default:
00391                         ++cur;
00392                         break;
00393                 }
00394         }
00395 }
00396 
00397 void Annotate::updatePatchScript(SCRef sha, SCRef par) {
00398 
00399         if (sha == QGit::ZERO_SHA) 
00400                 return;            
00401 
00402         const QString runCmd(sha + " " + par);
00403         patchScript.append(runCmd).append('\n');
00404 }
00405 
00406 const QString Annotate::getNextPatch(QString& patchFile, SCRef fileName, SCRef sha) {
00407 
00408         if (sha == QGit::ZERO_SHA) {
00409                 
00410                 
00411                 QString runOutput;
00412                 QString runCmd("git diff-index --no-color -r -m -p HEAD -- " + QGit::QUOTE_CHAR +
00413                                 fileName + QGit::QUOTE_CHAR);
00414 
00415                 git->run(runCmd, &runOutput);
00416                 if (cancelingAnnotate)
00417                         return "";
00418 
00419                 
00420                 const QString nextHeader("\n:100644 100644 " + QGit::ZERO_SHA + ' '
00421                                          + nextFileSha + " M\t" + fileName + '\n');
00422                 runOutput.append(nextHeader);
00423                 patchFile.prepend(runOutput);
00424                 nextFileSha = QGit::ZERO_SHA;
00425         }
00426         
00427         
00428         ah[sha].fileSha = nextFileSha;
00429 
00430         bool noNewLine = (patchFile[0] == ':');
00431         if (noNewLine)
00432                 dbp("WARNING: No newline at the end of %1 patch", sha);
00433 
00434         int end = (noNewLine) ? 0 : patchFile.find("\n:");
00435         QString diff;
00436         if (end != -1) {
00437                 diff = patchFile.left(end + 1);
00438                 nextFileSha = patchFile.mid(end + 57 - (int)noNewLine, 40);
00439                 patchFile.remove(0, end + 100); 
00440         } else
00441                 diff = patchFile;
00442 
00443         int start = diff.find('@');
00444         
00445         diff = (start != -1) ? diff.mid(start) : "";
00446 
00447         int i = 0;
00448         while (diffMap.contains(Key(sha, i)))
00449                 i++;
00450         diffMap.insert(Key(sha, i), diff);
00451         return diff;
00452 }
00453 
00454 bool Annotate::getNextSection(SCRef d, int& idx, QString& sec, SCRef target) {
00455 
00456         if (idx >= (int)d.length())
00457                 return false;
00458 
00459         int newIdx = d.find(target, idx);
00460         if (newIdx == -1) 
00461                 newIdx = d.length() - 1;
00462 
00463         sec = d.mid(idx, newIdx - idx + 1);
00464         idx = newIdx + 1;
00465         return true;
00466 }
00467 
00468 
00469 
00470 
00471 
00472 
00473 bool Annotate::getRange(SCRef sha, RangeInfo* r) {
00474 
00475         if (!rangeMap.contains(sha) || !valid || canceled) {
00476                 r->clear();
00477                 return false;
00478         }
00479         *r = rangeMap[sha]; 
00480         return true;
00481 }
00482 
00483 void Annotate::updateCrossRanges(SCRef chunk, bool rev, int fileLen, int ofs, RangeInfo* r) {
00484 
00485 
00486 
00487 
00488 
00489 
00490 
00491 
00492 
00493 
00494 
00495 
00496 
00497 
00498 
00499 
00500 
00501 
00502 
00503         
00504         
00505         
00506         QStringList beforeAnn, afterAnn;
00507         for (int lineNum = ofs + 1; lineNum <= ofs + fileLen; lineNum++)
00508                 beforeAnn.append(QString::number(lineNum));
00509 
00510         const QString fakedAuthor("*");
00511         setAnnotation(chunk, fakedAuthor, beforeAnn, afterAnn, ofs);
00512         int newStart = ofs + 1;
00513         int newEnd = ofs + fileLen;
00514 
00515         if (rev) {
00516                 
00517                 
00518                 QStringList::const_iterator itStart(afterAnn.at(r->start - ofs - 1));
00519                 QStringList::const_iterator itEnd(afterAnn.at(r->end - ofs - 1));
00520 
00521                 bool leftExtended = (*itStart == fakedAuthor);
00522                 bool rightExtended = (*itEnd == fakedAuthor);
00523 
00524                 
00525                 
00526                 ++itStart;
00527                 do {
00528                         --itStart;
00529                         if (*itStart != fakedAuthor) {
00530                                 newStart = (*itStart).toInt();
00531                                 break;
00532                         }
00533                 } while (itStart != afterAnn.constBegin());
00534 
00535                 while (itEnd != afterAnn.constEnd()) {
00536 
00537                         if (*itEnd != fakedAuthor) {
00538                                 newEnd = (*itEnd).toInt();
00539                                 break;
00540                         }
00541                         ++itEnd;
00542                 }
00543                 if (leftExtended && *itStart != fakedAuthor)
00544                         newStart++;
00545 
00546                 if (rightExtended && itEnd != afterAnn.constEnd())
00547                         newEnd--;
00548 
00549                 r->modified = (leftExtended || rightExtended);
00550 
00551                 if (!r->modified) { 
00552                         for (int i = r->start; i <= r->end; ++i, ++itStart)
00553                                 if (i - r->start != (*itStart).toInt() - newStart) {
00554                                         r->modified = true;
00555                                         break;
00556                                 }
00557                 }
00558                 if (newStart > newEnd) 
00559                         newStart = newEnd = 0;
00560 
00561         } else { 
00562 
00563                 
00564                 QStringList::const_iterator itStart(afterAnn.constEnd());
00565                 QStringList::const_iterator itEnd(afterAnn.constEnd());
00566 
00567                 QStringList::const_iterator it(afterAnn.constBegin());
00568                 for (int lineNum = ofs + 1; it != afterAnn.constEnd(); ++lineNum, ++it) {
00569 
00570                         if (*it != fakedAuthor) {
00571 
00572                                 if ((*it).toInt() <= r->start) {
00573                                         newStart = lineNum;
00574                                         itStart = it;
00575                                 }
00576                                 if (  (*it).toInt() >= r->end
00577                                     && itEnd == afterAnn.constEnd()) { 
00578                                         newEnd = lineNum;
00579                                         itEnd = it;
00580                                 }
00581                         }
00582                 }
00583                 if (itStart != afterAnn.constEnd() && (*itStart).toInt() < r->start)
00584                         newStart++;
00585 
00586                 if (itEnd != afterAnn.constEnd() && (*itEnd).toInt() > r->end)
00587                         newEnd--;
00588 
00589                 r->modified = (itStart == afterAnn.constEnd() || itEnd == afterAnn.constEnd());
00590 
00591                 if (!r->modified) { 
00592                         for (int i = r->start; i <= r->end; ++itStart, i++)
00593                                 if ((*itStart).toInt() != i) {
00594                                 r->modified = true;
00595                                 break;
00596                                 }
00597                 }
00598                 if (newStart > newEnd) 
00599                         newStart = newEnd = 0;
00600         }
00601         r->start = newStart;
00602         r->end = newEnd;
00603 }
00604 
00605 void Annotate::updateRange(RangeInfo* r, SCRef diff, bool reverse) {
00606 
00607         r->modified = false;
00608         if (r->start == 0)
00609                 return;
00610 
00611         
00612         
00613         
00614         
00615         
00616         
00617         int idx = 0;
00618         QString chunk;
00619         QStringList chunkList;
00620         while (getNextSection(diff, idx, chunk, "\n@"))
00621                 if (reverse)
00622                         chunkList.prepend(chunk);
00623                 else
00624                         chunkList.append(chunk);
00625 
00626         QStringList::const_iterator chunkIt(chunkList.constBegin());
00627         while (chunkIt != chunkList.constEnd()) {
00628 
00629                 
00630                 
00631                 
00632                 
00633                 
00634                 chunk = *chunkIt++;
00635                 int m = chunk.find('-');
00636                 int c1 = chunk.find(',', m);
00637                 int p = chunk.find('+', c1);
00638                 int c2 = chunk.find(',', p);
00639                 int e = chunk.find(' ', c2);
00640 
00641                 int oldLineCnt = chunk.mid(c1 + 1, p - c1 - 2).toInt();
00642                 int newLineId = chunk.mid(p + 1, c2 - p - 1).toInt();
00643                 int newLineCnt = chunk.mid(c2 + 1, e - c2 - 1).toInt();
00644                 int lineNumDiff = newLineCnt - oldLineCnt;
00645 
00646                 
00647                 
00648                 int patchStart = newLineId;
00649 
00650                 
00651                 int patchEnd = patchStart + ((reverse) ? newLineCnt : oldLineCnt);
00652                 patchEnd--; 
00653 
00654                 
00655                 if (patchStart > r->end)
00656                         continue;
00657 
00658                 
00659                 if (patchEnd < r->start) {
00660                         r->start += ((reverse) ? -lineNumDiff : lineNumDiff);
00661                         r->end += ((reverse) ? -lineNumDiff : lineNumDiff);
00662                         continue;
00663                 }
00664                 
00665                 if (patchStart >= r->start && patchEnd <= r->end) {
00666                         r->end += ((reverse) ? -lineNumDiff : lineNumDiff);
00667                         r->modified = true;
00668                         continue;
00669                 }
00670                 
00671                 
00672 
00673                 
00674                 int beforePadding = (r->start > patchStart) ? 0 : patchStart - r->start;
00675 
00676                 
00677                 int afterPadding = (patchEnd > r->end) ? 0 : r->end - patchEnd;
00678 
00679                 
00680                 
00681                 int fileLenght = beforePadding + oldLineCnt + afterPadding;
00682 
00683                 
00684                 
00685                 
00686                 
00687                 
00688                 
00689                 
00690                 
00691                 int fileOffset = newLineId - beforePadding - 1;
00692 
00693                 
00694                 
00695                 updateCrossRanges(chunk, reverse, fileLenght, fileOffset, r);
00696         }
00697 }
00698 
00699 const QString Annotate::getAncestor(SCRef sha, SCRef fileName, int* shaIdx) {
00700 
00701         QString fileSha;
00702 
00703         try {
00704                 annotateActivity = true;
00705                 EM_REGISTER(exAnnCanceled);
00706 
00707                 fileSha = git->getFileSha(fileName, sha); 
00708                 if (fileSha.isEmpty()) {
00709                         dbp("ASSERT in getAncestor: empty file from %1", sha);
00710                         return "";
00711                 }
00712                 EM_REMOVE(exAnnCanceled);
00713                 annotateActivity = false;
00714 
00715         } catch(int i) {
00716 
00717                 EM_REMOVE(exAnnCanceled);
00718                 annotateActivity = false;
00719 
00720                 if (EM_MATCH(i, exAnnCanceled, "getting ancestor")) {
00721                         EM_THROW_PENDING;
00722                         return "";
00723                 }
00724                 const QString info("Exception \'" + EM_DESC(i) + "\' "
00725                                    "not handled in Annotation lookup...re-throw");
00726                 dbs(info);
00727                 throw;
00728         }
00729         
00730         
00731         
00732         
00733         
00734         for (*shaIdx = 0; *shaIdx < (int)histRevOrder.count(); (*shaIdx)++) {
00735 
00736                 const FileAnnotation& fa(ah[histRevOrder[*shaIdx]]);
00737                 if (fa.fileSha == fileSha)
00738                         return histRevOrder[*shaIdx];
00739         }
00740         
00741         
00742         
00743         if (git->getAllRefSha(Git::UN_APPLIED).contains(sha))
00744                 return histRevOrder.first();
00745 
00746         dbp("ASSERT in getAncestor: ancestor of %1 not found", sha);
00747         return "";
00748 }
00749 
00750 bool Annotate::isDescendant(SCRef sha, SCRef target) {
00751 
00752 
00753 
00754 
00755 
00756         const Rev* r = git->revLookup(sha, fh);
00757         if (!r)
00758                 return false;
00759 
00760         int shaIdx = r->orderIdx;
00761         r = git->revLookup(target, fh);
00762 
00763         while (r && r->orderIdx < shaIdx && r->parentsCount() == 1)
00764                 r = git->revLookup(r->parent(0), fh);
00765 
00766         return (r && r->orderIdx == shaIdx);
00767 }
00768 
00769 
00770 
00771 
00772 
00773 
00774 
00775 
00776 
00777 
00778 
00779 const QString Annotate::computeRanges(SCRef sha, int rangeStart, int rangeEnd, SCRef target) {
00780 
00781         rangeMap.clear();
00782 
00783         if (!valid || canceled) {
00784                 dbp("ASSERT in computeRanges: annotation from %1 not valid", sha);
00785                 return "";
00786         }
00787         QString ancestor(sha);
00788         int shaIdx;
00789         for (shaIdx = 0; shaIdx < (int)histRevOrder.count(); shaIdx++)
00790                 if (histRevOrder[shaIdx] == sha)
00791                         break;
00792 
00793         if (shaIdx == (int)histRevOrder.count()) { 
00794                 ancestor = getAncestor(sha, fileName, &shaIdx);
00795                 if (ancestor.isEmpty())
00796                         return "";
00797         }
00798         
00799         rangeMap.insert(ancestor, RangeInfo(rangeStart, rangeEnd, true));
00800 
00801         
00802         bool isDirectDescendant = isDescendant(ancestor, target);
00803 
00804         
00805         const QString oldest(histRevOrder.last()); 
00806         const Rev* curRev = git->revLookup(ancestor, fh); 
00807         QString curRevSha(curRev->sha());
00808         while (curRevSha != oldest && !isDirectDescendant) {
00809 
00810                 if (!diffMap.contains(Key(curRevSha, 0))) {
00811                         if (curRev->parentsCount() == 0)  
00812                                 break;
00813 
00814                         dbp("ASSERT in rangeFilter 1: diff for %1 not found", curRevSha);
00815                         return "";
00816                 }
00817                 RangeInfo r(rangeMap[curRevSha]);
00818                 updateRange(&r, diffMap[Key(curRevSha, 0)], true);
00819 
00820                 
00821                 
00822                 
00823                 rangeMap[curRevSha].modified = r.modified;
00824 
00825                 
00826                 
00827                 
00828                 
00829                 
00830                 
00831                 
00832                 r.modified = (r.start != 0);
00833 
00834                 if (curRev->parentsCount() == 0)
00835                         break;
00836 
00837                 curRev = git->revLookup(curRev->parent(0), fh);
00838                 curRevSha = curRev->sha();
00839                 rangeMap.insert(curRevSha, r);
00840 
00841                 if (curRevSha == target) 
00842                         return ancestor;
00843         }
00844         
00845         
00846         
00847         if (!isDirectDescendant)
00848                 shaIdx = histRevOrder.count() - 1;
00849 
00850         for ( ; shaIdx >= 0; shaIdx--) {
00851 
00852                 SCRef sha(histRevOrder[shaIdx]);
00853 
00854                 if (!rangeMap.contains(sha)) {
00855 
00856                         curRev = git->revLookup(sha, fh);
00857 
00858                         if (curRev->parentsCount() == 0) {
00859                                 
00860                                 
00861                                 
00862                                 
00863                                 rangeMap.insert(sha, RangeInfo());
00864                                 continue;
00865                         }
00866                         if (!diffMap.contains(Key(sha, 0))) {
00867                                 dbp("ASSERT in rangeFilter 2: diff for %1 not found", sha);
00868                                 return "";
00869                         }
00870                         SCRef parSha(curRev->parent(0));
00871 
00872                         if (!rangeMap.contains(parSha)) {
00873 
00874                                 if (isDirectDescendant) 
00875                                         continue;       
00876 
00877                                 dbp("ASSERT in rangeFilter: range info for %1 not found", parSha);
00878                                 return "";
00879                         }
00880                         RangeInfo r(rangeMap[parSha]);
00881                         updateRange(&r, diffMap[Key(sha, 0)], false);
00882                         rangeMap.insert(sha, r);
00883 
00884                         if (sha == target) 
00885                                 return ancestor;
00886                 }
00887         }
00888         return ancestor;
00889 }
00890 
00891 bool Annotate::seekPosition(int* paraFrom, int* paraTo, SCRef fromSha, SCRef toSha) {
00892 
00893         if ((*paraFrom == 0 && *paraTo == 0) || fromSha == toSha)
00894                 return true;
00895 
00896         QMap<QString, RangeInfo> backup;
00897         backup = rangeMap;  
00898 
00899         
00900         if (computeRanges(fromSha, *paraFrom + 1, *paraTo + 1, toSha).isEmpty())
00901                 goto fail;
00902 
00903         if (!rangeMap.contains(toSha))
00904                 goto fail;
00905 
00906         *paraFrom = rangeMap[toSha].start - 1;
00907         *paraTo = rangeMap[toSha].end - 1;
00908         rangeMap = backup;
00909         return true;
00910 
00911 fail:
00912         rangeMap = backup;
00913         return false;
00914 }