Logo Search packages:      
Sourcecode: qgit version File versions

git_startup.cpp

/*
      Description: start-up repository opening and reading

      Author: Marco Costalba (C) 2005-2006

      Copyright: See COPYING file that comes with this distribution

*/
#include <unistd.h> // usleep()
#include <qapplication.h>
#include <qsettings.h>
#include <qeventloop.h>
#include <qregexp.h>
#include <qtextcodec.h>
#include "exceptionmanager.h"
#include "rangeselectimpl.h"
#include "cache.h"
#include "annotate.h"
#include "mainimpl.h"
#include "dataloader.h"
#include "git.h"

#define POST_MSG(x) QApplication::postEvent(parent(), new MessageEvent(x))

using namespace QGit;

const QString Git::getArgs(QString* range, bool askForRange, bool* quit) {

      static bool startup = true; // it's OK to be unique among qgit windows
      if (startup) {
            *range = "";
            for (int i = 1; i < qApp->argc(); i++) {
                  // in arguments with spaces double quotes
                  // are stripped by Qt, so re-add them
                  QString arg(qApp->argv()[i]);
                  if (arg.contains(' '))
                        arg.prepend('\"').append('\"');

                  range->append(arg + ' ');
            }
      }
      if (    askForRange
          &&  testFlag(RANGE_SELECT_F)
          && (!startup || range->isEmpty())) {

            RangeSelectImpl rs((QWidget*)parent(), range, getTagNames(), quit);
            rs.exec(); // modal execution
            if (*quit)
                  return "";
      }
      startup = false;
      QString runOutput;
      if (!run("git rev-parse --default HEAD " + *range, &runOutput))
            return "";

      return runOutput.replace('\n', " ");
}

const QString Git::getBaseDir(bool* changed, SCRef wd, bool* ok, QString* gd) {
// we could run from a subdirectory, so we need to get correct directories

      QString runOutput, tmp(workDir);
      workDir = wd;
      errorReportingEnabled = false;
      bool ret = run("git rev-parse --git-dir", &runOutput); // run under newWorkDir
      errorReportingEnabled = true;
      workDir = tmp;
      runOutput = runOutput.stripWhiteSpace();
      if (!ret || runOutput.isEmpty()) {
            *changed = true;
            if (ok)
                  *ok = false;
            return wd;
      }
      // 'git rev-parse --git-dir' output could be a relative
      // to working dir (as ex .git) or an absolute path
      QDir d(runOutput.startsWith("/") ? runOutput : wd + "/" + runOutput);
      *changed = (d.absPath() != gitDir);
      if (gd)
            *gd = d.absPath();
      if (ok)
            *ok = true;
      d.cdUp();
      return d.absPath();
}

bool Git::getRefs() {

      heads.clear(); headsSHA.clear();
      tags.clear(); tagsSHA.clear(); tagsObj.clear();
      refs.clear(); refsSHA.clear();
      refsStillToFindMasterCopy.clear();

      // check for a StGIT stack
      QDir d(gitDir);
      if (d.exists("patches")) { // early skip
            errorReportingEnabled = false;
            isStGIT = run("stg unapplied"); // slow command, check for stg bin
            errorReportingEnabled = true;
      } else
            isStGIT = false;

      // check for a merge and read current branch sha
      isMergeHead = d.exists("MERGE_HEAD");
      if (!run("git rev-parse HEAD", &currentBranchSHA))
            return false;

      currentBranchSHA = currentBranchSHA.stripWhiteSpace();

      // read refs, normally unsorted
      QString runOutput;
      if (!run("git peek-remote " + gitDir, &runOutput))
            return false;

      const QStringList rLst(QStringList::split('\n', runOutput));
      loopList(it, rLst) {

            SCRef refSha = (*it).left(40);
            SCRef refName = (*it).mid(41);

            if (refName.startsWith("refs/patches/")) {
                  // StGIT patches should not be added to refs,
                  // but an applied StGIT patch could be also an head or
                  // a tag in this case will be added in another loop cycle
                  continue;
            }
            refsStillToFindMasterCopy.insert(refSha, "");

            if (refName.startsWith("refs/tags/")) {

                  if (refName.endsWith("^{}")) { // tag dereference

                        // we assume that a tag dereference follows strictly
                        // the corresponding tag object in rLst. So the
                        // last added tag is a tag object, not a commit object

                        // store tag object. Will be used to fetching
                        // tag message (if any) when necessary.
                        tagsObj.insert(refSha, tagsSHA.first());

                        // we need to replace tagObj from tags sha list
                        // with the corresponding commit objects
                        refsStillToFindMasterCopy.remove(tagsSHA.first());
                        tagsSHA.first() = refSha;

                  } else {
                        tags.prepend(refName.mid(10));
                        tagsSHA.prepend(refSha);
                  }
                  continue;
            }
            if (refName.startsWith("refs/heads/")) {
                  heads.append(refName.mid(11));
                  headsSHA.append(refSha);
                  continue;
            }
            if (refName != "HEAD") {
                  refs.append(refName);
                  refsSHA.append(refSha);
            }
      }
      return !(tags.empty() && heads.empty());
}

