00001
00002
00003
00004
00005
00006
00007
00008
00009 #include <stdlib.h>
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;
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
00075
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
00096
00097
00098
00099
00100
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;
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);
00159 QString mimeName(tc ? tc->mimeName() : "Latin1");
00160
00161
00162
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)
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())
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
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) {
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
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);
00315 }
00316 if (onlyLoaded) {
00317 names.sort();
00318 QStringList::iterator itN(names.begin());
00319 for ( ; itN != names.end(); ++itN)
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(" --> "))
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;
00416 }
00417
00418 addExtraFileInfo(rowName, sha, diffToSha, allMergeFiles);
00419
00420 if (rowName->contains(" --> ")) {
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) {
00430
00431 Annotate* ann = new Annotate(this, guiObj);
00432 ann->start(fh);
00433 return ann;
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
00457
00458 emit cancelLoading(fh);
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;
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();
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
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 ");
00603 runCmd.append(diffToSha + " " + sha);
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;
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);
00624 }
00625
00626 MyProcess* Git::getFile(SCRef file, SCRef revSha, QObject* receiver,
00627 QByteArray* result, QString* fSha) {
00628
00629 QString runCmd;
00630
00631
00632
00633
00634
00635
00636
00637
00638
00639
00640 const QString fileSha(getFileSha(file, revSha));
00641 if (fileSha == ZERO_SHA)
00642 runCmd = "cat " + quote(file);
00643 else {
00644 if (fileSha.isEmpty())
00645 runCmd = "git diff-tree HEAD HEAD";
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;
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;
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);
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) {
00706
00707 getWorkDirFiles(unfiles, dummy, RevFile::UNKNOWN);
00708 FOREACH_SL (it, unfiles) {
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
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
00725
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("?");
00731 newFiles.pop_front();
00732 }
00733
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()) {
00742 names.append(newFiles.first());
00743 shas.append("");
00744 types.append("?");
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;
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);
00797 }
00798
00799 bool Git::isSameFiles(SCRef tree1Sha, SCRef tree2Sha) {
00800
00801
00802
00803
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
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)
00916 return "";
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
00953 QTextCodec* tc = QTextCodec::codecForCStrings();
00954 QTextCodec::setCodecForCStrings(0);
00955 text = QStyleSheet::convertFromPlainText(text);
00956 QTextCodec::setCodecForCStrings(tc);
00957
00958
00959
00960
00961
00962
00963
00964
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())
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", "<");
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)
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
01037
01038 return insertNewFiles(CUSTOM_SHA, runOutput);
01039 }
01040 RevFile* rf = revsFiles[sha];
01041 if (rf)
01042 return rf;
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)
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);
01069 FOREACH (StrVect, it, revData.revOrder) {
01070 RevFile* rf = revsFiles[*it];
01071 if (!rf)
01072 continue;
01073
01074
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))
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));
01121 if (ok)
01122 ok = run("stg refresh");
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;
01159
01160 int n = shaList.count();
01161 bool ret = false;
01162 FOREACH_SL (it, shaList) {
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
01187
01188 const QString msgFile(gitDir + "/qgit_cmt_msg");
01189 if (!writeToFile(msgFile, msg))
01190 return false;
01191
01192
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
01207
01208 const QStringList notSelInIndexFiles(getOtherFiles(selFiles, optOnlyInIndex));
01209
01210
01211
01212
01213 QStringList selNotDelFiles;
01214 const RevFile* files = getFiles(ZERO_SHA);
01215 FOREACH_SL (it, selFiles) {
01216 int idx = findFileIndex(*files, *it);
01217 if (!files->statusCmp(idx, RevFile::DELETED))
01218 selNotDelFiles.append(*it);
01219 }
01220
01221
01222 if (!notSelInIndexFiles.empty())
01223 if (!run("git read-tree --reset HEAD"))
01224 return false;
01225
01226
01227
01228
01229 updateIndex(selFiles);
01230
01231
01232
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
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);
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()) {
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);
01288 }
01289
01290 void Git::removeDeleted(SCList selFiles) {
01291
01292 QDir dir(workDir);
01293 const RevFile* files = getFiles(ZERO_SHA);
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
01304
01305
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
01312
01313
01314 const QStringList notSelFiles = getOtherFiles(selFiles, !optOnlyInIndex);
01315 const QStringList notSelInIndexFiles = getOtherFiles(selFiles, optOnlyInIndex);
01316
01317
01318 if (!run("git read-tree --reset HEAD"))
01319 goto error;
01320 if (!updateIndex(selFiles))
01321 goto error;
01322
01323
01324 if (!mkPatchFromIndex(msg, patchFile))
01325 goto error;
01326
01327
01328 removeFiles(selFiles, workDir, extS);
01329 removeFiles(notSelFiles, workDir, extNS);
01330
01331
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
01338 if (fold) {
01339
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"))
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);
01357
01358 error:
01359 retval = false;
01360
01361 exit:
01362
01363
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);
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());
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");
01416 }