git.cpp

Go to the documentation of this file.
00001 /*
00002         Description: interface to git programs
00003 
00004         Author: Marco Costalba (C) 2005-2006
00005 
00006         Copyright: See COPYING file that comes with this distribution
00007 
00008 */
00009 #include <stdlib.h>    // used by getenv()
00010 #include <qapplication.h>
00011 #include <qdatetime.h>
00012 #include <qtextcodec.h>
00013 #include <qregexp.h>
00014 #include <qsettings.h>
00015 #include <qfile.h>
00016 #include <qdir.h>
00017 #include <qeventloop.h>
00018 #include <qprocess.h>
00019 #include <qstylesheet.h>
00020 #include <qobjectlist.h>
00021 #include "lanes.h"
00022 #include "myprocess.h"
00023 #include "annotate.h"
00024 #include "cache.h"
00025 #include "git.h"
00026 
00027 using namespace QGit;
00028 
00029 FileHistory::FileHistory() {
00030 
00031         lns = new Lanes();
00032         revs.setAutoDelete(true);
00033         clear("");
00034         revs.resize(QGit::MAX_DICT_SIZE);
00035         rowData.setAutoDelete(true);
00036 }
00037 
00038 FileHistory::~FileHistory() {
00039 
00040         delete lns;
00041 }
00042 
00043 void FileHistory::clear(SCRef name) {
00044 
00045         revs.clear();
00046         revOrder.clear();
00047         firstFreeLane = 0;
00048         lns->clear();
00049         fileName = name;
00050         rowData.clear();
00051 }
00052 
00053 Git::Git(QWidget* p) : QObject(p) {
00054 
00055         EM_INIT(exGitStopped, "Stopping connection with git");
00056 
00057         fileCacheAccessed = cacheNeedsUpdate = isMergeHead = false;
00058         isStGIT = isGIT = loadingUnAppliedPatches = isTextHighlighterFound = false;
00059         errorReportingEnabled = true; // report errors if run() fails
00060         curDomain = NULL;
00061 
00062         revsFiles.resize(MAX_DICT_SIZE);
00063         revsFiles.setAutoDelete(true);
00064 }
00065 
00066 void Git::checkEnvironment() {
00067 
00068         QString version;
00069         if (run("git --version", &version)) {
00070 
00071                 version = version.section(' ', -1, -1).section('.', 0, 2);
00072                 if (version < GIT_VERSION) {
00073 
00074                         // simply send information, the 'not compatible version'
00075                         // policy should be implemented upstream
00076                         const QString cmd("Current git version is " + version +
00077                               " but is required " + GIT_VERSION + " or better");
00078 
00079                         const QString errorDesc("Your installed git is too old."
00080                               "\nPlease upgrade to avoid possible misbehaviours.");
00081 
00082                         MainExecErrorEvent* e = new MainExecErrorEvent(cmd, errorDesc);
00083                         QApplication::postEvent(parent(), e);
00084                 }
00085         }
00086         errorReportingEnabled = false;
00087         isTextHighlighterFound = run("source-highlight -V", &version);
00088         errorReportingEnabled = true;
00089         if (isTextHighlighterFound)
00090                 dbp("Found %1", version.section('\n', 0, 0));
00091 }
00092 
00093 void Git::userInfo(SList info) {
00094 /*
00095   git looks for commit user information in following order:
00096 
00097         - GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL environment variables
00098         - repository config file
00099         - global config file
00100         - your name, hostname and domain
00101 */
00102         QString user(getenv("GIT_AUTHOR_NAME"));
00103         QString email(getenv("GIT_AUTHOR_EMAIL"));
00104 
00105         info.clear();
00106         info << "Environment" << user << email;
00107 
00108         errorReportingEnabled = false; // 'git repo-config' could fail, see docs
00109 
00110         run("git repo-config user.name", &user);
00111         run("git repo-config user.email", &email);
00112         info << "Local config" << user << email;
00113 
00114         run("git repo-config --global user.name", &user);
00115         run("git repo-config --global user.email", &email);
00116         info << "Global config" << user << email;
00117 
00118         errorReportingEnabled = true;
00119 }
00120 
00121 bool Git::isBinaryFile(SCRef file) {
00122 
00123         static const char* binaryFileExtensions[] = {"bmp", "gif", "jpeg", "jpg",
00124                            "png", "svg", "tiff", "pcx", "xcf", "xpm",
00125                            "bz", "bz2", "rar", "tar", "z", "gz", "tgz", "zip", 0};
00126 
00127         const QString ext(file.section('.', -1).lower());
00128         int i = 0;
00129         while (binaryFileExtensions[i] != 0)
00130                 if (ext == binaryFileExtensions[i++])
00131                         return true;
00132         return false;
00133 }
00134 
00135 void Git::setThrowOnStop(bool b) {
00136 
00137         if (b)
00138                 EM_REGISTER(exGitStopped);
00139         else
00140                 EM_REMOVE(exGitStopped);
00141 }
00142 
00143 bool Git::isThrowOnStopRaised(int excpId, SCRef curContext) {
00144 
00145         return EM_MATCH(excpId, exGitStopped, curContext);
00146 }
00147 
00148 bool Git::allProcessDeleted() {
00149 
00150         QObjectList* l = this->queryList("QProcess");
00151         bool allProcessAreDeleted = (l->count() == 0);
00152         delete l;
00153         return allProcessAreDeleted;
00154 }
00155 
00156 void Git::setTextCodec(QTextCodec* tc) {
00157 
00158         QTextCodec::setCodecForCStrings(tc); // works also with tc == 0 (Latin1)
00159         QString mimeName(tc ? tc->mimeName() : "Latin1");
00160 
00161         // workaround Qt issue of mime name different from
00162         // standard http://www.iana.org/assignments/character-sets
00163         if (mimeName == "Big5-HKSCS")
00164                 mimeName = "Big5";
00165 
00166         run("git repo-config i18n.commitencoding " + mimeName);
00167 }
00168 
00169 QTextCodec* Git::getTextCodec(bool* isGitArchive) {
00170 
00171         *isGitArchive = isGIT;
00172         if (!isGIT) // can be called also when not in an archive
00173                 return NULL;
00174 
00175         QString runOutput;
00176         if (!run("git repo-config --get i18n.commitencoding", &runOutput))
00177                 return NULL;
00178 
00179         if (runOutput.isEmpty()) // git docs says default is utf-8
00180                 return QTextCodec::codecForName("utf8");
00181 
00182         return QTextCodec::codecForName(runOutput.stripWhiteSpace());
00183 }
00184 
00185 const QString Git::quote(SCRef nm) {
00186 
00187         return (QUOTE_CHAR + nm + QUOTE_CHAR);
00188 }
00189 
00190 const QString Git::quote(SCList sl) {
00191 
00192         QString q(sl.join(QUOTE_CHAR + ' ' + QUOTE_CHAR));
00193         q.prepend(QUOTE_CHAR).append(QUOTE_CHAR);
00194         return q;
00195 }
00196 
00197 const QString Git::getLocalDate(SCRef gitDate) {
00198 
00199         QDateTime d;
00200         d.setTime_t(gitDate.toULong());
00201         return d.toString(Qt::LocalDate);
00202 }
00203 
00204 uint Git::checkRef(SCRef sha, uint mask) const {
00205 
00206         QMap<QString, Reference>::const_iterator it(refsShaMap.find(sha));
00207         return (it != refsShaMap.constEnd() ? (*it).type & mask : 0);
00208 }
00209 
00210 const QStringList Git::getRefName(SCRef sha, RefType type, QString* curBranch) const {
00211 
00212         if (!checkRef(sha, type))
00213                 return QStringList();
00214 
00215         const Reference& rf = refsShaMap[sha];
00216 
00217         if (curBranch)
00218                 *curBranch = rf.currentBranch;
00219 
00220         if (type == TAG)
00221                 return rf.tags;
00222 
00223         else if (type == BRANCH)
00224                 return rf.branches;
00225 
00226         else if (type == RMT_BRANCH)
00227                 return rf.remoteBranches;
00228 
00229         else if (type == REF)
00230                 return rf.refs;
00231 
00232         else if (type == APPLIED || type == UN_APPLIED)
00233                 return QStringList(rf.stgitPatch);
00234 
00235         return QStringList();
00236 }
00237 
00238 const QStringList Git::getAllRefSha(uint mask) {
00239 
00240         QStringList shas;
00241         FOREACH (RefMap, it, refsShaMap)
00242                 if ((*it).type & mask)
00243                         shas.append(it.key());
00244         return shas;
00245 }
00246 
00247 const QString Git::getRefSha(SCRef refName, RefType type, bool askGit) {
00248 
00249         bool any = (type == ANY_REF);
00250 
00251         FOREACH (RefMap, it, refsShaMap) {
00252 
00253                 const Reference& rf = *it;
00254 
00255                 if ((any || type == TAG) && rf.tags.contains(refName))
00256                         return it.key();
00257 
00258                 else if ((any || type == BRANCH) && rf.branches.contains(refName))
00259                         return it.key();
00260 
00261                 else if ((any || type == RMT_BRANCH) && rf.remoteBranches.contains(refName))
00262                         return it.key();
00263 
00264                 else if ((any || type == REF) && rf.refs.contains(refName))
00265                         return it.key();
00266 
00267                 else if ((any || type == APPLIED || type == UN_APPLIED) && rf.stgitPatch == refName)
00268                         return it.key();
00269         }
00270         if (!askGit)
00271                 return "";
00272 
00273         // if a ref was not found perhaps is an abbreviated form
00274         QString runOutput;
00275         errorReportingEnabled = false;
00276         bool ok = run("git rev-parse " + refName, &runOutput);
00277         errorReportingEnabled = true;
00278         return (ok ? runOutput.stripWhiteSpace() : "");
00279 }
00280 
00281 void Git::appendNamesWithId(QStringList& names, SCRef sha, SCList data, bool onlyLoaded) {
00282 
00283         const Rev* r = revLookup(sha);
00284         if (onlyLoaded && !r)
00285                 return;
00286 
00287         if (onlyLoaded) { // prepare for later sorting
00288                 SCRef cap = QString("%1 ").arg(r->orderIdx, 6);
00289                 FOREACH_SL (it, data)
00290                         names.append(cap + *it);
00291         } else
00292                 names += data;
00293 }
00294 
00295 const QStringList Git::getAllRefNames(uint mask, bool onlyLoaded) {
00296 // returns reference names sorted by loading order if 'onlyLoaded' is set
00297 
00298         QStringList names;
00299         FOREACH (RefMap, it, refsShaMap) {
00300 
00301                 if (mask & TAG)
00302                         appendNamesWithId(names, it.key(), (*it).tags, onlyLoaded);
00303 
00304                 if (mask & BRANCH)
00305                         appendNamesWithId(names, it.key(), (*it).branches, onlyLoaded);
00306 
00307                 if (mask & RMT_BRANCH)
00308                         appendNamesWithId(names, it.key(), (*it).remoteBranches, onlyLoaded);
00309 
00310                 if (mask & REF)
00311                         appendNamesWithId(names, it.key(), (*it).refs, onlyLoaded);
00312 
00313                 if ((mask & (APPLIED | UN_APPLIED)) && !onlyLoaded)
00314                         names.append((*it).stgitPatch); // doesn't work with 'onlyLoaded'
00315         }
00316         if (onlyLoaded) {
00317                 names.sort();
00318                 QStringList::iterator itN(names.begin());
00319                 for ( ; itN != names.end(); ++itN) // strip 'idx'
00320                         (*itN) = (*itN).section(' ', -1, -1);
00321         }
00322         return names;
00323 }
00324 
00325 const QString Git::getRevInfo(SCRef sha) {
00326 
00327         uint type = checkRef(sha);
00328         if (type == 0)
00329                 return "";
00330 
00331         QString refsInfo;
00332         if (type & BRANCH) {
00333                 const QString cap(type & CUR_BRANCH ? "Head: " : "Branch: ");
00334                 refsInfo =  cap + getRefName(sha, BRANCH).join(" ");
00335         }
00336         if (type & RMT_BRANCH)
00337                 refsInfo.append("   Remote branch: " + getRefName(sha, RMT_BRANCH).join(" "));
00338 
00339         if (type & TAG)
00340                 refsInfo.append("   Tag: " + getRefName(sha, TAG).join(" "));
00341 
00342         if (type & REF)
00343                 refsInfo.append("   Ref: " + getRefName(sha, REF).join(" "));
00344 
00345         if (type & APPLIED)
00346                 refsInfo.append("   Patch: " + getRefName(sha, APPLIED).join(" "));
00347 
00348         if (type & UN_APPLIED)
00349                 refsInfo.append("   Patch: " + getRefName(sha, UN_APPLIED).join(" "));
00350 
00351         if (type & TAG) {
00352                 SCRef msg(getTagMsg(sha));
00353                 if (!msg.isEmpty())
00354                         refsInfo.append("  [" + msg + "]");
00355         }
00356         return refsInfo.stripWhiteSpace();
00357 }
00358 
00359 const QString Git::getTagMsg(SCRef sha) {
00360 
00361         if (!checkRef(sha, TAG)) {
00362                 dbs("ASSERT in Git::getTagMsg, tag not found");
00363                 return "";
00364         }
00365         Reference& rf = refsShaMap[sha];
00366 
00367         if (!rf.tagMsg.isEmpty())
00368                 return rf.tagMsg;
00369 
00370         QRegExp pgp("-----BEGIN PGP SIGNATURE*END PGP SIGNATURE-----", true, true);
00371 
00372         if (!rf.tagObj.isEmpty()) {
00373                 QString ro;
00374                 if (run("git cat-file tag " + rf.tagObj, &ro))
00375                         rf.tagMsg = ro.section("\n\n", 1).remove(pgp).stripWhiteSpace();
00376         }
00377         return rf.tagMsg;
00378 }
00379 
00380 bool Git::isPatchName(SCRef nm) {
00381 
00382         if (!getRefSha(nm, UN_APPLIED, false).isEmpty())
00383                 return true;
00384 
00385         return !getRefSha(nm, APPLIED, false).isEmpty();
00386 }
00387 
00388 void Git::addExtraFileInfo(QString* rowName, SCRef sha, SCRef diffToSha, bool allMergeFiles) {
00389 
00390         const RevFile* files = getFiles(sha, diffToSha, allMergeFiles);
00391         if (!files)
00392                 return;
00393 
00394         int idx = findFileIndex(*files, *rowName);
00395         if (idx == -1)
00396                 return;
00397 
00398         QString extSt(files->extendedStatus(idx));
00399         if (extSt.isEmpty())
00400                 return;
00401 
00402         *rowName = extSt;
00403 }
00404 
00405 void Git::removeExtraFileInfo(QString* rowName) {
00406 
00407         if (rowName->contains(" --> ")) // return destination file name
00408                 *rowName = rowName->section(" --> ", 1, 1).section(" (", 0, 0);
00409 }
00410 
00411 void Git::formatPatchFileHeader(QString* rowName, SCRef sha, SCRef diffToSha,
00412                                 bool combined, bool allMergeFiles) {
00413         if (combined) {
00414                 rowName->prepend("diff --combined ");
00415                 return; // TODO rename/copy still not supported in this case
00416         }
00417         // let's see if it's a rename/copy...
00418         addExtraFileInfo(rowName, sha, diffToSha, allMergeFiles);
00419 
00420         if (rowName->contains(" --> ")) { // ...it is!
00421 
00422                 SCRef destFile(rowName->section(" --> ", 1, 1).section(" (", 0, 0));
00423                 SCRef origFile(rowName->section(" --> ", 0, 0));
00424                 *rowName = "diff --git a/" + origFile + " b/" + destFile;
00425         } else
00426                 *rowName = "diff --git a/" + *rowName + " b/" + *rowName;
00427 }
00428 
00429 Annotate* Git::startAnnotate(FileHistory* fh, QObject* guiObj) { // non blocking
00430 
00431         Annotate* ann = new Annotate(this, guiObj);
00432         ann->start(fh); // non blocking call
00433         return ann; // caller will delete with Git::cancelAnnotate()
00434 }
00435 
00436 void Git::cancelAnnotate(Annotate* ann) {
00437 
00438         if (ann)
00439                 ann->deleteWhenDone();
00440 }
00441 
00442 void Git::annotateExited(Annotate* ann) {
00443 
00444         SCRef msg = QString("Annotated %1 files in %2 ms")
00445                     .arg(ann->count()).arg(ann->elapsed());
00446 
00447         emit annotateReady(ann, ann->file(), ann->isValid(), msg);
00448 }
00449 
00450 const FileAnnotation* Git::lookupAnnotation(Annotate* ann, SCRef fileName, SCRef sha) {
00451 
00452         return (ann ? ann->lookupAnnotation(sha, fileName) : NULL);
00453 }
00454 
00455 void Git::cancelDataLoading(const FileHistory* fh) {
00456 // normally called when closing file viewer
00457 
00458         emit cancelLoading(fh); // non blocking
00459 }
00460 
00461 const Rev* Git::revLookup(SCRef sha, const FileHistory* fh) const {
00462 
00463         const RevMap& r = (fh ? fh->revs : revData.revs);
00464         return r[sha];
00465 }
00466 
00467 bool Git::run(SCRef runCmd, QString* runOutput, QObject* receiver, SCRef buf, QStringList* env) {
00468 
00469         if (!runOutput)
00470                 return run(NULL, runCmd, receiver, buf, env);
00471 
00472         *runOutput = "";
00473         QByteArray ba;
00474         bool ret = run(&ba, runCmd, receiver, buf, env);
00475         if (!ba.isEmpty())
00476                 runOutput->append(ba);
00477 
00478         return ret;
00479 }
00480 
00481 bool Git::run(QByteArray* runOutput, SCRef runCmd, QObject* receiver, SCRef buf, QStringList* env) {
00482 
00483         MyProcess p(parent(), this, workDir, errorReportingEnabled);
00484         return p.runSync(runCmd, runOutput, receiver, buf, env);
00485 }
00486 
00487 MyProcess* Git::runAsync(SCRef runCmd, QObject* receiver, SCRef buf, QStringList* env) {
00488 
00489         MyProcess* p = new MyProcess(parent(), this, workDir, errorReportingEnabled);
00490         if (!p->runAsync(runCmd, receiver, buf, env)) {
00491                 delete p;
00492                 p = NULL;
00493         }
00494         return p; // auto-deleted when done
00495 }
00496 
00497 MyProcess* Git::runAsScript(SCRef runCmd, QObject* receiver, SCRef buf, QStringList* env) {
00498 
00499         const QString scriptFile(workDir + "/qgit_script.sh");
00500         if (!writeToFile(scriptFile, runCmd, true))
00501                 return NULL;
00502 
00503         MyProcess* p = runAsync(scriptFile, receiver, buf, env);
00504         if (p)
00505                 connect(p, SIGNAL(eof()), this, SLOT(on_runAsScript_eof()));
00506         return p;
00507 }
00508 
00509 void Git::on_runAsScript_eof() {
00510 
00511         QDir dir(workDir);
00512         dir.remove("qgit_script.sh");
00513 }
00514 
00515 void Git::cancelProcess(MyProcess* p) {
00516 
00517         if (p)
00518                 p->on_cancel(); // non blocking call
00519 }
00520 
00521 int Git::findFileIndex(const RevFile& rf, SCRef name) {
00522 
00523         if (name.isEmpty())
00524                 return -1;
00525 
00526         int idx = name.findRev('/') + 1;
00527         SCRef dr = name.left(idx);
00528         SCRef nm = name.mid(idx);
00529 
00530         for (uint i = 0, cnt = rf.count(); i < cnt; ++i) {
00531                 if (fileNamesVec[rf.names[i]] == nm && dirNamesVec[rf.dirs[i]] == dr)
00532                         return i;
00533         }
00534         return -1;
00535 }
00536 
00537 const QString Git::getLaneParent(SCRef fromSHA, uint laneNum) {
00538 
00539         const Rev* rs = revLookup(fromSHA);
00540         if (!rs)
00541                 return "";
00542 
00543         for (int idx = rs->orderIdx - 1; idx >= 0; idx--) {
00544 
00545                 const Rev* r = revLookup(revData.revOrder[idx]);
00546                 if (laneNum >= r->lanes.count())
00547                         return "";
00548 
00549                 if (!isFreeLane(r->lanes[laneNum])) {
00550 
00551                         int type = r->lanes[laneNum], parNum = 0;
00552                         while (!isMerge(type) && type != ACTIVE) {
00553 
00554                                 if (isHead(type))
00555                                         parNum++;
00556 
00557                                 type = r->lanes[--laneNum];
00558                         }
00559                         return r->parent(parNum);
00560                 }
00561         }
00562         return "";
00563 }
00564 
00565 const QStringList Git::getChilds(SCRef parent) {
00566 
00567         QStringList childs;
00568         const Rev* r = revLookup(parent);
00569         if (!r)
00570                 return childs;
00571 
00572         for (uint i = 0; i < r->childs.count(); i++)
00573                 childs.append(revData.revOrder[r->childs[i]]);
00574 
00575         // reorder childs by loading order
00576         QStringList::iterator itC(childs.begin());
00577         for ( ; itC != childs.end(); ++itC) {
00578                 const Rev* r = revLookup(*itC);
00579                 (*itC).prepend(QString("%1 ").arg(r->orderIdx, 6));
00580         }
00581         childs.sort();
00582         for (itC = childs.begin(); itC != childs.end(); ++itC)
00583                 (*itC) = (*itC).section(' ', -1, -1);
00584 
00585         return childs;
00586 }
00587 
00588 const QString Git::getShortLog(SCRef sha) {
00589 
00590         const Rev* r = revLookup(sha);
00591         return (r ? r->shortLog() : "");
00592 }
00593 
00594 MyProcess* Git::getDiff(SCRef sha, QObject* receiver, SCRef diffToSha, bool combined) {
00595 
00596         if (sha.isEmpty())
00597                 return NULL;
00598 
00599         QString runCmd;
00600         if (sha != ZERO_SHA) {
00601                 runCmd = "git diff-tree --no-color -r --patch-with-stat ";
00602                 runCmd.append(combined ? "-c " : "-C -m "); // TODO rename for combined
00603                 runCmd.append(diffToSha + " " + sha); // diffToSha could be empty
00604         } else
00605                 runCmd = "git diff-index --no-color -r -m --patch-with-stat HEAD";
00606 
00607         return runAsync(runCmd, receiver);
00608 }
00609 
00610 const QString Git::getFileSha(SCRef file, SCRef revSha) {
00611 
00612         if (revSha == ZERO_SHA) {
00613                 QStringList files, dummy;
00614                 getWorkDirFiles(files, dummy, RevFile::ANY);
00615                 if (files.contains(file))
00616                         return ZERO_SHA; // it is unknown to git
00617         }
00618         const QString sha(revSha == ZERO_SHA ? "HEAD" : revSha);
00619         QString runCmd("git ls-tree -r " + sha + " " + quote(file)), runOutput;
00620         if (!run(runCmd, &runOutput))
00621                 return "";
00622 
00623         return runOutput.mid(12, 40); // could be empty, deleted file case
00624 }
00625 
00626 MyProcess* Git::getFile(SCRef file, SCRef revSha, QObject* receiver,
00627                         QByteArray* result, QString* fSha) {
00628 
00629         QString runCmd;
00630         /*
00631           symlinks in git are one line files with just the name of the target,
00632           not the target content. Instead 'cat' command resolves symlinks and
00633           returns target content. So we use 'cat' only if the file is modified
00634           in working dir, to let annotation work for changed files, otherwise
00635           we go with a safe 'git cat-file blob <file sha of HEAD>' instead.
00636           NOTE: This fails if the modified file is a new symlink, converted
00637           from an old plain file. In this case annotation will fail until
00638           change is committed.
00639         */
00640         const QString fileSha(getFileSha(file, revSha));
00641         if (fileSha == ZERO_SHA)
00642                 runCmd = "cat " + quote(file);
00643         else {
00644                 if (fileSha.isEmpty()) // deleted
00645                         runCmd = "git diff-tree HEAD HEAD"; // fake an empty file reading
00646                 else
00647                         runCmd = "git cat-file blob " + fileSha;
00648         }
00649         if (fSha)
00650                 *fSha = fileSha;
00651 
00652         if (!receiver) {
00653                 run(result, runCmd);
00654                 return NULL; // in case of sync call we ignore run() return value
00655         }
00656         return runAsync(runCmd, receiver);
00657 }
00658 
00659 MyProcess* Git::getHighlightedFile(SCRef file, SCRef sha, QObject* receiver, QString* result) {
00660 
00661         if (!isTextHighlighter()) {
00662                 dbs("ASSERT in getHighlightedFile: highlighter not found");
00663                 return NULL;
00664         }
00665         QString ext(file.section('.', -1, -1, QString::SectionIncludeLeadingSep));
00666         QString inputFile(workDir + "/qgit_hlght_input" + ext);
00667         if (!saveFile(file, sha, inputFile))
00668                 return NULL;
00669 
00670         QString runCmd("source-highlight --failsafe -f html -i " + quote(inputFile));
00671 
00672         if (!receiver) {
00673                 run(runCmd, result);
00674                 on_getHighlightedFile_eof();
00675                 return NULL; // in case of sync call we ignore run() return value
00676         }
00677         MyProcess* p = runAsync(runCmd, receiver);
00678         if (p)
00679                 connect(p, SIGNAL(eof()), this, SLOT(on_getHighlightedFile_eof()));
00680         return p;
00681 }
00682 
00683 void Git::on_getHighlightedFile_eof() {
00684 
00685         QDir dir(workDir);
00686         const QStringList sl(dir.entryList("qgit_hlght_input*"));
00687         FOREACH_SL (it, sl)
00688                 dir.remove(*it);
00689 }
00690 
00691 bool Git::saveFile(SCRef file, SCRef sha, SCRef path) {
00692 
00693         QByteArray fileData;
00694         getFile(file, sha, NULL, &fileData); // sync call
00695         if (isBinaryFile(file))
00696                 return writeToFile(path, fileData);
00697 
00698         return writeToFile(path, QString(fileData));
00699 }
00700 
00701 bool Git::getTree(SCRef treeSha, SList names, SList shas,
00702                   SList types, bool isWorkingDir, SCRef treePath) {
00703 
00704         QStringList newFiles, unfiles, delFiles, dummy;
00705         if (isWorkingDir) { // retrieve unknown and deleted files under treePath
00706 
00707                 getWorkDirFiles(unfiles, dummy, RevFile::UNKNOWN);
00708                 FOREACH_SL (it, unfiles) { // don't add unknown files under other directories
00709                         QFileInfo f(*it);
00710                         SCRef d(f.dirPath(false));
00711                         if (d == treePath || (treePath.isEmpty() && d == "."))
00712                                 newFiles.append(f.fileName());
00713                 }
00714                 getWorkDirFiles(delFiles, dummy, RevFile::DELETED);
00715         }
00716         // if needed fake a working directory tree starting from HEAD tree
00717         const QString tree(treeSha == ZERO_SHA ? "HEAD" : treeSha);
00718         QString runOutput;
00719         if (!run("git ls-tree " + tree, &runOutput))
00720                 return false;
00721 
00722         const QStringList sl(QStringList::split('\n', runOutput));
00723         FOREACH_SL (it, sl) {
00724                 // insert in order any good unknown file to the list,
00725                 // newFiles must be already sorted
00726                 SCRef fn((*it).section('\t', 1, 1));
00727                 while (!newFiles.empty() && newFiles.first() < fn) {
00728                         names.append(newFiles.first());
00729                         shas.append("");
00730                         types.append("?"); // FIXME test
00731                         newFiles.pop_front();
00732                 }
00733                 // append any not deleted file
00734                 SCRef fp(treePath.isEmpty() ? fn : treePath + '/' + fn);
00735                 if (delFiles.empty() || (delFiles.findIndex(fp) == -1)) {
00736                         names.append(fn);
00737                         shas.append((*it).mid(12, 40));
00738                         types.append((*it).mid(7, 4));
00739                 }
00740         }
00741         while (!newFiles.empty()) { // append any remaining unknown file
00742                 names.append(newFiles.first());
00743                 shas.append("");
00744                 types.append("?"); // FIXME test
00745                 newFiles.pop_front();
00746         }
00747         return true;
00748 }
00749 
00750 void Git::getWorkDirFiles(SList files, SList dirs, RevFile::StatusFlag status) {
00751 
00752         files.clear();
00753         dirs.clear();
00754         const RevFile* f = getFiles(ZERO_SHA);
00755         if (!f)
00756                 return;
00757 
00758         for (int i = 0; i < f->count(); i++) {
00759 
00760                 if (f->statusCmp(i, status)) {
00761 
00762                         SCRef fp(filePath(*f, i));
00763                         files.append(fp);
00764                         for (int j = 0, cnt = fp.contains('/'); j < cnt; j++) {
00765 
00766                                 SCRef dir(fp.section('/', 0, j));
00767                                 if (dirs.findIndex(dir) == -1)
00768                                         dirs.append(dir);
00769                         }
00770                 }
00771         }
00772 }
00773 
00774 bool Git::isNothingToCommit() {
00775 
00776         const RevFile* rf = revsFiles[ZERO_SHA];
00777         return (!rf || ((uint)rf->count() == _wd.otherFiles.count()));
00778 }
00779 
00780 bool Git::isTreeModified(SCRef sha) {
00781 
00782         const RevFile* f = getFiles(sha);
00783         if (!f)
00784                 return true; // no files info, stay on the safe side
00785 
00786         for (int i = 0; i < f->count(); ++i)
00787                 if (!f->statusCmp(i, RevFile::MODIFIED))
00788                         return true;
00789 
00790         return false;
00791 }
00792 
00793 bool Git::isParentOf(SCRef par, SCRef child) {
00794 
00795         const Rev* c = revLookup(child);
00796         return (c && c->parentsCount() == 1 && c->parent(0) == par); // no merges
00797 }
00798 
00799 bool Git::isSameFiles(SCRef tree1Sha, SCRef tree2Sha) {
00800 
00801         // early skip common case of browsing with up and down arrows, i.e.
00802         // going from parent(child) to child(parent). In this case we can
00803         // check RevFileMap and skip a costly 'git diff-tree' call.
00804         if (isParentOf(tree1Sha, tree2Sha))
00805                 return !isTreeModified(tree2Sha);
00806 
00807         if (isParentOf(tree2Sha, tree1Sha))
00808                 return !isTreeModified(tree1Sha);
00809 
00810         const QString runCmd("git diff-tree --no-color -r " + tree1Sha + " " + tree2Sha);
00811         QString runOutput;
00812         if (!run(runCmd, &runOutput))
00813                 return false;
00814 
00815         bool isChanged = (runOutput.find(" A\t") != -1 || runOutput.find(" D\t") != -1);
00816         return !isChanged;
00817 }
00818 
00819 const QStringList Git::getDescendantBranches(SCRef sha) {
00820 
00821         QStringList tl;
00822         const Rev* r = revLookup(sha);
00823         if (!r || (r->descBrnMaster == -1))
00824                 return tl;
00825 
00826         const QValueVector<int>& nr = revLookup(revData.revOrder[r->descBrnMaster])->descBranches;
00827 
00828         for (uint i = 0; i < nr.count(); i++) {
00829 
00830                 SCRef sha = revData.revOrder[nr[i]];
00831                 SCRef cap = " (" + sha + ") ";
00832                 RefMap::const_iterator it(refsShaMap.find(sha));
00833                 if (it == refsShaMap.constEnd())
00834                         continue;
00835 
00836                 if (!(*it).branches.empty())
00837                         tl.append((*it).branches.join(cap).append(cap));
00838 
00839                 if (!(*it).remoteBranches.empty())
00840                         tl.append((*it).remoteBranches.join(cap).append(cap));
00841         }
00842         return tl;
00843 }
00844 
00845 const QStringList Git::getNearTags(bool goDown, SCRef sha) {
00846 
00847         QStringList tl;
00848         const Rev* r = revLookup(sha);
00849         if (!r)
00850                 return tl;
00851 
00852         int nearRefsMaster = (goDown ? r->descRefsMaster : r->ancRefsMaster);
00853         if (nearRefsMaster == -1)
00854                 return tl;
00855 
00856         const QValueVector<int>& nr = goDown ? revLookup(revData.revOrder[nearRefsMaster])->descRefs :
00857                                                revLookup(revData.revOrder[nearRefsMaster])->ancRefs;
00858 
00859         for (uint i = 0; i < nr.count(); i++) {
00860 
00861                 SCRef sha = revData.revOrder[nr[i]];
00862                 SCRef cap = " (" + sha + ")";
00863                 RefMap::const_iterator it(refsShaMap.find(sha));
00864                 if (it != refsShaMap.constEnd())
00865                         tl.append((*it).tags.join(cap).append(cap));
00866         }
00867         return tl;
00868 }
00869 
00870 const QString Git::getDefCommitMsg() {
00871 
00872         QString sha(ZERO_SHA);
00873         if (isStGIT && !getAllRefSha(APPLIED).isEmpty()) {
00874                 QString top;
00875                 if (run("stg top", &top))
00876                         sha = getRefSha(top.stripWhiteSpace(), APPLIED, false);
00877         }
00878         const Rev* c = revLookup(sha);
00879         if (!c) {
00880                 dbp("ASSERT: getDefCommitMsg sha <%1> not found", sha);
00881                 return "";
00882         }
00883         if (sha == ZERO_SHA)
00884                 return c->longLog();
00885 
00886         return c->shortLog() + '\n' + c->longLog().stripWhiteSpace();
00887 }
00888 
00889 const QString Git::colorMatch(SCRef txt, QRegExp& regExp) {
00890 
00891         QString text(txt);
00892         if (regExp.isEmpty())
00893                 return text;
00894 
00895         // we use $_1 and $_2 instead of '<' and '>' to avoid later substitutions
00896         SCRef startCol(QString::fromLatin1("$_1b$_2$_1font color=\"red\"$_2"));
00897         SCRef endCol(QString::fromLatin1("$_1/font$_2$_1/b$_2"));
00898         int pos = 0;
00899         while ((pos = text.find(regExp, pos)) != -1) {
00900 
00901                 SCRef match(regExp.cap(0));
00902                 const QString coloredText(startCol + match + endCol);
00903                 text.replace(pos, match.length(), coloredText);
00904                 pos += coloredText.length();
00905         }
00906         return text;
00907 }
00908 
00909 const QString Git::getDesc(SCRef sha, QRegExp& shortLogRE, QRegExp& longLogRE, bool showHeader) {
00910 
00911         if (sha.isEmpty())
00912                 return "";
00913 
00914         const Rev* c = revLookup(sha);
00915         if (!c)            // sha of a not loaded revision, as
00916                 return ""; // example asked from file history
00917 
00918         QString text;
00919         if (c->isDiffCache)
00920                 text = c->longLog();
00921         else {
00922                 if (showHeader) {
00923                         text = QString("Author: " + c->author() + "\nDate:   ");
00924                         text.append(getLocalDate(c->authorDate()));
00925                         if (!c->isUnApplied && !c->isApplied) {
00926                                 text.append("\nParent: ").append(c->parents().join("\nParent: "));
00927 
00928                                 QStringList sl = getChilds(sha);
00929                                 if (!sl.isEmpty())
00930                                         text.append("\nChild: ").append(sl.join("\nChild: "));
00931 
00932                                 sl = getDescendantBranches(sha);
00933                                 if (!sl.empty())
00934                                         text.append("\nBranch: ").append(sl.join("\nBranch: "));
00935 
00936                                 sl = getNearTags(!optGoDown, sha);
00937                                 if (!sl.isEmpty())
00938                                         text.append("\nFollows: ").append(sl.join(", "));
00939 
00940                                 sl = getNearTags(optGoDown, sha);
00941                                 if (!sl.isEmpty())
00942                                         text.append("\nPrecedes: ").append(sl.join(", "));
00943                         }
00944                         text.append("\n\n    " + colorMatch(c->shortLog(), shortLogRE) + "\n");
00945                 }
00946                 const QString log(colorMatch(c->longLog(), longLogRE));
00947                 if (!showHeader && log.stripWhiteSpace().isEmpty())
00948                         text.append(colorMatch(c->shortLog(), shortLogRE));
00949                 else
00950                         text.append(log);
00951         }
00952         // workaround a Qt issue
00953         QTextCodec* tc = QTextCodec::codecForCStrings();
00954         QTextCodec::setCodecForCStrings(0);
00955         text = QStyleSheet::convertFromPlainText(text); // this puppy needs Latin-1
00956         QTextCodec::setCodecForCStrings(tc);
00957 
00958         // highlight SHA's
00959         //
00960         // search for abbreviated sha too. To filter out debug backtraces sometimes
00961         // added to commit logs, we avoid to call git rev-parse for a possible abbreviated
00962         // sha if there isn't a leading trailing space or an open parenthesis and,
00963         // in that case, before the space must not be a ':' character.
00964         // It's an ugly heuristic, but seems to work in most cases.
00965         QRegExp reSHA("..[0-9a-f]{21,40}|[^:][\\s(][0-9a-f]{6,20}", false);
00966         reSHA.setMinimal(false);
00967         int pos = 0;
00968         while ((pos = text.find(reSHA, pos)) != -1) {
00969 
00970                 SCRef ref = reSHA.cap(0).mid(2);
00971                 const Rev* r = (ref.length() == 40 ? revLookup(ref) : revLookup(getRefSha(ref)));
00972                 if (r) {
00973                         QString slog(r->shortLog());
00974                         if (slog.isEmpty()) // very rare but possible
00975                                 slog = r->sha();
00976                         if (slog.length() > 60)
00977                                 slog = slog.left(57).stripWhiteSpace().append("...");
00978 
00979                         slog = QStyleSheet::escape(slog);
00980                         const QString link("<a href=\"" + r->sha() + "\">" + slog + "</a>");
00981                         text.replace(pos + 2, ref.length(), link);
00982                         pos += link.length();
00983                 } else
00984                         pos += reSHA.cap(0).length();
00985         }
00986         text.replace("$_1", "<"); // see colorMatch()
00987         text.replace("$_2", ">");
00988         return text;
00989 }
00990 
00991 const RevFile* Git::insertNewFiles(SCRef sha, SCRef data) {
00992 
00993         RevFile* rf = new RevFile();
00994         parseDiffFormat(*rf, data);
00995         revsFiles.insert(sha, rf);
00996         return rf;
00997 }
00998 
00999 const RevFile* Git::getAllMergeFiles(const Rev* r) {
01000 
01001         SCRef mySha(ALL_MERGE_FILES + r->sha());
01002         RevFile* rf = revsFiles[mySha];
01003         if (rf)
01004                 return rf;
01005 
01006         QString runCmd("git diff-tree --no-color  -r -m -C " + r->sha()), runOutput;
01007         if (!run(runCmd, &runOutput))
01008                 return NULL;
01009 
01010         return insertNewFiles(mySha, runOutput);
01011 }
01012 
01013 const RevFile* Git::getFiles(SCRef sha, SCRef diffToSha, bool allFiles, SCRef path) {
01014 
01015         const Rev* r = revLookup(sha);
01016         if (!r)
01017                 return NULL;
01018 
01019         if (r->parentsCount() == 0) // skip initial rev
01020                 return NULL;
01021 
01022         if (r->parentsCount() > 1 && diffToSha.isEmpty() && allFiles)
01023                 return getAllMergeFiles(r);
01024 
01025         if (!diffToSha.isEmpty() && (sha != ZERO_SHA)) {
01026 
01027                 QString runCmd("git diff-tree --no-color -r -m -C ");
01028                 runCmd.append(diffToSha + " " + sha);
01029                 if (!path.isEmpty())
01030                         runCmd.append(" " + path);
01031 
01032                 QString runOutput;
01033                 if (!run(runCmd, &runOutput))
01034                         return NULL;
01035 
01036                 // we insert a dummy revision file object. It will be
01037                 // overwritten at each request but we don't care.
01038                 return insertNewFiles(CUSTOM_SHA, runOutput);
01039         }
01040         RevFile* rf = revsFiles[sha];
01041         if (rf)
01042                 return rf; // ZERO_SHA search arrives here
01043 
01044         if (sha == ZERO_SHA) {
01045                 dbs("ASSERT in Git::getFiles, ZERO_SHA not found");
01046                 return NULL;
01047         }
01048         QString runCmd("git diff-tree --no-color -r -c -C " + sha), runOutput;
01049         if (!run(runCmd, &runOutput))
01050                 return NULL;
01051 
01052         rf = revsFiles[sha];
01053         if (rf) // has been created in the mean time?
01054                 return rf;
01055 
01056         cacheNeedsUpdate = true;
01057         return insertNewFiles(sha, runOutput);
01058 }
01059 
01060 bool Git::startFileHistory(FileHistory* fh) {
01061 
01062         return startRevList(fh->fileName, fh);
01063 }
01064 
01065 void Git::getFileFilter(SCRef path, QMap<QString, bool>& shaMap) {
01066 
01067         shaMap.clear();
01068         QRegExp rx(path, false, true); // not case sensitive and with wildcard
01069         FOREACH (StrVect, it, revData.revOrder) {
01070                 RevFile* rf = revsFiles[*it];
01071                 if (!rf)
01072                         continue;
01073 
01074                 // case insensitive, wildcard search
01075                 for (int i = 0; i < rf->count(); ++i)
01076                         if (rx.search(filePath(*rf, i)) != -1) {
01077                                 shaMap.insert(*it, true);
01078                                 break;
01079                         }
01080         }
01081 }
01082 
01083 bool Git::getPatchFilter(SCRef exp, bool isRegExp, QMap<QString, bool>& shaMap) {
01084 
01085         shaMap.clear();
01086         QString buf;
01087         FOREACH (StrVect, it, revData.revOrder)
01088                 if (*it != ZERO_SHA)
01089                         buf.append(*it).append('\n');
01090 
01091         if (buf.isEmpty())
01092                 return true;
01093 
01094         QString runCmd("git diff-tree --no-color -r -s --stdin "), runOutput;
01095         if (isRegExp)
01096                 runCmd.append("--pickaxe-regex ");
01097 
01098         runCmd.append(quote("-S" + exp));
01099         if (!run(runCmd, &runOutput, NULL, buf)) // could be slow
01100                 return false;
01101 
01102         const QStringList sl(QStringList::split('\n', runOutput));
01103         FOREACH_SL (it, sl)
01104                 shaMap.insert(*it, true);
01105 
01106         return true;
01107 }
01108 
01109 bool Git::resetCommits(int parentDepth) {
01110 
01111         QString runCmd("git reset --soft HEAD~");
01112         runCmd.append(QString::number(parentDepth));
01113         return run(runCmd);
01114 }
01115 
01116 bool Git::applyPatchFile(SCRef patchPath, bool fold, bool isDragDrop) {
01117 
01118         if (isStGIT) {
01119                 if (fold) {
01120                         bool ok = run("stg fold " + quote(patchPath)); // merge in working dir
01121                         if (ok)
01122                                 ok = run("stg refresh"); // update top patch
01123                         return ok;
01124                 } else
01125                         return run("stg import --mail " + quote(patchPath));
01126         }
01127         QString runCmd("git am --utf8 --3way ");
01128         if (isDragDrop)
01129                 runCmd.append("--keep ");
01130 
01131         else if (testFlag(SIGN_PATCH_F))
01132                 runCmd.append("--signoff ");
01133 
01134         return run(runCmd + quote(patchPath));
01135 }
01136 
01137 bool Git::formatPatch(SCList shaList, SCRef dirPath, SCRef remoteDir) {
01138 
01139         bool remote = !remoteDir.isEmpty();
01140         QSettings settings;
01141         const QString FPArgs = settings.readEntry(APP_KEY + FPATCH_ARGS_KEY, "");
01142 
01143         QString runCmd("git format-patch --no-color");
01144         if (testFlag(NUMBERS_F) && !remote)
01145                 runCmd.append(" -n");
01146 
01147         if (remote)
01148                 runCmd.append(" --keep-subject");
01149 
01150         runCmd.append(" -o " + dirPath);
01151         if (!FPArgs.isEmpty())
01152                 runCmd.append(" " + FPArgs);
01153 
01154         runCmd.append(" --start-number=");
01155 
01156         const QString tmp(workDir);
01157         if (remote)
01158                 workDir = remoteDir; // run() uses workDir value
01159 
01160         int n = shaList.count();
01161         bool ret = false;
01162         FOREACH_SL (it, shaList) { // shaList is ordered by newest to oldest
01163                 const QString cmd(runCmd + QString::number(n) + " " +
01164                                   *it + QString::fromLatin1("^..") + *it);
01165                 n--;
01166                 ret = run(cmd);
01167                 if (!ret)
01168                         break;
01169         }
01170         workDir = tmp;
01171         return ret;
01172 }
01173 
01174 bool Git::updateIndex(SCList selFiles) {
01175 
01176         if (selFiles.empty())
01177                 return true;
01178 
01179         QString runCmd("git update-index --add --remove --replace -- ");
01180         runCmd.append(quote(selFiles));
01181         return run(runCmd);
01182 }
01183 
01184 bool Git::commitFiles(SCList selFiles, SCRef msg) {
01185 /*
01186         Part of this code is taken from Fredrik Kuivinen "Gct" tool
01187 */
01188         const QString msgFile(gitDir + "/qgit_cmt_msg");
01189         if (!writeToFile(msgFile, msg)) // early skip
01190                 return false;
01191 
01192         // add user selectable commit options
01193         QSettings settings;
01194         const QString CMArgs = settings.readEntry(APP_KEY + CMT_ARGS_KEY, "");
01195 
01196         QString cmtOptions;
01197         if (!CMArgs.isEmpty())
01198                 cmtOptions.append(" " + CMArgs);
01199 
01200         if (testFlag(SIGN_CMT_F))
01201                 cmtOptions.append(" -s");
01202 
01203         if (testFlag(VERIFY_CMT_F))
01204                 cmtOptions.append(" -v");
01205 
01206         // extract not selected files already updated
01207         // in index, save them to restore at the end
01208         const QStringList notSelInIndexFiles(getOtherFiles(selFiles, optOnlyInIndex));
01209 
01210         // extract selected NOT to be deleted files to
01211         // later feed git commit. Files to be deleted
01212         // should avoid going through 'git commit'
01213         QStringList selNotDelFiles;
01214         const RevFile* files = getFiles(ZERO_SHA); // files != NULL
01215         FOREACH_SL (it, selFiles) {
01216                 int idx = findFileIndex(*files, *it);
01217                 if (!files->statusCmp(idx, RevFile::DELETED))
01218                         selNotDelFiles.append(*it);
01219         }
01220         // test if we need a git read-tree to temporary
01221         // remove not selected files from index
01222         if (!notSelInIndexFiles.empty())
01223                 if (!run("git read-tree --reset HEAD"))
01224                         return false;
01225 
01226         // before to commit we have to update index with all
01227         // the selected files because git commit doesn't
01228         // use --add flag
01229         updateIndex(selFiles);
01230 
01231         // now we can commit, 'git commit' will update index
01232         // with selected (not to be deleted) files for us
01233         QString runCmd("git commit" + cmtOptions + " -F " + msgFile);
01234         if (!selNotDelFiles.empty())
01235                 runCmd.append(" -i " + quote(selNotDelFiles));
01236 
01237         if (!run(runCmd))
01238                 return false;
01239 
01240         // finally restore not selected files in index
01241         if (!notSelInIndexFiles.empty())
01242                 if (!updateIndex(notSelInIndexFiles))
01243                         return false;
01244 
01245         QDir dir(workDir);
01246         dir.remove(msgFile);
01247         return true;
01248 }
01249 
01250 bool Git::mkPatchFromIndex(SCRef msg, SCRef patchFile) {
01251 
01252         QString runOutput;
01253         if (!run("git diff-index --no-color --cached -p HEAD", &runOutput))
01254                 return false;
01255 
01256         const QString patch("Subject: " + msg + "\n---\n" + runOutput);
01257         return writeToFile(patchFile, patch);
01258 }
01259 
01260 const QStringList Git::getOtherFiles(SCList selFiles, bool onlyInIndex) {
01261 
01262         const RevFile* files = getFiles(ZERO_SHA); // files != NULL
01263         QStringList notSelFiles;
01264         for (int i = 0; i < files->count(); ++i) {
01265                 SCRef fp = filePath(*files, i);
01266                 if (selFiles.find(fp) == selFiles.constEnd()) { // not selected...
01267                         if (!onlyInIndex)
01268                                 notSelFiles.append(fp);
01269                         else if (files->statusCmp(i, RevFile::IN_INDEX))
01270                                 notSelFiles.append(fp);
01271                 }
01272         }
01273         return notSelFiles;
01274 }
01275 
01276 void Git::removeFiles(SCList selFiles, SCRef workDir, SCRef ext) {
01277 
01278         QDir d(workDir);
01279         FOREACH_SL (it, selFiles)
01280                 d.rename(*it, *it + ext);
01281 }
01282 
01283 void Git::restoreFiles(SCList selFiles, SCRef workDir, SCRef ext) {
01284 
01285         QDir d(workDir);
01286         FOREACH_SL (it, selFiles)
01287                 d.rename(*it + ext, *it); // overwrites any existent file
01288 }
01289 
01290 void Git::removeDeleted(SCList selFiles) {
01291 
01292         QDir dir(workDir);
01293         const RevFile* files = getFiles(ZERO_SHA); // files != NULL
01294         FOREACH_SL (it, selFiles) {
01295                 int idx = findFileIndex(*files, *it);
01296                 if (files->statusCmp(idx, RevFile::DELETED))
01297                         dir.remove(*it);
01298         }
01299 }
01300 
01301 bool Git::stgCommit(SCList selFiles, SCRef msg, SCRef patchName, bool fold) {
01302 
01303         // here the deal is to create a patch with the diffs between the
01304         // updated index and HEAD, then resetting the index and working
01305         // dir to HEAD so to have a clean tree, then import/fold the patch
01306         bool retval = true;
01307         const QString patchFile(gitDir + "/qgit_tmp_patch");
01308         const QString extNS(".qgit_removed_not_selected");
01309         const QString extS(".qgit_removed_selected");
01310 
01311         // we have selected modified files in selFiles, we still need
01312         // to know the modified but not selected files and, among
01313         // these the cached ones to properly restore state at the end.
01314         const QStringList notSelFiles = getOtherFiles(selFiles, !optOnlyInIndex);
01315         const QStringList notSelInIndexFiles = getOtherFiles(selFiles, optOnlyInIndex);
01316 
01317         // update index with selected files
01318         if (!run("git read-tree --reset HEAD"))
01319                 goto error;
01320         if (!updateIndex(selFiles))
01321                 goto error;
01322 
01323         // create a patch with diffs between index and HEAD
01324         if (!mkPatchFromIndex(msg, patchFile))
01325                 goto error;
01326 
01327         // temporary remove files according to their type
01328         removeFiles(selFiles, workDir, extS); // to use in case of rollback
01329         removeFiles(notSelFiles, workDir, extNS); // to restore at the end
01330 
01331         // checkout index to have a clean tree
01332         if (!run("git read-tree --reset HEAD"))
01333                 goto error;
01334         if (!run("git checkout-index -q -f -u -a"))
01335                 goto rollback;
01336 
01337         // finally import/fold the patch
01338         if (fold) {
01339                 // update patch message before to fold so to use refresh only as a rename tool
01340                 if (!msg.isEmpty()) {
01341                         if (!run("stg refresh --message \"" + msg.stripWhiteSpace() + "\""))
01342                                 goto rollback;
01343                 }
01344                 if (!run("stg fold " + quote(patchFile)))
01345                         goto rollback;
01346                 if (!run("stg refresh")) // refresh needed after fold
01347                         goto rollback;
01348         } else {
01349                 if (!run("stg import --mail --name " + quote(patchName) + " " + quote(patchFile)))
01350                         goto rollback;
01351         }
01352         goto exit;
01353 
01354 rollback:
01355         restoreFiles(selFiles, workDir, extS);
01356         removeDeleted(selFiles); // remove files to be deleted from working tree
01357 
01358 error:
01359         retval = false;
01360 
01361 exit:
01362         // it is safe to call restore() also if back-up files don't
01363         // exist, so we can 'goto exit' from anywhere.
01364         restoreFiles(notSelFiles, workDir, extNS);
01365         updateIndex(notSelInIndexFiles);
01366         QDir dir(workDir);
01367         dir.remove(patchFile);
01368         FOREACH_SL (it, selFiles)
01369                 dir.remove(*it + extS); // remove temporary backup rollback files
01370         return retval;
01371 }
01372 
01373 bool Git::makeTag(SCRef sha, SCRef tagName, SCRef msg) {
01374 
01375         if (msg.isEmpty())
01376                 return run("git tag " + tagName + " " + sha);
01377 
01378         return run("git tag -m \"" + msg + "\" " + tagName + " " + sha);
01379 }
01380 
01381 bool Git::deleteTag(SCRef sha) {
01382 
01383         const QStringList tags(getRefName(sha, TAG));
01384         if (!tags.empty())
01385                 return run("git tag -d " + tags.first()); // only one
01386 
01387         return false;
01388 }
01389 
01390 bool Git::stgPush(SCRef sha) {
01391 
01392         const QStringList patch(getRefName(sha, UN_APPLIED));
01393         if (patch.count() != 1) {
01394                 dbp("ASSERT in Git::stgPush, found %1 patches instead of 1", patch.count());
01395                 return false;
01396         }
01397         return run("stg push " + quote(patch.first()));
01398 }
01399 
01400 bool Git::stgPop(SCRef sha) {
01401 
01402         QString top;
01403         if (!run("stg top", &top))
01404                 return false;
01405 
01406         const QStringList patch(getRefName(sha, APPLIED));
01407         if (patch.count() != 1) {
01408                 dbp("ASSERT in Git::stgPop, found %1 patches instead of 1", patch.count());
01409                 return false;
01410         }
01411         if (patch.first() != top.stripWhiteSpace())
01412                 if (!run("stg pop " + quote(patch)))
01413                         return false;
01414 
01415         return run("stg pop"); // finally remove selected one
01416 }

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