void Git::dirWalker(SCRef dirPath, SList files, SList filesSHA, SCRef nameFilter) {
// search through dirPath recursively fetching any possible
// file whom first 40 chars of content are a possible sha

      QFileInfo* fi;
      QDir d(dirPath, "", QDir::Name, QDir::Dirs);
      QFileInfoListIterator it(*d.entryInfoList());
      ++it; ++it; // first two entries are . and ..
      while ((fi = it.current()) != 0) {
            dirWalker(fi->absFilePath(), files, filesSHA, nameFilter);
            ++it;
      }
      QString sha;
      QDir f(dirPath, nameFilter, QDir::Name, QDir::Files);
      it = *f.entryInfoList();
      while ((fi = it.current()) != 0) {

            readFromFile(fi->absFilePath(), sha);
            // we accept also files with a sha + other stuff
            sha = sha.stripWhiteSpace().append('\n').section('\n', 0, 0);
            if (sha.length() == 40) {
                  files.append(fi->absFilePath());
                  filesSHA.append(sha);
            }
            ++it;
      }
}

bool Git::lookUpPatchesSHA(SCRef patchName, SList files, SList filesSHA, QString& sha) {
// this function updates sha and also patchNames map

      const QStringList fl(files.grep("/" + patchName + "/top"));
      if (fl.count() != 1) {
            qDebug("ASSERT: found %i patches instead of 1 in %s",
                   fl.count(), patchName.latin1());
            return false;
      }
      int pos = files.findIndex(fl.first());
      sha = filesSHA[pos];
      patchNames.insert(sha, patchName);
      return true;
}

bool Git::getStGITPatches() {
// appliedSHA and unAppliedSHA must be already cleared because
// getStGITPatches() is conditionally executed

      // get current branch
      QString branch;
      if (!run("stg branch", &branch))
            return false;

      branch = branch.stripWhiteSpace();

      // get patch names and status of current branch
      QString runOutput;
      if (!run("stg series", &runOutput))
            return false;

      if (runOutput.isEmpty())
            return false; // false is used by caller as early exit

      QStringList ul, al;
      const QStringList pl(QStringList::split('\n', runOutput));
      loopList(it, pl) { // keep stg order for unapplied
            SCRef st = (*it).left(1);
            SCRef pn = (*it).mid(2);
            if (st == "+" || st == ">")
                  al.append(pn);
            else
                  ul.append(pn);
      }
      // get all sha's in "top" files under /<gitDir>/patches/<current branch>
      QDir d(gitDir + "/patches/" + branch);
      QStringList files, filesSHA;
      dirWalker(d.absPath(), files, filesSHA, "top");

      // now match names and SHA's for unapplied
      QString sha;
      it = ul.constBegin();
      for ( ; it != ul.constEnd(); ++it) { // keep stg order for unapplied
            if (!lookUpPatchesSHA(*it, files, filesSHA, sha))
                  return false;
            unAppliedSHA.append(sha);
      }
      // the same for applied, in this case the order is not important
      for (it = al.constBegin(); it != al.constEnd(); ++it) {
            if (!lookUpPatchesSHA(*it, files, filesSHA, sha))
                  return false;
            appliedSHA.append(sha);
      }
      return true;
}

const Rev* Git::fakeWorkDirRev(SCRef parent, SCRef log, SCRef longLog, int idx) {

      QString date(QString::number(QDateTime::currentDateTime().toTime_t()) + " +0200");
      QString data(ZERO_SHA + ' ' + parent + "\ntree ");
      data.append(ZERO_SHA);
      data.append("\nparent " + parent);
      data.append("\nauthor Working Dir " + date);
      data.append("\ncommitter Working Dir " + date);
      data.append("\n\n    " + log + '\n');
      data.append(longLog);

      Rev* c = new Rev(data, idx);

      c->isDiffCache = true;
      c->lanes.append(EMPTY);
      return c;
}

const QStringList Git::getOthersFiles() {
// add files exsistant in working directory but not in git archive

      QString runCmd("git ls-files --others ");
      QSettings settings;
      QString exFile(settings.readEntry(APP_KEY + EX_KEY, EX_DEF));
      QString exPerDir(settings.readEntry(APP_KEY + EX_PER_DIR_KEY, EX_PER_DIR_DEF));
      if (!exFile.isEmpty()) {
            QString path = (exFile.startsWith("/")) ? exFile : workDir + "/" + exFile;
            if (QFile::exists(path))
                  runCmd.append(" --exclude-from=" + quote(exFile));
      }
      if (!exPerDir.isEmpty())
            runCmd.append(" --exclude-per-directory=" + quote(exPerDir));
      QString runOutput;
      run(runCmd, &runOutput);
      return QStringList::split('\n', runOutput);
}

