git_startup.cpp

Go to the documentation of this file.
00001 /*
00002         Description: start-up repository opening and reading
00003 
00004         Author: Marco Costalba (C) 2005-2006
00005 
00006         Copyright: See COPYING file that comes with this distribution
00007 
00008 */
00009 #include <unistd.h> // usleep()
00010 #include <qapplication.h>
00011 #include <qsettings.h>
00012 #include <qeventloop.h>
00013 #include <qregexp.h>
00014 #include <qtextcodec.h>
00015 #include "exceptionmanager.h"
00016 #include "rangeselectimpl.h"
00017 #include "lanes.h"
00018 #include "myprocess.h"
00019 #include "cache.h"
00020 #include "annotate.h"
00021 #include "mainimpl.h"
00022 #include "dataloader.h"
00023 #include "git.h"
00024 
00025 #define POST_MSG(x) QApplication::postEvent(parent(), new MessageEvent(x))
00026 
00027 using namespace QGit;
00028 
00029 const QStringList Git::getArgs(bool askForRange, bool* quit) {
00030 
00031         static bool startup = true; // it's OK to be unique among qgit windows
00032         if (startup) {
00033                 curRange = "";
00034                 for (int i = 1; i < qApp->argc(); i++) {
00035                         // in arguments with spaces double quotes
00036                         // are stripped by Qt, so re-add them
00037                         QString arg(qApp->argv()[i]);
00038                         if (arg.contains(' '))
00039                                 arg.prepend('\"').append('\"');
00040 
00041                         curRange.append(arg + ' ');
00042                 }
00043         }
00044         if (    askForRange
00045             &&  testFlag(RANGE_SELECT_F)
00046             && (!startup || curRange.isEmpty())) {
00047 
00048                 RangeSelectImpl rs((QWidget*)parent(), &curRange,
00049                                     getAllRefNames(TAG, !optOnlyLoaded), quit, this);
00050                 rs.exec(); // modal execution
00051                 if (*quit)
00052                         return QStringList();
00053         }
00054         startup = false;
00055         return MyProcess::splitArgList(curRange);
00056 }
00057 
00058 const QString Git::getBaseDir(bool* changed, SCRef wd, bool* ok, QString* gd) {
00059 // we could run from a subdirectory, so we need to get correct directories
00060 
00061         QString runOutput, tmp(workDir);
00062         workDir = wd;
00063         errorReportingEnabled = false;
00064         bool ret = run("git rev-parse --git-dir", &runOutput); // run under newWorkDir
00065         errorReportingEnabled = true;
00066         workDir = tmp;
00067         runOutput = runOutput.stripWhiteSpace();
00068         if (!ret || runOutput.isEmpty()) {
00069                 *changed = true;
00070                 if (ok)
00071                         *ok = false;
00072                 return wd;
00073         }
00074         // 'git rev-parse --git-dir' output could be a relative
00075         // to working dir (as ex .git) or an absolute path
00076         QDir d(runOutput.startsWith("/") ? runOutput : wd + "/" + runOutput);
00077         *changed = (d.absPath() != gitDir);
00078         if (gd)
00079                 *gd = d.absPath();
00080         if (ok)
00081                 *ok = true;
00082         d.cdUp();
00083         return d.absPath();
00084 }
00085 
00086 Git::Reference* Git::lookupReference(SCRef sha, bool create) {
00087 
00088         RefMap::iterator it(refsShaMap.find(sha));
00089         if (it == refsShaMap.end() && create)
00090                 it = refsShaMap.insert(sha, Reference());
00091 
00092         return (it != refsShaMap.end() ? &(*it) : NULL);
00093 }
00094 
00095 bool Git::getRefs() {
00096 
00097         // check for a StGIT stack
00098         QDir d(gitDir);
00099         QString stgCurBranch;
00100         if (d.exists("patches")) { // early skip
00101                 errorReportingEnabled = false;
00102                 isStGIT = run("stg branch", &stgCurBranch); // slow command
00103                 errorReportingEnabled = true;
00104                 stgCurBranch = stgCurBranch.stripWhiteSpace();
00105         } else
00106                 isStGIT = false;
00107 
00108         // check for a merge and read current branch sha
00109         isMergeHead = d.exists("MERGE_HEAD");
00110         QString curBranchSHA, curBranchName;
00111         if (!run("git rev-parse HEAD", &curBranchSHA))
00112                 return false;
00113 
00114         if (!run("git branch", &curBranchName))
00115                 return false;
00116 
00117         curBranchSHA = curBranchSHA.stripWhiteSpace();
00118         curBranchName = curBranchName.prepend('\n').section("\n*", 1);
00119         curBranchName = curBranchName.section('\n', 0, 0).stripWhiteSpace();
00120 
00121         // read refs, normally unsorted
00122         QString runOutput;
00123         if (!run("git show-ref -d", &runOutput))
00124                 return false;
00125 
00126         refsShaMap.clear();
00127         QString prevRefSha;
00128         QStringList patchNames, patchShas;
00129         const QStringList rLst(QStringList::split('\n', runOutput));
00130         FOREACH_SL (it, rLst) {
00131 
00132                 SCRef revSha = (*it).left(40);
00133                 SCRef refName = (*it).mid(41);
00134 
00135                 if (refName.startsWith("refs/patches/")) {
00136 
00137                         // save StGIT patch sha, to be used later
00138                         SCRef patchesDir("refs/patches/" + stgCurBranch + "/");
00139                         if (refName.startsWith(patchesDir)) {
00140                                 patchNames.append(refName.mid(patchesDir.length()));
00141                                 patchShas.append(revSha);
00142                         }
00143                         // StGIT patches should not be added to refs,
00144                         // but an applied StGIT patch could be also an head or
00145                         // a tag in this case will be added in another loop cycle
00146                         continue;
00147                 }
00148                 // one rev could have many tags
00149                 Reference* cur = lookupReference(revSha, optCreate);
00150 
00151                 if (refName.startsWith("refs/tags/")) {
00152 
00153                         if (refName.endsWith("^{}")) { // tag dereference
00154 
00155                                 // we assume that a tag dereference follows strictly
00156                                 // the corresponding tag object in rLst. So the
00157                                 // last added tag is a tag object, not a commit object
00158                                 cur->tags.append(refName.mid(10, refName.length() - 13));
00159 
00160                                 // store tag object. Will be used to fetching
00161                                 // tag message (if any) when necessary.
00162                                 cur->tagObj = prevRefSha;
00163 
00164                                 // tagObj must be removed from ref map
00165                                 refsShaMap.remove(prevRefSha);
00166 
00167                         } else
00168                                 cur->tags.append(refName.mid(10));
00169 
00170                         cur->type |= TAG;
00171 
00172                 } else if (refName.startsWith("refs/heads/")) {
00173 
00174                         cur->branches.append(refName.mid(11));
00175                         cur->type |= BRANCH;
00176                         if (curBranchSHA == revSha) {
00177                                 cur->type |= CUR_BRANCH;
00178                                 cur->currentBranch = curBranchName;
00179                         }
00180                 } else if (refName.startsWith("refs/remotes/") && !refName.endsWith("HEAD")) {
00181 
00182                         cur->remoteBranches.append(refName.mid(13));
00183                         cur->type |= RMT_BRANCH;
00184 
00185                 } else if (!refName.startsWith("refs/bases/") && !refName.endsWith("HEAD")) {
00186 
00187                         cur->refs.append(refName);
00188                         cur->type |= REF;
00189                 }
00190                 prevRefSha = revSha;
00191         }
00192         if (isStGIT && !patchNames.isEmpty())
00193                 parseStGitPatches(patchNames, patchShas);
00194 
00195         return !refsShaMap.empty();
00196 }
00197 
00198 void Git::parseStGitPatches(SCList patchNames, SCList patchShas) {
00199 
00200         patchesStillToFind = 0;
00201 
00202         // get patch names and status of current branch
00203         QString runOutput;
00204         if (!run("stg series", &runOutput))
00205                 return;
00206 
00207         const QStringList pl(QStringList::split('\n', runOutput));
00208         FOREACH_SL (it, pl) {
00209 
00210                 SCRef status = (*it).left(1);
00211                 SCRef patchName = (*it).mid(2);
00212 
00213                 bool applied = (status == "+" || status == ">");
00214                 int pos = patchNames.findIndex(patchName);
00215                 if (pos == -1) {
00216                         dbp("ASSERT in Git::parseStGitPatches(), patch %1 "
00217                             "not found in references list.", patchName);
00218                         continue;
00219                 }
00220                 Reference* cur = lookupReference(patchShas[pos], optCreate);
00221                 cur->stgitPatch = patchName;
00222                 cur->type |= (applied ? APPLIED : UN_APPLIED);
00223 
00224                 if (applied)
00225                         patchesStillToFind++;
00226         }
00227 }
00228 
00229 const QStringList Git::getOthersFiles() {
00230 // add files present in working directory but not in git archive
00231 
00232         QString runCmd("git ls-files --others");
00233         QSettings settings;
00234         QString exFile(settings.readEntry(APP_KEY + EX_KEY, EX_DEF));
00235         if (!exFile.isEmpty()) {
00236                 QString path = (exFile.startsWith("/")) ? exFile : workDir + "/" + exFile;
00237                 if (QFile::exists(path))
00238                         runCmd.append(" --exclude-from=" + quote(exFile));
00239         }
00240         QString exPerDir(settings.readEntry(APP_KEY + EX_PER_DIR_KEY, EX_PER_DIR_DEF));
00241         if (!exPerDir.isEmpty())
00242                 runCmd.append(" --exclude-per-directory=" + quote(exPerDir));
00243 
00244         QString runOutput;
00245         run(runCmd, &runOutput);
00246         return QStringList::split('\n', runOutput);
00247 }
00248 
00249 const Rev* Git::fakeWorkDirRev(SCRef parent, SCRef log, SCRef longLog, int idx, FileHistory* fh) {
00250 
00251         QString date(QString::number(QDateTime::currentDateTime().toTime_t()) + " +0200");
00252         QString data(ZERO_SHA + ' ' + parent + "\ntree ");
00253         data.append(ZERO_SHA);
00254         data.append("\nparent " + parent);
00255         data.append("\nauthor Working Dir " + date);
00256         data.append("\ncommitter Working Dir " + date);
00257         data.append("\n\n    " + log + '\n');
00258         data.append(longLog);
00259 
00260         QByteArray* ba = new QByteArray(data.length() + 1);
00261         memcpy(ba->data(), data.ascii(), data.length());
00262         ba->at(data.length()) = '\0';
00263         fh->rowData.append(ba);
00264         int dummy;
00265         Rev* c = new Rev(*ba, 0, idx, &dummy);
00266         c->isDiffCache = true;
00267         c->lanes.append(EMPTY);
00268         return c;
00269 }
00270 
00271 const RevFile* Git::fakeWorkDirRevFile(const WorkingDirInfo& wd) {
00272 
00273         RevFile* rf = new RevFile();
00274         parseDiffFormat(*rf, wd.diffIndex);
00275         rf->onlyModified = false;
00276 
00277         FOREACH_SL (it, wd.otherFiles) {
00278 
00279                 appendFileName(*rf, *it);
00280                 rf->status.append(RevFile::UNKNOWN);
00281                 rf->mergeParent.append(1);
00282         }
00283         RevFile cachedFiles;
00284         parseDiffFormat(cachedFiles, wd.diffIndexCached);
00285         for (uint i = 0; i < rf->status.count(); i++)
00286                 if (findFileIndex(cachedFiles, filePath(*rf, i)) != -1)
00287                         rf->status[i] |= RevFile::IN_INDEX;
00288         return rf;
00289 }
00290 
00291 void Git::getDiffIndex() {
00292 
00293         QString status;
00294         if (!run("git status", &status)) // git status refreshes the index, run as first
00295                 return;
00296 
00297         if (!run("git diff-index --no-color HEAD", &_wd.diffIndex))
00298                 return;
00299 
00300         // check for files already updated in cache, we will
00301         // save this information in status third field
00302         if (!run("git diff-index --no-color --cached HEAD", &_wd.diffIndexCached))
00303                 return;
00304 
00305         // get any file not in tree
00306         _wd.otherFiles = getOthersFiles();
00307 
00308         // now mockup a RevFile
00309         revsFiles.insert(ZERO_SHA, fakeWorkDirRevFile(_wd));
00310 
00311         // then mockup the corresponding Rev
00312         QString parent;
00313         if (!run("git rev-parse HEAD", &parent))
00314                 return;
00315 
00316         parent = parent.section('\n', 0, 0);
00317         SCRef log = (isNothingToCommit() ? "Nothing to commit" : "Working dir changes");
00318         const Rev* r = fakeWorkDirRev(parent, log, status, revData.revOrder.count(), &revData);
00319         revData.revs.insert(ZERO_SHA, r);
00320         revData.revOrder.append(ZERO_SHA);
00321 
00322         // finally send it to GUI
00323         emit newRevsAdded(&revData, revData.revOrder);
00324 }
00325 
00326 void Git::parseDiffFormatLine(RevFile& rf, SCRef line, int parNum) {
00327 
00328         if (line[1] == ':') { // it's a combined merge
00329 
00330                 /* For combined merges rename/copy information is useless
00331                  * because nor the original file name, nor similarity info
00332                  * is given, just the status tracks that in the left/right
00333                  * branch a renamed/copy occurred (as example status could
00334                  * be RM or MR). For visualization purposes we could consider
00335                  * the file as modified
00336                  */
00337                 appendFileName(rf, line.section('\t', -1));
00338                 setStatus(rf, "M");
00339                 rf.mergeParent.append(parNum);
00340         } else { // faster parsing in normal case
00341 
00342                 if (line[98] == '\t') {
00343                         appendFileName(rf, line.mid(99));
00344                         setStatus(rf, line.at(97));
00345                         rf.mergeParent.append(parNum);
00346                 } else
00347                         // it's a rename or a copy, we are not in fast path now!
00348                         setExtStatus(rf, line.mid(97), parNum);
00349         }
00350 }
00351 
00352 void Git::setStatus(RevFile& rf, SCRef rowSt) {
00353 
00354         char status = rowSt.at(0).latin1();
00355         switch (status) {
00356         case 'M':
00357         case 'T':
00358                 rf.status.append(RevFile::MODIFIED);
00359                 break;
00360         case 'D':
00361                 rf.status.append(RevFile::DELETED);
00362                 rf.onlyModified = false;
00363                 break;
00364         case 'A':
00365                 rf.status.append(RevFile::NEW);
00366                 rf.onlyModified = false;
00367                 break;
00368         case '?':
00369                 rf.status.append(RevFile::UNKNOWN);
00370                 rf.onlyModified = false;
00371                 break;
00372         default:
00373                 dbp("ASSERT in Git::setStatus, unknown status <%1>. "
00374                     "'MODIFIED' will be used instead.", rowSt);
00375                 rf.status.append(RevFile::MODIFIED);
00376                 break;
00377         }
00378 }
00379 
00380 void Git::setExtStatus(RevFile& rf, SCRef rowSt, int parNum) {
00381 
00382         const QStringList sl(QStringList::split('\t', rowSt));
00383         if (sl.count() != 3) {
00384                 dbp("ASSERT in setExtStatus, unexpected status string %1", rowSt);
00385                 return;
00386         }
00387         // we want store extra info with format "orig --> dest (Rxx%)"
00388         // but git give us something like "Rxx\t<orig>\t<dest>"
00389         SCRef type = sl[0];
00390         SCRef orig = sl[1];
00391         SCRef dest = sl[2];
00392         const QString extStatusInfo(orig + " --> " + dest + " (" + type + "%)");
00393 
00394         /*
00395            NOTE: we set rf.extStatus size equal to position of latest
00396                  copied/renamed file. So it can have size lower then
00397                  rf.count() if after copied/renamed file there are
00398                  others. Here we have no possibility to know final
00399                  dimension of this RefFile. We are still in parsing.
00400         */
00401 
00402         // simulate new file
00403         appendFileName(rf, dest);
00404         rf.mergeParent.append(parNum);
00405         rf.status.append(RevFile::NEW);
00406         rf.extStatus.resize(rf.status.size());
00407         rf.extStatus[rf.status.size() - 1] = extStatusInfo;
00408 
00409         // simulate deleted orig file only in case of rename
00410         if (type.at(0) == 'R') { // renamed file
00411                 appendFileName(rf, orig);
00412                 rf.mergeParent.append(parNum);
00413                 rf.status.append(RevFile::DELETED);
00414                 rf.extStatus.resize(rf.status.size());
00415                 rf.extStatus[rf.status.size() - 1] = extStatusInfo;
00416         }
00417         rf.onlyModified = false;
00418 }
00419 
00420 void Git::parseDiffFormat(RevFile& rf, SCRef buf) {
00421 
00422         int parNum = 1, startPos = 0, endPos = buf.find('\n');
00423         while (endPos != -1) {
00424 
00425                 SCRef line = buf.mid(startPos, endPos - startPos);
00426                 if (line[0] == ':') // avoid sha's in merges output
00427                         parseDiffFormatLine(rf, line, parNum);
00428                 else
00429                         parNum++;
00430 
00431                 startPos = endPos + 1;
00432                 endPos = buf.find('\n', endPos + 99);
00433         }
00434 }
00435 
00436 bool Git::startParseProc(SCList initCmd, FileHistory* fh) {
00437 
00438         DataLoader* dl = new DataLoader(this, fh); // auto-deleted when done
00439 
00440         connect(this, SIGNAL(cancelLoading(const FileHistory*)),
00441                 dl, SLOT(on_cancel(const FileHistory*)));
00442 
00443         connect(dl, SIGNAL(newDataReady(const FileHistory*)),
00444                 this, SLOT(on_newDataReady(const FileHistory*)));
00445 
00446         connect(dl, SIGNAL(loaded(const FileHistory*, ulong, int,
00447                 bool, const QString&, const QString&)), this,
00448                 SLOT(on_loaded(const FileHistory*, ulong, int,
00449                 bool, const QString&, const QString&)));
00450 
00451         return dl->start(initCmd, workDir);
00452 }
00453 
00454 bool Git::startRevList(SCList args, FileHistory* fh) {
00455 
00456         const QString baseCmd("git rev-list --header --parents --boundary --default HEAD");
00457         QStringList initCmd(QStringList::split(' ', baseCmd));
00458         if (!isMainHistory(fh)) {
00459                 // fetch history from all branches so any revision in
00460                 // main view that changes the file is always found
00461                 // NOTE: we don't use '--remove-empty' option because
00462                 // in case a file is deleted and then a new file with
00463                 // the same name is created again in the same directory
00464                 // then, with this option, file history is truncated to
00465                 // the file deletion revision.
00466                 initCmd += getAllRefSha(BRANCH | RMT_BRANCH);
00467                 initCmd += "--";
00468         } else
00469                 initCmd += "--topo-order";
00470 
00471         return startParseProc(initCmd + args, fh);
00472 }
00473 
00474 bool Git::startUnappliedList() {
00475 
00476         // TODO truncate sha to avoid overflow in 'git rev-list'
00477         // command line arguments
00478         const QStringList unAppliedSha(getAllRefSha(UN_APPLIED));
00479         if (unAppliedSha.isEmpty())
00480                 return false;
00481 
00482         // WARNING: with this command git rev-list could send spurious
00483         // revs so we need some filter out logic during loading
00484         QStringList initCmd(QStringList::split(' ', "git rev-list --header --parents"));
00485         initCmd += unAppliedSha;
00486         initCmd += QString::fromLatin1("^HEAD");
00487         return startParseProc(initCmd, &revData);
00488 }
00489 
00490 bool Git::stop(bool saveCache) {
00491 // normally called when changing directory or closing
00492 
00493         EM_RAISE(exGitStopped);
00494 
00495         // stop all data sending from process and asks them
00496         // to terminate. Note that process could still keep
00497         // running for a while although silently
00498         emit cancelAllProcesses(); // non blocking
00499 
00500         if (cacheNeedsUpdate && saveCache) {
00501 
00502                 cacheNeedsUpdate = false;
00503                 if (!filesLoadingCurSha.isEmpty()) // we are in the middle of a loading
00504                         revsFiles.remove(filesLoadingCurSha); // remove partial data
00505 
00506                 if (!revsFiles.isEmpty()) {
00507                         POST_MSG("Saving cache. Please wait...");
00508                         EM_PROCESS_EVENTS_NO_INPUT; // to paint the message
00509                         if (!Cache::save(gitDir, revsFiles, dirNamesVec, fileNamesVec))
00510                                 dbs("ERROR unable to save file names cache");
00511                 }
00512         }
00513         return allProcessDeleted();
00514 }
00515 
00516 void Git::clearRevs() {
00517 
00518         revData.clear("");
00519         patchesStillToFind = 0; // TODO TEST WITH FILTERING
00520         firstNonStGitPatch = "";
00521         _wd.clear();
00522         revsFiles.remove(ZERO_SHA);
00523 }
00524 
00525 void Git::clearFileNames() {
00526 
00527         revsFiles.clear();
00528         fileNamesMap.clear();
00529         dirNamesMap.clear();
00530         dirNamesVec.clear();
00531         fileNamesVec.clear();
00532         cacheNeedsUpdate = false;
00533 }
00534 
00535 bool Git::init(SCRef wd, bool askForRange, QStringList* filterList, bool* quit) {
00536 // normally called when changing git directory. Must be called after stop()
00537 
00538         *quit = false;
00539         bool filteredLoading = (filterList != NULL);
00540         clearRevs();
00541         try {
00542                 setThrowOnStop(true);
00543 
00544                 // check if repository is valid
00545                 bool repoChanged;
00546                 workDir = getBaseDir(&repoChanged, wd, &isGIT, &gitDir);
00547 
00548                 if (repoChanged) {
00549                         clearFileNames();
00550                         fileCacheAccessed = false;
00551                 }
00552                 if (!isGIT) {
00553                         setThrowOnStop(false);
00554                         return false;
00555                 }
00556                 const QString msg1("Path is '" + workDir + "'    Loading ");
00557                 QStringList args;
00558                 if (!filteredLoading) {
00559 
00560                         // update text codec according to repo settings
00561                         bool dummy;
00562                         QTextCodec::setCodecForCStrings(getTextCodec(&dummy));
00563 
00564                         // load references
00565                         POST_MSG(msg1 + "refs...");
00566                         if (!getRefs())
00567                                 dbs("WARNING: no tags or heads found");
00568 
00569                         // startup input range dialog
00570                         POST_MSG("");
00571                         args = getArgs(askForRange, quit); // must be called with refs loaded
00572                         if (*quit) {
00573                                 setThrowOnStop(false);
00574                                 return false;
00575                         }
00576                         // load StGit unapplied patches, must be after getRefs()
00577                         if (isStGIT) {
00578                                 loadingUnAppliedPatches = startUnappliedList();
00579                                 if (loadingUnAppliedPatches) {
00580 
00581                                         POST_MSG(msg1 + "StGIT unapplied patches...");
00582                                         while (loadingUnAppliedPatches) {
00583                                                 // WARNING we are in setRepository() now
00584                                                 usleep(20000);
00585                                                 EM_PROCESS_EVENTS;
00586                                         }
00587                                         revData.lns->clear(); // again to reset lanes
00588                                 }
00589                         }
00590                         // load working dir files
00591                         if (testFlag(DIFF_INDEX_F)) {
00592                                 POST_MSG(msg1 + "working directory changed files...");
00593                                 getDiffIndex(); // blocking, we are in setRepository() now
00594                         }
00595 
00596                 } else { // filteredLoading
00597                         args += getAllRefSha(BRANCH | RMT_BRANCH);
00598                         args += "--";
00599                         args += *filterList;
00600                 }
00601                 POST_MSG(msg1 + "revisions...");
00602                 if (!startRevList(args, &revData))
00603                         dbs("ERROR: unable to start git-rev-list loading");
00604 
00605                 setThrowOnStop(false);
00606                 return true;
00607 
00608         } catch (int i) {
00609 
00610                 setThrowOnStop(false);
00611 
00612                 if (isThrowOnStopRaised(i, "initializing")) {
00613                         EM_THROW_PENDING;
00614                         return false;
00615                 }
00616                 const QString info("Exception \'" + EM_DESC(i) + "\' "
00617                                    "not handled in init...re-throw");
00618                 dbs(info);
00619                 throw;
00620         }
00621 }
00622 
00623 void Git::on_newDataReady(const FileHistory* fh) {
00624 
00625         emit newRevsAdded(fh , fh->revOrder);
00626 }
00627 
00628 void Git::on_loaded(const FileHistory* fh, ulong byteSize, int loadTime,
00629                     bool normalExit, SCRef cmd, SCRef errorDesc) {
00630 
00631         if (!errorDesc.isEmpty()) {
00632                 MainExecErrorEvent* e = new MainExecErrorEvent(cmd, errorDesc);
00633                 QApplication::postEvent(parent(), e);
00634         }
00635         if (normalExit) { // do not send anything if killed
00636 
00637                 on_newDataReady(fh);
00638 
00639                 if (!loadingUnAppliedPatches) {
00640 
00641                         uint kb = byteSize / 1024;
00642                         float mbs = (float)byteSize / loadTime / 1000;
00643                         QString tmp;
00644                         tmp.sprintf("Loaded %i revisions  (%i KB),   "
00645                                     "time elapsed: %i ms  (%.2f MB/s)",
00646                                     fh->revs.count(), kb, loadTime, mbs);
00647 
00648                         emit loadCompleted(fh, tmp);
00649 
00650                         if (isMainHistory(fh))
00651                                 // check for revisions modified files out of fast path
00652                                 // let the dust to settle down, so that the first
00653                                 // revision is shown to user without noticeable delay
00654                                 QTimer::singleShot(500, this, SLOT(loadFileNames()));
00655                 }
00656         }
00657         if (loadingUnAppliedPatches)
00658                 loadingUnAppliedPatches = false;
00659 }
00660 
00661 void Git::populateFileNamesMap() {
00662 
00663         for (uint i = 0; i < dirNamesVec.count(); ++i)
00664                 dirNamesMap.insert(dirNamesVec[i], i);
00665 
00666         for (uint i = 0; i < fileNamesVec.count(); ++i)
00667                 fileNamesMap.insert(fileNamesVec[i], i);
00668 }
00669 
00670 void Git::loadFileNames() {
00671 // warning this function is not re-entrant, background
00672 // activity after repository has been loaded
00673 
00674         if (!fileCacheAccessed) { // deferred file names cache load
00675 
00676                 fileCacheAccessed = true;
00677                 bool isWorkingDirRevFile = (getFiles(ZERO_SHA) != NULL);
00678                 clearFileNames(); // any already created RevFile will be lost
00679 
00680                 if (Cache::load(gitDir, revsFiles, dirNamesVec, fileNamesVec))
00681                         populateFileNamesMap();
00682                 else
00683                         dbs("ERROR: unable to load file names cache");
00684 
00685                 if (isWorkingDirRevFile) // re-add ZERO_SHA with new file names indices
00686                         revsFiles.insert(ZERO_SHA, fakeWorkDirRevFile(_wd));
00687         }
00688 
00689         QString diffTreeBuf;
00690         FOREACH (StrVect, it, revData.revOrder) {
00691                 if (!revsFiles.find(*it)) {
00692                         const Rev* c = revLookup(*it);
00693                         if (c->parentsCount() == 1) // skip initials and merges
00694                                 diffTreeBuf.append(*it).append('\n');
00695                 }
00696         }
00697         if (!diffTreeBuf.isEmpty()) {
00698                 filesLoadingPending = filesLoadingCurSha = "";
00699                 const QString runCmd("git diff-tree --no-color -r -C --stdin");
00700                 runAsync(runCmd, this, diffTreeBuf);
00701         }
00702         indexTree();
00703 }
00704 
00705 int Git::addChunk(FileHistory* fh, const QByteArray& ba, int start) {
00706 
00707         RevMap& r = fh->revs;
00708         int nextStart;
00709         Rev* rev = new Rev(ba, start, r.count(), &nextStart); // only here we create a new rev
00710 
00711         if (nextStart == -1) { // half chunk detected
00712                 delete rev;
00713                 return -1;
00714         }
00715 
00716         SCRef sha = rev->sha();
00717 
00718         if (isStGIT) {
00719                 if (loadingUnAppliedPatches) { // filter out possible spurious revs
00720                         Reference* rf = lookupReference(sha);
00721                         if (!(rf && (rf->type & UN_APPLIED))) {
00722                                 delete rev;
00723                                 return nextStart;
00724                         }
00725                 }
00726                 // remove StGIT spurious revs filter
00727                 if (!firstNonStGitPatch.isEmpty() && firstNonStGitPatch == sha)
00728                         firstNonStGitPatch = "";
00729 
00730                 // StGIT called with --all option creates spurious revs so filter
00731                 // out unknown revs until no more StGIT patches are waited and
00732                 // firstNonStGitPatch is reached
00733                 if (!(firstNonStGitPatch.isEmpty() && patchesStillToFind == 0) &&
00734                     !loadingUnAppliedPatches && isMainHistory(fh)) {
00735 
00736                         Reference* rf = lookupReference(sha);
00737                         if (!(rf && (rf->type & APPLIED))) {
00738                                 delete rev;
00739                                 return nextStart;
00740                         }
00741                 }
00742                 if (r.find(sha)) {
00743                         // StGIT unapplied patches could be sent again by
00744                         // git rev-list as example if called with --all option.
00745                         if (r[sha]->isUnApplied) {
00746                                 delete rev;
00747                                 return nextStart;
00748                         }
00749                         dbp("ASSERT: addChunk sha <%1> already received", sha);
00750                 }
00751         }
00752         if (r.isEmpty() && !isMainHistory(fh)) {
00753                 bool added = copyDiffIndex(fh, sha);
00754                 rev->orderIdx = added ? 1 : 0;
00755         }
00756         r.insert(sha, rev);
00757         fh->revOrder.append(sha);
00758 
00759         if (isStGIT) {
00760                 // updateLanes() is called too late, after loadingUnAppliedPatches
00761                 // has been reset so update the lanes now.
00762                 if (loadingUnAppliedPatches) {
00763 
00764                         Rev* c = const_cast<Rev*>(revLookup(sha, fh));
00765                         c->isUnApplied = true;
00766                         c->lanes.append(UNAPPLIED);
00767 
00768                 } else if (patchesStillToFind > 0 || !isMainHistory(fh)) { // try to avoid costly lookup
00769 
00770                         Reference* rf = lookupReference(sha);
00771                         if (rf && (rf->type & APPLIED)) {
00772 
00773                                 Rev* c = const_cast<Rev*>(revLookup(sha, fh));
00774                                 c->isApplied = true;
00775                                 if (isMainHistory(fh)) {
00776                                         patchesStillToFind--;
00777                                         if (patchesStillToFind == 0)
00778                                                 // any rev will be discarded until
00779                                                 // firstNonStGitPatch arrives
00780                                                 firstNonStGitPatch = c->parent(0);
00781                                 }
00782                         }
00783                 }
00784         }
00785         return nextStart;
00786 }
00787 
00788 bool Git::copyDiffIndex(FileHistory* fh, SCRef parent) {
00789 // must be called with empty revs and empty revOrder
00790 
00791         if (!fh->revOrder.isEmpty() || !fh->revs.isEmpty()) {
00792                 dbs("ASSERT in copyDiffIndex: called with wrong context");
00793                 return false;
00794         }
00795         const Rev* r = revLookup(ZERO_SHA);
00796         if (!r)
00797                 return false;
00798 
00799         const RevFile* files = getFiles(ZERO_SHA);
00800         if (!files || findFileIndex(*files, fh->fileName) == -1)
00801                 return false;
00802 
00803         // insert a custom ZERO_SHA rev with proper parent
00804         const Rev* rf = fakeWorkDirRev(parent, "Working dir changes", "dummy", 0, fh);
00805         fh->revs.insert(ZERO_SHA, rf);
00806         fh->revOrder.append(ZERO_SHA);
00807         return true;
00808 }
00809 
00810 void Git::setLane(SCRef sha, FileHistory* fh) {
00811 
00812         Lanes* l = fh->lns;
00813         uint i = fh->firstFreeLane;
00814         const QValueVector<QString>& shaVec(fh->revOrder);
00815         for (uint cnt = shaVec.count(); i < cnt; ++i) {
00816                 SCRef curSha = shaVec[i];
00817                 Rev* r = const_cast<Rev*>(revLookup(curSha, fh));
00818                 if (r->lanes.count() == 0)
00819                         updateLanes(*r, *l, curSha);
00820 
00821                 if (curSha == sha)
00822                         break;
00823         }
00824         fh->firstFreeLane = ++i;
00825 }
00826 
00827 void Git::updateLanes(Rev& c, Lanes& lns, SCRef sha) {
00828 // we could get third argument from c.sha(), but we are in fast path here
00829 // and c.sha() involves a deep copy, so we accept a little redundancy
00830 
00831         if (lns.isEmpty())
00832                 lns.init(sha);
00833 
00834         bool isDiscontinuity;
00835         bool isFork = lns.isFork(sha, isDiscontinuity);
00836         bool isMerge = (c.parentsCount() > 1);
00837         bool isInitial = (c.parentsCount() == 0);
00838 
00839         if (isDiscontinuity)
00840                 lns.changeActiveLane(sha); // uses previous isBoundary state
00841 
00842         lns.setBoundary(c.isBoundary()); // update must be here
00843 
00844         if (isFork)
00845                 lns.setFork(sha);
00846         if (isMerge)
00847                 lns.setMerge(c.parents());
00848         if (c.isApplied)
00849                 lns.setApplied();
00850         if (isInitial)
00851                 lns.setInitial();
00852 
00853         lns.getLanes(c.lanes); // here lanes are snapshotted
00854 
00855         SCRef nextSha = (isInitial) ? "" : c.parent(0);
00856         lns.nextParent(nextSha);
00857 
00858         if (c.isApplied)
00859                 lns.afterApplied();
00860         if (isMerge)
00861                 lns.afterMerge();
00862         if (isFork)
00863                 lns.afterFork();
00864         if (lns.isBranch())
00865                 lns.afterBranch();
00866 
00867 //      QString tmp = "", tmp2;
00868 //      for (uint i = 0; i < c.lanes.count(); i++) {
00869 //              tmp2.setNum(c.lanes[i]);
00870 //              tmp.append(tmp2 + "-");
00871 //      }
00872 //      qDebug("%s %s",tmp.latin1(), c.sha.latin1());
00873 }
00874 
00875 void Git::on_procDataReady(const QByteArray& fileChunk) {
00876 
00877         if (filesLoadingPending.isEmpty())
00878                 filesLoadingPending = QString(fileChunk);
00879         else
00880                 filesLoadingPending.append(fileChunk); // add to previous half lines
00881 
00882         RevFile* rf = revsFiles[filesLoadingCurSha];
00883         int nextEOL = filesLoadingPending.find('\n');
00884         int lastEOL = -1;
00885         while (nextEOL != -1) {
00886 
00887                 SCRef line(filesLoadingPending.mid(lastEOL + 1, nextEOL - lastEOL - 1));
00888                 if (line.constref(0) != ':') {
00889                         SCRef sha = line.left(40);
00890                         if (!rf || sha != filesLoadingCurSha) { // new commit
00891                                 rf = new RevFile();
00892                                 revsFiles.insert(sha, rf);
00893                                 filesLoadingCurSha = sha;
00894                                 cacheNeedsUpdate = true;
00895                         } else
00896                                 dbp("ASSERT: repeated sha %1 in file names loading", sha);
00897                 } else // line.constref(0) == ':'
00898                         parseDiffFormatLine(*rf, line, 1);
00899 
00900                 lastEOL = nextEOL;
00901                 nextEOL = filesLoadingPending.find('\n', lastEOL + 1);
00902         }
00903         if (lastEOL != -1)
00904                 filesLoadingPending.remove(0, lastEOL + 1);
00905 }
00906 
00907 void Git::appendFileName(RevFile& rf, SCRef name) {
00908 
00909         int idx = name.findRev('/') + 1;
00910         SCRef dr = name.left(idx);
00911         SCRef nm = name.mid(idx);
00912 
00913         QMap<QString, int>::const_iterator it(dirNamesMap.find(dr));
00914         if (it == dirNamesMap.constEnd()) {
00915                 int idx = dirNamesVec.count();
00916                 dirNamesMap.insert(dr, idx);
00917                 dirNamesVec.append(dr);
00918                 rf.dirs.append(idx);
00919         } else
00920                 rf.dirs.append(*it);
00921 
00922         it = fileNamesMap.find(nm);
00923         if (it == fileNamesMap.constEnd()) {
00924                 int idx = fileNamesVec.count();
00925                 fileNamesMap.insert(nm, idx);
00926                 fileNamesVec.append(nm);
00927                 rf.names.append(idx);
00928         } else
00929                 rf.names.append(*it);
00930 }
00931 
00932 static bool vecContains(const QValueVector<int>& vect, int value) {
00933 
00934         for (uint i = 0, end = vect.count(); i < end; i++)
00935                 if (vect[i] == value)
00936                         return true;
00937         return false;
00938 }
00939 
00940 void Git::updateDescMap(const Rev* r,uint idx, QMap<QPair<uint, uint>, bool>& dm,
00941                         QMap<uint, QValueVector<int> >& dv) {
00942 
00943         QValueVector<int> descVec;
00944         if (r->descRefsMaster != -1) {
00945 
00946                 const Rev* tmp = revLookup(revData.revOrder[r->descRefsMaster]);
00947                 const QValueVector<int>& nr = tmp->descRefs;
00948 
00949                 for (uint i = 0; i < nr.count(); i++) {
00950 
00951                         if (!dv.contains(nr[i])) {
00952                                 dbp("ASSERT descendant for %1 not found", r->sha());
00953                                 return;
00954                         }
00955                         const QValueVector<int>& dvv = dv[nr[i]];
00956 
00957                         // copy the whole vector instead of each element
00958                         // in the first iteration of the loop below
00959                         descVec = dvv; // quick (shared) copy
00960 
00961                         for (uint y = 0; y < dvv.count(); y++) {
00962 
00963                                 uint v = (uint)dvv[y];
00964                                 QPair<uint, uint> key = qMakePair(idx, v);
00965                                 QPair<uint, uint> keyN = qMakePair(v, idx);
00966                                 dm.insert(key, true);
00967                                 dm.insert(keyN, false);
00968 
00969                                 // we don't want duplicated entry, otherwise 'dvv' grows
00970                                 // greatly in repos with many tagged development branches
00971                                 if (i > 0 && !vecContains(descVec, v)) // i > 0 is rare, no
00972                                         descVec.append(v);             // need to optimize
00973                         }
00974                 }
00975         }
00976         descVec.append(idx);
00977         dv.insert(idx, descVec);
00978 }
00979 
00980 void Git::mergeBranches(Rev* p, const Rev* r) {
00981 
00982         int r_descBrnMaster = (checkRef(r->sha(), BRANCH | RMT_BRANCH) ? r->orderIdx : r->descBrnMaster);
00983 
00984         if (p->descBrnMaster == r_descBrnMaster || r_descBrnMaster == -1)
00985                 return;
00986 
00987         // we want all the descendant branches, so just avoid duplicates
00988         const QValueVector<int>& src1 = revLookup(revData.revOrder[p->descBrnMaster])->descBranches;
00989         const QValueVector<int>& src2 = revLookup(revData.revOrder[r_descBrnMaster])->descBranches;
00990         QValueVector<int> dst(src1);
00991         for (uint i = 0; i < src2.count(); i++)
00992                 if (qFind(src1.constBegin(), src1.constEnd(), src2[i]) == src1.constEnd())
00993                         dst.append(src2[i]);
00994 
00995         p->descBranches = dst;
00996         p->descBrnMaster = p->orderIdx;
00997 }
00998 
00999 void Git::mergeNearTags(bool down, Rev* p, const Rev* r, const QMap<QPair<uint, uint>, bool>& dm) {
01000 
01001         bool isTag = checkRef(r->sha(), TAG);
01002         int r_descRefsMaster = isTag ? r->orderIdx : r->descRefsMaster;
01003         int r_ancRefsMaster = isTag ? r->orderIdx : r->ancRefsMaster;
01004 
01005         if (down && (p->descRefsMaster == r_descRefsMaster || r_descRefsMaster == -1))
01006                 return;
01007 
01008         if (!down && (p->ancRefsMaster == r_ancRefsMaster || r_ancRefsMaster == -1))
01009                 return;
01010 
01011         // we want the nearest tag only, so remove any tag
01012         // that is ancestor of any other tag in p U r
01013         const StrVect& ro = revData.revOrder;
01014         SCRef sha1 = down ? ro[p->descRefsMaster] : ro[p->ancRefsMaster];
01015         SCRef sha2 = down ? ro[r_descRefsMaster] : ro[r_ancRefsMaster];
01016         const QValueVector<int>& src1 = down ? revLookup(sha1)->descRefs : revLookup(sha1)->ancRefs;
01017         const QValueVector<int>& src2 = down ? revLookup(sha2)->descRefs : revLookup(sha2)->ancRefs;
01018         QValueVector<int> dst(src1);
01019 
01020         for (uint s2 = 0; s2 < src2.count(); s2++) {
01021 
01022                 bool add = false;
01023                 for (uint s1 = 0; s1 < src1.count(); s1++) {
01024 
01025                         if (src2[s2] == src1[s1]) {
01026                                 add = false;
01027                                 break;
01028                         }
01029                         QPair<uint, uint> key = qMakePair((uint)src2[s2], (uint)src1[s1]);
01030 
01031                         if (!dm.contains(key)) { // could be empty if all tags are independent
01032                                 add = true; // could be an independent path
01033                                 continue;
01034                         }
01035                         add = (down && dm[key]) || (!down && !dm[key]);
01036                         if (add)
01037                                 dst[s1] = -1; // mark for removing
01038                         else
01039                                 break;
01040                 }
01041                 if (add)
01042                         dst.append(src2[s2]);
01043         }
01044         QValueVector<int>& nearRefs = (down) ? p->descRefs : p->ancRefs;
01045         int& nearRefsMaster = (down) ? p->descRefsMaster : p->ancRefsMaster;
01046 
01047         nearRefs.clear();
01048         for (uint s2 = 0; s2 < dst.count(); s2++)
01049                 if (dst[s2] != -1)
01050                         nearRefs.append(dst[s2]);
01051 
01052         nearRefsMaster = p->orderIdx;
01053 }
01054 
01055 void Git::indexTree() {
01056 
01057         const StrVect& ro = revData.revOrder;
01058         if (ro.count() == 0)
01059                 return;
01060 
01061         // we keep the pairs(x, y). Value is true if x is
01062         // ancestor of y or false if y is ancestor of x
01063         QMap<QPair<uint, uint>, bool> descMap;
01064         QMap<uint, QValueVector<int> > descVect;
01065 
01066         // walk down the tree from latest to oldest,
01067         // compute children and nearest descendants
01068         for (uint i = 0, cnt = ro.count(); i < cnt; i++) {
01069 
01070                 uint type = checkRef(ro[i]);
01071                 bool isB = (type & (BRANCH | RMT_BRANCH));
01072                 bool isT = (type & TAG);
01073 
01074                 const Rev* r = revLookup(ro[i]);
01075 
01076                 if (isB) {
01077                         Rev* rr = const_cast<Rev*>(r);
01078                         if (r->descBrnMaster != -1) {
01079                                 SCRef sha = ro[r->descBrnMaster];
01080                                 rr->descBranches = revLookup(sha)->descBranches;
01081                         }
01082                         rr->descBranches.append(i);
01083                 }
01084                 if (isT) {
01085                         updateDescMap(r, i, descMap, descVect);
01086                         Rev* rr = const_cast<Rev*>(r);
01087                         rr->descRefs.clear();
01088                         rr->descRefs.append(i);
01089                 }
01090                 for (uint y = 0; y < r->parentsCount(); y++) {
01091 
01092                         Rev* p = const_cast<Rev*>(revLookup(r->parent(y)));
01093                         if (p) {
01094                                 p->childs.append(i);
01095 
01096                                 if (p->descBrnMaster == -1)
01097                                         p->descBrnMaster = isB ? r->orderIdx : r->descBrnMaster;
01098                                 else
01099                                         mergeBranches(p, r);
01100 
01101                                 if (p->descRefsMaster == -1)
01102                                         p->descRefsMaster = isT ? r->orderIdx : r->descRefsMaster;
01103                                 else
01104                                         mergeNearTags(optGoDown, p, r, descMap);
01105                         }
01106                 }
01107         }
01108         // walk backward through the tree and compute nearest tagged ancestors
01109         for (int i = ro.count() - 1; i >= 0; i--) {
01110 
01111                 const Rev* r = revLookup(ro[i]);
01112                 bool isTag = checkRef(ro[i], TAG);
01113 
01114                 if (isTag) {
01115                         Rev* rr = const_cast<Rev*>(r);
01116                         rr->ancRefs.clear();
01117                         rr->ancRefs.append(i);
01118                 }
01119                 for (uint y = 0; y < r->childs.count(); y++) {
01120 
01121                         Rev* c = const_cast<Rev*>(revLookup(ro[r->childs[y]]));
01122                         if (c) {
01123                                 if (c->ancRefsMaster == -1)
01124                                         c->ancRefsMaster = isTag ? r->orderIdx:r->ancRefsMaster;
01125                                 else
01126                                         mergeNearTags(!optGoDown, c, r, descMap);
01127                         }
01128                 }
01129         }
01130 }
01131 
01132 // ********************************* Rev **************************
01133 
01134 const QString Rev::mid(int start, int len) const {
01135 
01136         // warning no sanity check is done on arguments
01137         return QString::fromAscii(&(ba[start]), len);
01138 }
01139 
01140 const QString Rev::parent(int idx) const {
01141 
01142         return mid(start + startOfs + 41 + 41 * idx, 40);
01143 }
01144 
01145 const QStringList Rev::parents() const {
01146 
01147         if (parentsCnt == 0)
01148                 return QStringList();
01149 
01150         int ofs = start + startOfs + 41;
01151         return QStringList::split(' ', mid(ofs, 41 * parentsCnt - 1));
01152 }
01153 
01154 int Rev::indexData() { // fast path here, less then 4% of load time
01155 /*
01156         When git-rev-list is called with --header option, after the first
01157         line with the commit sha, the following information is produced
01158 
01159         - one line with "tree"
01160         - an arbitrary amount of "parent" lines
01161         - one line with "author"
01162         - one line with "committer"
01163         - zero or more non blank lines with other info, as the encoding
01164         - one blank line
01165         - zero or one line with log title
01166         - zero or more lines with log message
01167         - a terminating '\0'
01168 */
01169         int last = ba.size() - 1;
01170 
01171         // take in account --boundary and --left-right options
01172         startOfs = uint(ba.at(start) == '-' || ba.at(start) == '<' || ba.at(start) == '>');
01173         boundary = startOfs && ba.at(start) == '-';
01174 
01175         parentsCnt = 0;
01176         int idx = start + startOfs + 40;
01177         while (idx < last && ba[idx] == ' ') {
01178                 idx += 41;
01179                 parentsCnt++;
01180         }
01181         idx += 47; // idx points to first line '\n', so skip tree line
01182         while (idx < last && ba[idx] == 'p') //skip parents
01183                 idx += 48;
01184 
01185         idx += 23;
01186         if (idx > last)
01187                 return -1;
01188 
01189         int lineEnd = ba.find('\n', idx); // author line end
01190         if (lineEnd == -1)
01191                 return -1;
01192 
01193         lineEnd += 23;
01194         if (lineEnd > last)
01195                 return -1;
01196 
01197         autStart = idx - 16;
01198         autLen = lineEnd - idx - 24;
01199         autDateStart = lineEnd - 39;
01200         autDateLen = 10;
01201 
01202         idx = ba.find('\n', lineEnd); // committer line end
01203         if (idx == -1)
01204                 return -1;
01205 
01206         // shortlog could be not '\n' terminated so use committer
01207         // end of line as a safe start point to find chunk end
01208         int end = ba.find('\0', idx); // this is the slowest find
01209         if (end == -1)
01210                 return -1;
01211 
01212         // ok, from here we are sure we have a complete chunk
01213         while (++idx < end && ba[idx] != '\n') // check for the first blank line
01214                 idx = ba.find('\n', idx);
01215 
01216         sLogStart = idx + 5;
01217         if (end < sLogStart) { // no shortlog and no longLog
01218 
01219                 sLogStart = sLogLen = 0;
01220                 lLogStart = lLogLen = 0;
01221                 return ++end;
01222         }
01223         lLogStart = ba.find('\n', sLogStart);
01224         if (lLogStart != -1 && lLogStart < end) {
01225 
01226                 sLogLen = lLogStart++ - sLogStart;
01227                 lLogLen = end - lLogStart;
01228 
01229         } else { // no longLog and no new line at the end of shortlog
01230                 sLogLen = end - sLogStart;
01231                 lLogStart = lLogLen = 0;
01232         }
01233         return ++end;
01234 }

Generated on Fri Dec 7 21:57:37 2007 for QGit by  doxygen 1.5.3