void Git::getDiffIndex() {

      QString status;
      if (!run("git status", &status)) // git status refreshes the index, run as first
            return;

      QString diffIndex;
      if (!run("git diff-index HEAD", &diffIndex))
            return;

      // now mockup a RevFile
      revsFiles.insert(ZERO_SHA, new RevFile());
      RevFile* rf = revsFiles[ZERO_SHA];
      parseDiffFormat(*rf, diffIndex);

      const QStringList otherFiles(getOthersFiles()); // get unknown files
      if (!otherFiles.isEmpty()) {
            loopList(it, otherFiles) {
                  appendFileName(*rf, *it);
                  rf->status.append(UNKNOWN);
                  rf->mergeParent.append(1);
            }
      }
      revOrder.append(ZERO_SHA);
      unknownFiles = (otherFiles.count() > 0);
      nothingToCommit = (rf->names.count() == otherFiles.count());
      const QString log(nothingToCommit ? "Nothing to commit" : "Working dir changes");

      // then mockup the corresponding Rev
      QString runOutput;
      if (!run("git rev-parse --default HEAD", &runOutput))
            return;

      const QString parent(runOutput.section('\n', 0, 0));
      revs.insert(ZERO_SHA, fakeWorkDirRev(parent, log, status, revs.count()));

      // check for files already updated in cache, we will
      // save this information in status third field
      RevFile cachedFiles;
      if (!run("git diff-index --cached HEAD", &runOutput))
            return;

      parseDiffFormat(cachedFiles, runOutput);

      for (uint i = 0; i < rf->status.count(); i++) {

            SCRef t = (findFileIndex(cachedFiles, filePath(*rf, i)) == -1) ? NOT_IN_INDEX :
                                                                             IN_INDEX;
            rf->status[i].append(",," + t); // third field of status string
      }
      // finally send it to GUI
      emit newRevsAdded(NULL, revOrder);
}

void Git::parseDiffFormatLine(RevFile& rf, SCRef line, int parNum) {

      if (line[1] == ':') { // it's a combined merge

            // TODO rename/copy is not suported for combined merges
            appendFileName(rf, line.section('\t', -1));
            rf.status.append(line.section('\t', -2, -1).right(1));
            rf.mergeParent.append(parNum);
      } else { // faster parsing in normal case

            if (line[98] == '\t') {
                  appendFileName(rf, line.mid(99));
                  rf.status.append(line.at(97));
                  rf.mergeParent.append(parNum);
            } else
                  // it's a rename or a copy, we are not in fast path now!
                  setStatus(rf, line.mid(97), parNum);
      }
}

void Git::setStatus(RevFile& rf, SCRef rowSt, int parNum) {

      const QStringList sl(QStringList::split('\t', rowSt));
      if (sl.count() == 3) {

            // we want store extra info with format "origFile --> destFile (Rxx%)"
            // but git give us something like "Rxx\t<origFile>\t<destFile>"
            SCRef origFile = sl[1];
            SCRef destFile = sl[2];
            QString info("," + origFile + " --> " + destFile + " (" + sl[0] + "%),,");

            // simulate new file
            appendFileName(rf, destFile);
            rf.mergeParent.append(parNum);
            rf.status.append(NEW + info);

            // simulate deleted orig file only in case of rename
            if (sl[0].at(0) == RENAMED) {
                  appendFileName(rf, origFile);
                  rf.mergeParent.append(parNum);
                  rf.status.append(DELETED + info);
            }
      } else
            dbp("ASSERT in setStatus, unexpected status string %1", rowSt);
}

void Git::parseDiffFormat(RevFile& rf, SCRef buf) {

      int parNum = 1, startPos = 0, endPos = buf.find('\n');
      while (endPos != -1) {
            SCRef line = buf.mid(startPos, endPos - startPos);
            if (line[0] == ':') // avoid sha's in merges output
                  parseDiffFormatLine(rf, line, parNum);
            else
                  parNum++;
            startPos = endPos + 1;
            endPos = buf.find('\n', endPos + 99);
      }
}

bool Git::startParseProc(SCRef initCmd, FileHistory* fh) {

      if (fh == NULL) {
            loadedTagNames.clear();
            loadedBranchNames.clear();
      }
      DataLoader* dl = new DataLoader(this, fh); // auto-deleted when done

      connect(this, SIGNAL(cancelLoading(const FileHistory*)),
              dl, SLOT(on_cancel(const FileHistory*)));

      connect(dl, SIGNAL(newDataReady(const FileHistory*)),
              this, SLOT(on_newDataReady(const FileHistory*)));

      connect(dl, SIGNAL(loaded(const FileHistory*, ulong, int,
              bool, const QString&, const QString&)), this,
              SLOT(on_loaded(const FileHistory*, ulong, int,
              bool, const QString&, const QString&)));

      QStringList args;
      MyProcess::parseArgs(initCmd, args);
      return dl->start(args, workDir);
}

bool Git::startRevList(SCRef args, FileHistory* fh) {

      refsStillToFind = refsStillToFindMasterCopy;
      QString initCmd("git rev-list --header --boundary --parents ");
      if (fh)
            // fetch history from all trees so any revision in
            // main view that changes the file is always found
            initCmd.append("--remove-empty --all -- ");
      else
            initCmd.append("--topo-order ");

      return startParseProc(initCmd + args, fh);
}

bool Git::startUnappliedList() {

      // WARNING: with this command git rev-list could send spurious
      // revs so we need some filter out logic during loading
      QString initCmd("git rev-list --header --parents ");
      initCmd.append(unAppliedSHA.join(" "));
      initCmd.append(QString::fromLatin1(" ^HEAD"));
      return startParseProc(initCmd, NULL);
}

bool Git::stop(bool saveCache) {
// normally called when changing directory or closing

      EM_RAISE(exGitStopped);

      // stop all data sending from process and asks them
      // to terminate. Note that process could still keep
      // running for a while although silently
      emit cancelAllProcesses(); // non blocking

      if (cacheNeedsUpdate && saveCache) {
            cacheNeedsUpdate = false;
            if (!filesLoadingCurSha.isEmpty()) // we are in the middle of a loading
                  revsFiles.remove(filesLoadingCurSha); // remove partial data

            if (!revsFiles.isEmpty()) {
                  POST_MSG("Saving cache. Please wait...");
                  EM_PROCESS_EVENTS_NO_INPUT; // to paint the message
                  if (!cache->save(gitDir, revsFiles, dirNamesVec, fileNamesVec))
                        dbs("ERROR unable to update cache");
            }
      }
      return allProcessDeleted();
}

void Git::clearRevs() {

      revs.clear();
      revOrder.clear();
      firstFreeLane = 0;
      unAppliedSHA.clear();
      appliedSHA.clear();
      patchNames.clear();
      firstNonStGitPatch = "";
      nothingToCommit = true;
      unknownFiles = false;
      lns.clear();
}

void Git::populateFileDict() {

      for (uint i = 0; i < dirNamesVec.count(); ++i)
            dirNames.insert(dirNamesVec[i], i);

      for (uint i = 0; i < fileNamesVec.count(); ++i)
            fileNames.insert(fileNamesVec[i], i);
}

bool Git::init(SCRef wd, bool askForRange, QStringList* filterList, bool* quit) {
// normally called when changing git directory. Must be called after stop()

      *quit = false;
      bool filteredLoading = (filterList != NULL);
      clearRevs();
      try {
            setThrowOnStop(true);
            bool archiveChanged;
            workDir = getBaseDir(&archiveChanged, wd, &isGIT, &gitDir);
            if (!isGIT) {
                  setThrowOnStop(false);
                  return false;
            }
            if (archiveChanged) {
                  revsFiles.clear();
                  fileNames.clear();
                  dirNames.clear();
                  fileNamesVec.clear();
                  dirNamesVec.clear();
                  if (!cache->load(gitDir, revsFiles, dirNamesVec, fileNamesVec))
                        dbs("ERROR: unable to load cache file");
                  else
                        populateFileDict();
            }
            QString msg1("Path is '" + workDir + "'    Loading ");
            QString args;
            if (!filteredLoading) {
                  POST_MSG(msg1 + "refs...");
                  if (!getRefs())
                        dbs("WARNING: no tags or heads found");

                  POST_MSG("");
                  args = getArgs(&curRange, askForRange, quit); // must be called with refs loaded
                  if (*quit) {
                        setThrowOnStop(false);
                        return false;
                  }
                  // update text codec according to repo settings
                  bool dummy;
                  QTextCodec::setCodecForCStrings(getTextCodec(&dummy));
            } else {
                  args.append("--all -- ");
                  args.append(filterList->join(" "));
            }
            patchesStillToFind = 0;
            if (isStGIT && !filteredLoading) { // updated by getRefs()
                  POST_MSG(msg1 + "StGIT unapplied patches...");
                  if (getStGITPatches()) {
                        patchesStillToFind = appliedSHA.count();
                        loadingUnAppliedPatches = true;
                        if (startUnappliedList()) {
                              while (loadingUnAppliedPatches) {
                                    // WARNING we are in setRepository()
                                    usleep(20000);
                                    EM_PROCESS_EVENTS;
                              }
                              lns.clear(); // again to reset lanes
                        } else
                              dbs("ERROR: unable to load unapplied list");
                  }
            }
            if (testFlag(DIFF_INDEX_F) && !filteredLoading) {
                  POST_MSG(msg1 + "working directory changed files...");
                  getDiffIndex(); // runs syncronous, we are in setRepository()
            }
            POST_MSG(msg1 + "revisions...");
            if (!startRevList(args))
                  dbs("ERROR: unable to start rev list loading");

            setThrowOnStop(false);
            return true;

      } catch(int i) {

            setThrowOnStop(false);

            if (isThrowOnStopRaised(i, "initializing")) {
                  EM_THROW_PENDING;
                  return false;
            }
            const QString info("Exception \'" + EM_DESC(i) + "\' "
                               "not handled in init...re-throw");
            dbs(info);
            throw;
      }
}

void Git::on_newDataReady(const FileHistory* fh) {

      emit newRevsAdded(fh, fh ? fh->revOrder : revOrder);
}

void Git::on_loaded(const FileHistory* fh, ulong byteSize, int loadTime,
                    bool normalExit, SCRef cmd, SCRef errorDesc) {

      if (!errorDesc.isEmpty()) {
            MainExecErrorEvent* e = new MainExecErrorEvent(cmd, errorDesc);
            QApplication::postEvent(parent(), e);
      }
      if (normalExit) { // do not send anything if killed

            on_newDataReady(fh);

            if (!loadingUnAppliedPatches) {

                  uint kb = byteSize / 1024;
                  QString tmp(QString("Loaded %1 revisions (%2 KB), time elapsed: %3 ms")
                                      .arg(revs.count()).arg(kb).arg(loadTime));

                  emit loadCompleted(fh, tmp);

                  if (fh == NULL)
                        // check for revs with no loaded files out of fast path
                        QTimer::singleShot(10, this, SLOT(loadFileNames()));
            }
      }
      if (loadingUnAppliedPatches)
            loadingUnAppliedPatches = false;
}

void Git::loadFileNames() {
// warning this function is not re-entrant

      QString diffTreeBuf;
      loop(StrVect, it, revOrder) {
            if (!revsFiles.find(*it)) {
                  const Rev* c = revLookup(*it);
                  if (c->parentsCount() == 1) // skip initials and merges
                        diffTreeBuf.append(*it).append('\n');
            }
      }
      if (!diffTreeBuf.isEmpty()) {
            filesLoadingPending = filesLoadingCurSha = "";
            const QString runCmd("git diff-tree -r -C --stdin");
            runAsync(runCmd, this, diffTreeBuf);
      }
      indexTree();
}

void Git::addChunk(FileHistory* fh, SCRef buffer) {

      RevMap& r = (fh ? fh->revs : revs);

      // take in account boundaries
      SCRef sha = (buffer[0] == '-') ? buffer.mid(1, 40) : buffer.left(40);

      if (loadingUnAppliedPatches) // filter out possible spurious revs
            if (!unAppliedSHA.contains(sha))
                  return;

      // remove StGIT spurious revs filter
      if (!firstNonStGitPatch.isEmpty() && firstNonStGitPatch == sha)
            firstNonStGitPatch = "";

      // StGIT called with --all option creates spurious revs so filter
      // out unknown revs until no more StGIT patches are waited and
      // firstNonStGitPatch is reached
      if (!(firstNonStGitPatch.isEmpty() && patchesStillToFind == 0) &&
            !loadingUnAppliedPatches) {
            if (!appliedSHA.contains(sha))
                  return;
      }
      if (r.find(sha)) {
            // StGIT unapplied patches could be sent again by
            // git rev-list as example if called with --all option.
            if (r[sha]->isUnApplied)
                  return;
            dbp("ASSERT: addChunk sha <%1> already received", sha);
      }

      r.insert(sha, new Rev(buffer, r.count())); // only here we create a new rev

      Rev* c = const_cast<Rev*>(revLookup(sha, fh));
      updateRefs(*c, fh);

      // updateLanes() is called too late, after loadingUnAppliedPatches
      // has been reset so update the lanes now.
      if (loadingUnAppliedPatches)
            c->lanes.append(UNAPPLIED);

      if (fh) {
            if (r.count() == 1)
                  copyDiffIndex(fh);
            fh->revOrder.append(sha); // used to append commits in proper order
      } else
            revOrder.append(sha);
}

void Git::copyDiffIndex(FileHistory* fh) {
// try to add ZERO_SHA to history. Must be called with only one rev
// in histRevs that will be used as the parent.

      if (!fh->revOrder.isEmpty() || (fh->revs.count() != 1)) {
            dbs("ASSERT in copyDiffIndex: called with wrong context");
            return;
      }
      const Rev* r = revLookup(ZERO_SHA);
      if (r == NULL)
            return;

      const RevFile* files = getFiles(ZERO_SHA);
      if (!files || findFileIndex(*files, fh->fileName) == -1)
            return;

      // insert a custom ZERO_SHA rev with proper parent
      QDictIterator<Rev> it(fh->revs); // only one item
      QString parent(it.currentKey());
      fh->revs.insert(ZERO_SHA, fakeWorkDirRev(parent, "Working dir changes",
                                               "dummy", fh->revs.count()));
      fh->revOrder.append(ZERO_SHA);
}

void Git::setLane(SCRef sha, FileHistory* fh) {

      Lanes& l = (fh ? fh->lns : lns);
      uint i = (fh ? fh->firstFreeLane : firstFreeLane);
      const QValueVector<QString>& shaVec(fh ? fh->revOrder : revOrder);
      for ( ; i < shaVec.count(); ++i) {
            Rev* r = const_cast<Rev*>(revLookup(shaVec[i], fh));
            if (r->lanes.count() == 0)
                  updateLanes(*r, l);

            if (shaVec[i] == sha)
                  break;
      }
      if (fh)
            fh->firstFreeLane = ++i;
      else
            firstFreeLane = ++i;
}

void Git::updateLanes(Rev& c, Lanes& lns) {

      SCRef sha(c.sha());
      if (lns.isEmpty())
            lns.init(sha);

      bool isDiscontinuity;
      bool isFork = lns.isFork(sha, isDiscontinuity);
      bool isMerge = (c.parentsCount() > 1);
      bool isInitial = (c.parentsCount() == 0);
      bool isApplied = (c.isApplied);

      if (isDiscontinuity)
            lns.changeActiveLane(sha); // uses previous isBoundary state

      lns.setBoundary(c.isBoundary); // update must be here

      if (isFork)
            lns.setFork(sha);
      if (isMerge)
            lns.setMerge(c.parents());
      if (isApplied)
            lns.setApplied();
      if (isInitial)
            lns.setInitial();

      lns.getLanes(c.lanes); // here lanes are snapshotted

      SCRef nextSha = (isInitial) ? "" : c.parent(0);
      lns.nextParent(nextSha);

      if (isApplied)
            lns.afterApplied();
      if (isMerge)
            lns.afterMerge();
      if (isFork)
            lns.afterFork();
      if (lns.isBranch())
            lns.afterBranch();

//    QString tmp = "", tmp2;
//    for (uint i = 0; i < c.lanes.count(); i++) {
//          tmp2.setNum(c.lanes[i]);
//          tmp.append(tmp2 + "-");
//    }
//    qDebug("%s %s",tmp.latin1(), c.sha.latin1());
}

void Git::on_procDataReady(const QString& fileChunk) {

      if (filesLoadingPending.isEmpty())
            filesLoadingPending = fileChunk;
      else
            filesLoadingPending.append(fileChunk); // add to previous half lines

      RevFile* rf = revsFiles[filesLoadingCurSha];
      int nextEOL = filesLoadingPending.find('\n');
      int lastEOL = -1;
      while (nextEOL != -1) {

            SCRef line(filesLoadingPending.mid(lastEOL + 1, nextEOL - lastEOL - 1));
            if (line.constref(0) != ':') {
                  SCRef sha = line.left(40);
                  if (rf == NULL || sha != filesLoadingCurSha) { // new commit
                        revsFiles.insert(sha, new RevFile());
                        rf = revsFiles[sha];
                        filesLoadingCurSha = sha;
                        cacheNeedsUpdate = true;
                  } else
                        dbp("ASSERT: repeated sha %1 in file names loading", sha);
            } else // line.constref(0) == ':'
                  parseDiffFormatLine(*rf, line, 1);

            lastEOL = nextEOL;
            nextEOL = filesLoadingPending.find('\n', lastEOL + 1);
      }
      if (lastEOL != -1)
            filesLoadingPending.remove(0, lastEOL + 1);
}

void Git::appendFileName(RevFile& rf, SCRef name) {

      int idx = name.findRev('/') + 1;
      SCRef dr = name.left(idx);
      SCRef nm = name.mid(idx);

      StrSet::iterator it(dirNames.find(dr));
      if (it == dirNames.end()) {
            dirNamesVec.append(dr);
            it = dirNames.insert(dirNamesVec.last(), dirNamesVec.count() - 1);
      }
      rf.dirs.append(it.data());

      it = fileNames.find(nm);
      if (it == fileNames.end()) {
            fileNamesVec.append(nm);
            it = fileNames.insert(fileNamesVec.last(), fileNamesVec.count() - 1);
      }
      rf.names.append(it.data());
}

void Git::updateRefs(Rev& c, FileHistory* fh) {

      SCRef sha(c.sha());

      if (loadingUnAppliedPatches) {
            c.isUnApplied = true;
            return;
      }
      if (patchesStillToFind > 0 && appliedSHA.findIndex(sha) != -1) {
            c.isApplied = true;
            patchesStillToFind--;
            if (patchesStillToFind == 0)
                  // any rev will be discarded until firstNonStGitPatch arrives
                  firstNonStGitPatch = c.parent(0);
      }
      if (!refsStillToFind.contains(sha)) // early skip
            return;

      refsStillToFind.remove(sha);

      if (tagsSHA.findIndex(sha) != -1) {

            // one rev could be linked to many tags
            c.isTag = true;
            int idx = tagsSHA.findIndex(sha);
            QStringList::const_iterator itSha(tagsSHA.at(idx));
            QStringList::const_iterator itNames(tags.at(idx));
            while (itSha != tagsSHA.constEnd()) {
                  if (*itSha == sha) {
                        c.tag.append(*itNames + "\n");
                        if (fh == NULL)
                              loadedTagNames.append(*itNames);
                  }
                  ++itSha;
                  ++itNames;
            }
            c.tag = c.tag.stripWhiteSpace();
      }
      if (headsSHA.findIndex(sha) != -1) {

            // one rev could be linked to many branches
            c.isBranch = true;
            c.isCurrentBranch = (currentBranchSHA == sha);
            int idx = headsSHA.findIndex(sha);
            QStringList::const_iterator itSha(headsSHA.at(idx));
            QStringList::const_iterator itNames(heads.at(idx));
            while (itSha != headsSHA.constEnd()) {
                  if (*itSha == sha) {
                        c.branch.append(*itNames + "\n");
                        if (fh == NULL)
                              loadedBranchNames.append(*itNames);
                  }
                  ++itSha;
                  ++itNames;
            }
            c.branch = c.branch.stripWhiteSpace();
      }
      if (refsSHA.findIndex(sha) != -1) {
            c.isRef = true;
            c.ref = refs[refsSHA.findIndex(sha)];
            c.isCurrentBranch = (currentBranchSHA == sha);
      }
}

void Git::updateDescMap(const Rev* r,uint idx, QMap<QPair<uint, uint>, bool>& dm,
                        QMap<uint, QValueVector<int> >& dv) {

      QValueVector<int> descVec(1, idx);

      if (r->descRefsMaster != -1) {

            const QValueVector<int>& nr = revLookup(revOrder[r->descRefsMaster])->descRefs;

            for (uint i = 0; i < nr.count(); i++) {

                  if (!dv.contains(nr[i])) {
                        dbp("ASSERT descendant for %1 not found", r->sha());
                        return;
                  }
                  const QValueVector<int>& dvv = dv[nr[i]];
                  for (uint y = 0; y < dvv.count(); y++) {

                        QPair<uint, uint> key = qMakePair(idx, (uint)dvv[y]);
                        QPair<uint, uint> keyN = qMakePair((uint)dvv[y], idx);
                        dm.insert(key, true);
                        dm.insert(keyN, false);
                        descVec.append(dvv[y]);
                  }
            }
      }
      dv.insert(idx, descVec);
}

void Git::mergeBranches(Rev* p, const Rev* r) {

      int r_descBrnMaster = r->isBranch ? r->orderIdx : r->descBrnMaster;

      if (p->descBrnMaster == r_descBrnMaster || r_descBrnMaster == -1)
            return;

      // we want all the descendant branches, so just avoid duplicates
      const QValueVector<int>& src1 = revLookup(revOrder[p->descBrnMaster])->descBranches;
      const QValueVector<int>& src2 = revLookup(revOrder[r_descBrnMaster])->descBranches;
      QValueVector<int> dst(src1);
      for (uint i = 0; i < src2.count(); i++)
            if (qFind(src1.constBegin(), src1.constEnd(), src2[i]) == src1.constEnd())
                  dst.append(src2[i]);

      p->descBranches = dst;
      p->descBrnMaster = p->orderIdx;
}

void Git::mergeNearTags(bool down, Rev* p, const Rev* r, const QMap<QPair<uint, uint>, bool>& dm) {

      int r_descRefsMaster = r->isTag ? r->orderIdx : r->descRefsMaster;
      int r_ancRefsMaster = r->isTag ? r->orderIdx : r->ancRefsMaster;

      if (down && (p->descRefsMaster == r_descRefsMaster || r_descRefsMaster == -1))
            return;

      if (!down && (p->ancRefsMaster == r_ancRefsMaster || r_ancRefsMaster == -1))
            return;

      // we want the nearest tag only, so remove any tag
      // that is ancestor of any other tag in p U r
      SCRef sha1 = down ? revOrder[p->descRefsMaster] : revOrder[p->ancRefsMaster];
      SCRef sha2 = down ? revOrder[r_descRefsMaster] : revOrder[r_ancRefsMaster];
      const QValueVector<int>& src1 = down ? revLookup(sha1)->descRefs : revLookup(sha1)->ancRefs;
      const QValueVector<int>& src2 = down ? revLookup(sha2)->descRefs : revLookup(sha2)->ancRefs;
      QValueVector<int> dst(src1);

      for (uint s2 = 0; s2 < src2.count(); s2++) {

            bool add = false;
            for (uint s1 = 0; s1 < src1.count(); s1++) {

                  if (src2[s2] == src1[s1]) {
                        add = false;
                        break;
                  }
                  QPair<uint, uint> key = qMakePair((uint)src2[s2], (uint)src1[s1]);

                  if (!dm.contains(key)) { // could be empty if all tags are indipendent
                        add = true; // could be an indipendent path
                        continue;
                  }
                  add = (down && dm[key]) || (!down && !dm[key]);
                  if (add)
                        dst[s1] = -1; // mark for removing
                  else
                        break;
            }
            if (add)
                  dst.append(src2[s2]);
      }
      QValueVector<int>& nearRefs = (down) ? p->descRefs : p->ancRefs;
      int& nearRefsMaster = (down) ? p->descRefsMaster : p->ancRefsMaster;

      nearRefs.clear();
      for (uint s2 = 0; s2 < dst.count(); s2++)
            if (dst[s2] != -1)
                  nearRefs.append(dst[s2]);

      nearRefsMaster = p->orderIdx;
}

void Git::indexTree() {

      if (revOrder.count() == 0)
            return;

      // we keep the pairs(x, y). Value is true if x is
      // ancestor of y or false if y is ancestor of x
      QMap<QPair<uint, uint>, bool> descMap;
      QMap<uint, QValueVector<int> > descVect;

      // walk down the tree from latest to oldest,
      // compute chidlren and nearest descendants
      for (uint i = 0; i < revOrder.count(); i++) {

            const Rev* r = revLookup(revOrder[i]);

            if (r->isBranch) {
                  Rev* rr = const_cast<Rev*>(r);
                  if (r->descBrnMaster != -1) {
                        SCRef sha = revOrder[r->descBrnMaster];
                        rr->descBranches = revLookup(sha)->descBranches;
                  }
                  rr->descBranches.append(i);
            }
            if (r->isTag) {
                  updateDescMap(r, i, descMap, descVect);
                  Rev* rr = const_cast<Rev*>(r);
                  rr->descRefs.clear();
                  rr->descRefs.append(i);
            }
            for (uint y = 0; y < r->parentsCount(); y++) {

                  Rev* p = const_cast<Rev*>(revLookup(r->parent(y)));
                  if (p) {
                        p->childs.append(i);

                        if (p->descBrnMaster == -1)
                              p->descBrnMaster = r->isBranch ? r->orderIdx :
                                                               r->descBrnMaster;
                        else
                              mergeBranches(p, r);

                        if (p->descRefsMaster == -1)
                              p->descRefsMaster = r->isTag ? r->orderIdx :
                                                             r->descRefsMaster;
                        else
                              mergeNearTags(optGoDown, p, r, descMap);
                  }
            }
      }
      // walk backward through the tree and compute nearest tagged ancestors
      for (int i = revOrder.count() - 1; i >= 0; i--) {

            const Rev* r = revLookup(revOrder[i]);

            if (r->isTag) {
                  Rev* rr = const_cast<Rev*>(r);
                  rr->ancRefs.clear();
                  rr->ancRefs.append(i);
            }
            for (uint y = 0; y < r->childs.count(); y++) {

                  Rev* c = const_cast<Rev*>(revLookup(revOrder[r->childs[y]]));
                  if (c) {
                        if (c->ancRefsMaster == -1)
                              c->ancRefsMaster = r->isTag ? r->orderIdx:r->ancRefsMaster;
                        else
                              mergeNearTags(!optGoDown, c, r, descMap);
                  }
            }
      }
}

// ********************************* Rev **************************

const QString Rev::parent(int idx) const {

      int start = 41 + 41 * idx;
      if (isBoundary)
            start++;

      return d.mid(start, 40);
}

const QStringList Rev::parents() const {

      if (parentsCnt == 0)
            return QStringList();

      int start = (isBoundary) ? 42 : 41;
      return QStringList::split(" ", d.mid(start, 41 * parentsCnt - 1));
}

void Rev::indexData() const {

      int idx = 40 + parentsCnt * 41 + (int)isBoundary;
      idx += 47; // skip tree line
      while (d[idx] == 'p') //skip parents
            idx += 48;

      int lineEnd = d.find('\n', idx + 23); // author line
      autStart = idx + 7;
      autLen = lineEnd - idx - 24;
      autDateStart = lineEnd - 16;
      autDateLen = 10;
      idx = d.find('\n', lineEnd + 23); // skip committer
      lineEnd = d.find('\n', idx + 10);
      sLogStart = idx + 6;
      if (lineEnd != -1) {
            sLogLen = lineEnd - (idx + 6);
            lLogLen = d.length() - lineEnd - 1;

      } else { // no longLog and no new line at the end of short log
            sLogLen = d.length() - idx;
            lLogLen = 0;
      }
      indexed = true;
}

Generated by  Doxygen 1.6.0   Back to index