2 Description: interface to git programs
4 Author: Marco Costalba (C) 2005-2007
6 Copyright: See COPYING file that comes with this distribution
9 #include <QApplication>
12 #include <QImageReader>
15 //#include <QSet> //CT TODO remove
18 #include <QTextDocument>
19 #include <QTextStream>
20 #include "FileHistory.h"
23 #include "dataloader.h"
26 #include "myprocess.h"
27 #include "rangeselectimpl.h"
29 #define SHOW_MSG(x) QApplication::postEvent(parent(), new MessageEvent(x)); EM_PROCESS_EVENTS_NO_INPUT;
31 // Used on init() for reading parameters once;
32 // It's OK to be unique among qgit windows.
33 static bool startup
= true;
38 // ****************************************************************************
40 bool Git::TreeEntry::operator<(const TreeEntry
& te
) const {
42 if (this->type
== te
.type
)
43 return( this->name
.localeAwareCompare( te
.name
) < 0 );
45 // directories are smaller then files
46 // to appear as first when sorted
47 if (this->type
== "tree")
50 if (te
.type
== "tree")
53 return( this->name
.localeAwareCompare( te
.name
) < 0 );
56 Git::Git(QObject
* p
) : QObject(p
) {
58 EM_INIT(exGitStopped
, "Stopping connection with git");
60 fileCacheAccessed
= cacheNeedsUpdate
= isMergeHead
= false;
61 isStGIT
= isGIT
= loadingUnAppliedPatches
= isTextHighlighterFound
= false;
62 errorReportingEnabled
= true; // report errors if run() fails
65 revsFiles
.reserve(MAX_DICT_SIZE
);
68 void Git::checkEnvironment() {
71 if (run("git --version", &version
)) {
73 version
= version
.section(' ', -1, -1).section('.', 0, 2);
74 if (version
< GIT_VERSION
) {
76 // simply send information, the 'not compatible version'
77 // policy should be implemented upstream
78 const QString
cmd("Current git version is " + version
+
79 " but is required " + GIT_VERSION
+ " or better");
81 const QString
errorDesc("Your installed git is too old."
82 "\nPlease upgrade to avoid possible misbehaviours.");
84 MainExecErrorEvent
* e
= new MainExecErrorEvent(cmd
, errorDesc
);
85 QApplication::postEvent(parent(), e
);
88 dbs("Cannot find git files");
91 errorReportingEnabled
= false;
92 isTextHighlighterFound
= run("source-highlight -V", &version
);
93 errorReportingEnabled
= true;
94 if (isTextHighlighterFound
)
95 textHighlighterVersionFound
= version
.section('\n', 0, 0);
97 textHighlighterVersionFound
= "GNU source-highlight not installed";
100 void Git::userInfo(SList info
) {
102 git looks for commit user information in following order:
104 - GIT_AUTHOR_NAME and GIT_AUTHOR_EMAIL environment variables
105 - repository config file
107 - your name, hostname and domain
109 const QString
env(QProcess::systemEnvironment().join(","));
110 QString
user(env
.section("GIT_AUTHOR_NAME", 1).section(",", 0, 0).section("=", 1).trimmed());
111 QString
email(env
.section("GIT_AUTHOR_EMAIL", 1).section(",", 0, 0).section("=", 1).trimmed());
114 info
<< "Environment" << user
<< email
;
116 errorReportingEnabled
= false; // 'git config' could fail, see docs
118 run("git config user.name", &user
);
119 run("git config user.email", &email
);
120 info
<< "Local config" << user
<< email
;
122 run("git config --global user.name", &user
);
123 run("git config --global user.email", &email
);
124 info
<< "Global config" << user
<< email
;
126 errorReportingEnabled
= true;
129 const QStringList
Git::getGitConfigList(bool global
) {
133 errorReportingEnabled
= false; // 'git config' could fail, see docs
136 run("git config --global --list", &runOutput
);
138 run("git config --list", &runOutput
);
140 errorReportingEnabled
= true;
142 return runOutput
.split('\n', QString::SkipEmptyParts
);
145 bool Git::isImageFile(SCRef file
) {
147 const QString
ext(file
.section('.', -1).toLower());
148 return QImageReader::supportedImageFormats().contains(ext
.toLatin1());
151 //CT TODO investigate if there is a better way of getting this (from git e.g.)
152 bool Git::isBinaryFile(SCRef file
) {
154 static const char* binaryFileExtensions
[] = {"bmp", "gif", "jpeg", "jpg",
155 "png", "svg", "tiff", "pcx", "xcf", "xpm",
156 "bz", "bz2", "rar", "tar", "z", "gz", "tgz", "zip", 0};
158 if (isImageFile(file
))
161 const QString
ext(file
.section('.', -1).toLower());
163 while (binaryFileExtensions
[i
] != 0)
164 if (ext
== binaryFileExtensions
[i
++])
169 void Git::setThrowOnStop(bool b
) {
172 EM_REGISTER(exGitStopped
);
174 EM_REMOVE(exGitStopped
);
177 bool Git::isThrowOnStopRaised(int excpId
, SCRef curContext
) {
179 return EM_MATCH(excpId
, exGitStopped
, curContext
);
182 void Git::setTextCodec(QTextCodec
* tc
) {
184 // QTextCodec::setCodecForCStrings(tc); // works also with tc == 0 (Latin1)
185 QTextCodec::setCodecForLocale(tc
);
186 QString
name(tc
? tc
->name() : "Latin1");
188 // workaround Qt issue of mime name different from
189 // standard http://www.iana.org/assignments/character-sets
190 if (name
== "Big5-HKSCS")
193 run("git config i18n.commitencoding " + name
);
196 QTextCodec
* Git::getTextCodec(bool* isGitArchive
) {
198 *isGitArchive
= isGIT
;
199 if (!isGIT
) // can be called also when not in an archive
203 if (!run("git config i18n.commitencoding", &runOutput
))
206 if (runOutput
.isEmpty()) // git docs says default is utf-8
207 return QTextCodec::codecForName(QByteArray("utf8"));
209 return QTextCodec::codecForName(runOutput
.trimmed().toLatin1());
212 //CT TODO utility function; can go elsewhere
213 const QString
Git::quote(SCRef nm
) {
215 return (QUOTE_CHAR
+ nm
+ QUOTE_CHAR
);
218 //CT TODO utility function; can go elsewhere
219 const QString
Git::quote(SCList sl
) {
221 QString
q(sl
.join(QUOTE_CHAR
+ ' ' + QUOTE_CHAR
));
222 q
.prepend(QUOTE_CHAR
).append(QUOTE_CHAR
);
226 uint
Git::checkRef(const ShaString
& sha
, uint mask
) const {
228 RefMap::const_iterator
it(refsShaMap
.constFind(sha
));
229 return (it
!= refsShaMap
.constEnd() ? (*it
).type
& mask
: 0);
232 uint
Git::checkRef(SCRef sha
, uint mask
) const {
234 RefMap::const_iterator
it(refsShaMap
.constFind(toTempSha(sha
)));
235 return (it
!= refsShaMap
.constEnd() ? (*it
).type
& mask
: 0);
238 const QStringList
Git::getRefName(SCRef sha
, RefType type
, QString
* curBranch
) const {
240 if (!checkRef(sha
, type
))
241 return QStringList();
243 const Reference
& rf
= refsShaMap
[toTempSha(sha
)];
246 *curBranch
= rf
.currentBranch
;
251 else if (type
== BRANCH
)
254 else if (type
== RMT_BRANCH
)
255 return rf
.remoteBranches
;
257 else if (type
== REF
)
260 else if (type
== APPLIED
|| type
== UN_APPLIED
)
261 return QStringList(rf
.stgitPatch
);
263 return QStringList();
266 const QStringList
Git::getAllRefSha(uint mask
) {
269 FOREACH (RefMap
, it
, refsShaMap
)
270 if ((*it
).type
& mask
)
271 shas
.append(it
.key());
275 const QString
Git::getRefSha(SCRef refName
, RefType type
, bool askGit
) {
277 bool any
= (type
== ANY_REF
);
279 FOREACH (RefMap
, it
, refsShaMap
) {
281 const Reference
& rf
= *it
;
283 if ((any
|| type
== TAG
) && rf
.tags
.contains(refName
))
286 else if ((any
|| type
== BRANCH
) && rf
.branches
.contains(refName
))
289 else if ((any
|| type
== RMT_BRANCH
) && rf
.remoteBranches
.contains(refName
))
292 else if ((any
|| type
== REF
) && rf
.refs
.contains(refName
))
295 else if ((any
|| type
== APPLIED
|| type
== UN_APPLIED
) && rf
.stgitPatch
== refName
)
301 // if a ref was not found perhaps is an abbreviated form
303 errorReportingEnabled
= false;
304 bool ok
= run("git rev-parse --revs-only " + refName
, &runOutput
);
305 errorReportingEnabled
= true;
306 return (ok
? runOutput
.trimmed() : "");
309 void Git::appendNamesWithId(QStringList
& names
, SCRef sha
, SCList data
, bool onlyLoaded
) {
311 const Rev
* r
= revLookup(sha
);
312 if (onlyLoaded
&& !r
)
315 if (onlyLoaded
) { // prepare for later sorting
316 SCRef cap
= QString("%1 ").arg(r
->orderIdx
, 6);
317 FOREACH_SL (it
, data
)
318 names
.append(cap
+ *it
);
323 const QStringList
Git::getAllRefNames(uint mask
, bool onlyLoaded
) {
324 // returns reference names sorted by loading order if 'onlyLoaded' is set
327 FOREACH (RefMap
, it
, refsShaMap
) {
330 appendNamesWithId(names
, it
.key(), (*it
).tags
, onlyLoaded
);
333 appendNamesWithId(names
, it
.key(), (*it
).branches
, onlyLoaded
);
335 if (mask
& RMT_BRANCH
)
336 appendNamesWithId(names
, it
.key(), (*it
).remoteBranches
, onlyLoaded
);
339 appendNamesWithId(names
, it
.key(), (*it
).refs
, onlyLoaded
);
341 if ((mask
& (APPLIED
| UN_APPLIED
)) && !onlyLoaded
)
342 names
.append((*it
).stgitPatch
); // doesn't work with 'onlyLoaded'
346 QStringList::iterator
itN(names
.begin());
347 for ( ; itN
!= names
.end(); ++itN
) // strip 'idx'
348 (*itN
) = (*itN
).section(' ', -1, -1);
353 const QString
Git::getRevInfo(SCRef sha
) {
358 uint type
= checkRef(sha
);
364 const QString
cap(type
& CUR_BRANCH
? "HEAD: " : "Branch: ");
365 refsInfo
= cap
+ getRefName(sha
, BRANCH
).join(" ");
367 if (type
& RMT_BRANCH
)
368 refsInfo
.append(" Remote branch: " + getRefName(sha
, RMT_BRANCH
).join(" "));
371 refsInfo
.append(" Tag: " + getRefName(sha
, TAG
).join(" "));
374 refsInfo
.append(" Ref: " + getRefName(sha
, REF
).join(" "));
377 refsInfo
.append(" Patch: " + getRefName(sha
, APPLIED
).join(" "));
379 if (type
& UN_APPLIED
)
380 refsInfo
.append(" Patch: " + getRefName(sha
, UN_APPLIED
).join(" "));
383 SCRef
msg(getTagMsg(sha
));
385 refsInfo
.append(" [" + msg
+ "]");
387 return refsInfo
.trimmed();
390 const QString
Git::getTagMsg(SCRef sha
) {
392 if (!checkRef(sha
, TAG
)) {
393 dbs("ASSERT in Git::getTagMsg, tag not found");
396 Reference
& rf
= refsShaMap
[toTempSha(sha
)];
398 if (!rf
.tagMsg
.isEmpty())
401 QRegExp
pgp("-----BEGIN PGP SIGNATURE*END PGP SIGNATURE-----",
402 Qt::CaseSensitive
, QRegExp::Wildcard
);
404 if (!rf
.tagObj
.isEmpty()) {
406 if (run("git cat-file tag " + rf
.tagObj
, &ro
))
407 rf
.tagMsg
= ro
.section("\n\n", 1).remove(pgp
).trimmed();
412 bool Git::isPatchName(SCRef nm
) {
414 if (!getRefSha(nm
, UN_APPLIED
, false).isEmpty())
417 return !getRefSha(nm
, APPLIED
, false).isEmpty();
420 void Git::addExtraFileInfo(QString
* rowName
, SCRef sha
, SCRef diffToSha
, bool allMergeFiles
) {
422 const RevFile
* files
= getFiles(sha
, diffToSha
, allMergeFiles
);
426 int idx
= findFileIndex(*files
, *rowName
);
430 QString
extSt(files
->extendedStatus(idx
));
437 void Git::removeExtraFileInfo(QString
* rowName
) {
439 if (rowName
->contains(" --> ")) // return destination file name
440 *rowName
= rowName
->section(" --> ", 1, 1).section(" (", 0, 0);
443 void Git::formatPatchFileHeader(QString
* rowName
, SCRef sha
, SCRef diffToSha
,
444 bool combined
, bool allMergeFiles
) {
446 rowName
->prepend("diff --combined ");
447 return; // TODO rename/copy still not supported in this case
449 // let's see if it's a rename/copy...
450 addExtraFileInfo(rowName
, sha
, diffToSha
, allMergeFiles
);
452 if (rowName
->contains(" --> ")) { // ...it is!
454 SCRef
destFile(rowName
->section(" --> ", 1, 1).section(" (", 0, 0));
455 SCRef
origFile(rowName
->section(" --> ", 0, 0));
456 *rowName
= "diff --git a/" + origFile
+ " b/" + destFile
;
458 *rowName
= "diff --git a/" + *rowName
+ " b/" + *rowName
;
461 Annotate
* Git::startAnnotate(FileHistory
* fh
, QObject
* guiObj
) { // non blocking
463 Annotate
* ann
= new Annotate(this, guiObj
);
464 if (!ann
->start(fh
)) // non blocking call
465 return NULL
; // ann will delete itself when done
467 return ann
; // caller will delete with Git::cancelAnnotate()
470 void Git::cancelAnnotate(Annotate
* ann
) {
473 ann
->deleteWhenDone();
476 const FileAnnotation
* Git::lookupAnnotation(Annotate
* ann
, SCRef sha
) {
478 return (ann
? ann
->lookupAnnotation(sha
) : NULL
);
481 void Git::cancelDataLoading(const FileHistory
* fh
) {
482 // normally called when closing file viewer
484 emit
cancelLoading(fh
); // non blocking
487 const Rev
* Git::revLookup(SCRef sha
, const FileHistory
* fh
) const {
489 return revLookup(toTempSha(sha
), fh
);
492 const Rev
* Git::revLookup(const ShaString
& sha
, const FileHistory
* fh
) const {
494 const RevMap
& r
= (fh
? fh
->revs
: revData
->revs
);
495 return (sha
.latin1() ? r
.value(sha
) : NULL
);
498 bool Git::run(SCRef runCmd
, QString
* runOutput
, QObject
* receiver
, SCRef buf
) {
501 bool ret
= run(runOutput
? &ba
: NULL
, runCmd
, receiver
, buf
);
508 bool Git::run(QByteArray
* runOutput
, SCRef runCmd
, QObject
* receiver
, SCRef buf
) {
510 MyProcess
p(parent(), this, workDir
, errorReportingEnabled
);
511 return p
.runSync(runCmd
, runOutput
, receiver
, buf
);
514 MyProcess
* Git::runAsync(SCRef runCmd
, QObject
* receiver
, SCRef buf
) {
516 MyProcess
* p
= new MyProcess(parent(), this, workDir
, errorReportingEnabled
);
517 if (!p
->runAsync(runCmd
, receiver
, buf
)) {
521 return p
; // auto-deleted when done
524 MyProcess
* Git::runAsScript(SCRef runCmd
, QObject
* receiver
, SCRef buf
) {
526 const QString
scriptFile(workDir
+ "/qgit_script" + QGit::SCRIPT_EXT
);
528 // without this process doesn't start under Linux
529 QString
cmd(runCmd
.startsWith("#!") ? runCmd
: "#!/bin/sh\n" + runCmd
);
533 if (!writeToFile(scriptFile
, cmd
, true))
536 MyProcess
* p
= runAsync(scriptFile
, receiver
, buf
);
538 connect(p
, SIGNAL(eof()), this, SLOT(on_runAsScript_eof()));
542 void Git::on_runAsScript_eof() {
545 dir
.remove("qgit_script" + QGit::SCRIPT_EXT
);
548 void Git::cancelProcess(MyProcess
* p
) {
551 p
->on_cancel(); // non blocking call
554 int Git::findFileIndex(const RevFile
& rf
, SCRef name
) {
559 int idx
= name
.lastIndexOf('/') + 1;
560 SCRef dr
= name
.left(idx
);
561 SCRef nm
= name
.mid(idx
);
563 for (uint i
= 0, cnt
= rf
.count(); i
< cnt
; ++i
) {
564 if (fileNamesVec
[rf
.nameAt(i
)] == nm
&& dirNamesVec
[rf
.dirAt(i
)] == dr
)
570 const QString
Git::getLaneParent(SCRef fromSHA
, int laneNum
) {
572 const Rev
* rs
= revLookup(fromSHA
);
576 for (int idx
= rs
->orderIdx
- 1; idx
>= 0; idx
--) {
578 const Rev
* r
= revLookup(revData
->revOrder
[idx
]);
579 if (laneNum
>= r
->lanes
.count())
582 if (!isFreeLane(r
->lanes
[laneNum
])) {
584 int type
= r
->lanes
[laneNum
], parNum
= 0;
585 while (!isMerge(type
) && type
!= ACTIVE
) {
590 type
= r
->lanes
[--laneNum
];
592 return r
->parent(parNum
);
598 const QStringList
Git::getChildren(SCRef parent
) {
600 QStringList children
;
601 const Rev
* r
= revLookup(parent
);
605 for (int i
= 0; i
< r
->children
.count(); i
++)
606 children
.append(revData
->revOrder
[r
->children
[i
]]);
608 // reorder children by loading order
609 QStringList::iterator
itC(children
.begin());
610 for ( ; itC
!= children
.end(); ++itC
) {
611 const Rev
* r
= revLookup(*itC
);
612 (*itC
).prepend(QString("%1 ").arg(r
->orderIdx
, 6));
615 for (itC
= children
.begin(); itC
!= children
.end(); ++itC
)
616 (*itC
) = (*itC
).section(' ', -1, -1);
621 const QString
Git::getShortLog(SCRef sha
) {
623 const Rev
* r
= revLookup(sha
);
624 return (r
? r
->shortLog() : "");
627 MyProcess
* Git::getDiff(SCRef sha
, QObject
* receiver
, SCRef diffToSha
, bool combined
) {
633 if (sha
!= ZERO_SHA
) {
634 runCmd
= "git diff-tree --no-color -r --patch-with-stat ";
635 runCmd
.append(combined
? "-c " : "-C -m "); // TODO rename for combined
636 runCmd
.append(diffToSha
+ " " + sha
); // diffToSha could be empty
638 runCmd
= "git diff-index --no-color -r -m --patch-with-stat HEAD";
640 return runAsync(runCmd
, receiver
);
643 const QString
Git::getWorkDirDiff(SCRef fileName
) {
645 QString
runCmd("git diff-index --no-color -r -z -m -p --full-index --no-commit-id HEAD"), runOutput
;
646 if (!fileName
.isEmpty())
647 runCmd
.append(" -- " + quote(fileName
));
649 if (!run(runCmd
, &runOutput
))
652 /* For unknown reasons file sha of index is not ZERO_SHA but
653 a value of unknown origin.
654 Replace that with ZERO_SHA so to not fool annotate
656 int idx
= runOutput
.indexOf("..");
658 runOutput
.replace(idx
+ 2, 40, ZERO_SHA
);
663 const QString
Git::getFileSha(SCRef file
, SCRef revSha
) {
665 if (revSha
== ZERO_SHA
) {
666 QStringList files
, dummy
;
667 getWorkDirFiles(files
, dummy
, RevFile::ANY
);
668 if (files
.contains(file
))
669 return ZERO_SHA
; // it is unknown to git
671 const QString
sha(revSha
== ZERO_SHA
? "HEAD" : revSha
);
672 QString
runCmd("git ls-tree -r " + sha
+ " " + quote(file
)), runOutput
;
673 if (!run(runCmd
, &runOutput
))
676 return runOutput
.mid(12, 40); // could be empty, deleted file case
679 MyProcess
* Git::getFile(SCRef fileSha
, QObject
* receiver
, QByteArray
* result
, SCRef fileName
) {
683 symlinks in git are one line files with just the name of the target,
684 not the target content. Instead 'cat' command resolves symlinks and
685 returns target content. So we use 'cat' only if the file is modified
686 in working directory, to let annotation work for changed files, otherwise
687 we go with a safe 'git cat-file blob HEAD' instead.
688 NOTE: This fails if the modified file is a new symlink, converted
689 from an old plain file. In this case annotation will fail until
692 if (fileSha
== ZERO_SHA
)
696 QString winPath
= quote(fileName
);
697 winPath
.replace("/", "\\");
698 runCmd
= "type " + winPath
;
701 runCmd
= "cat " + quote(fileName
);
705 if (fileSha
.isEmpty()) // deleted
706 runCmd
= "git diff-tree HEAD HEAD"; // fake an empty file reading
708 runCmd
= "git cat-file blob " + fileSha
;
712 return NULL
; // in case of sync call we ignore run() return value
714 return runAsync(runCmd
, receiver
);
717 MyProcess
* Git::getHighlightedFile(SCRef fileSha
, QObject
* receiver
, QString
* result
, SCRef fileName
) {
719 if (!isTextHighlighter()) {
720 dbs("ASSERT in getHighlightedFile: highlighter not found");
723 QString
ext(fileName
.section('.', -1, -1, QString::SectionIncludeLeadingSep
));
724 QString
inputFile(workDir
+ "/qgit_hlght_input" + ext
);
725 if (!saveFile(fileSha
, fileName
, inputFile
))
728 QString
runCmd("source-highlight --failsafe -f html -i " + quote(inputFile
));
732 on_getHighlightedFile_eof();
733 return NULL
; // in case of sync call we ignore run() return value
735 MyProcess
* p
= runAsync(runCmd
, receiver
);
737 connect(p
, SIGNAL(eof()), this, SLOT(on_getHighlightedFile_eof()));
741 void Git::on_getHighlightedFile_eof() {
744 const QStringList
sl(dir
.entryList(QStringList() << "qgit_hlght_input*"));
749 bool Git::saveFile(SCRef fileSha
, SCRef fileName
, SCRef path
) {
752 getFile(fileSha
, NULL
, &fileData
, fileName
); // sync call
753 if (isBinaryFile(fileName
))
754 return writeToFile(path
, fileData
);
756 return writeToFile(path
, QString(fileData
));
759 bool Git::getTree(SCRef treeSha
, TreeInfo
& ti
, bool isWorkingDir
, SCRef path
) {
764 // retrieve unknown and deleted files under path
765 QStringList unknowns
, dummy
;
766 getWorkDirFiles(unknowns
, dummy
, RevFile::UNKNOWN
);
768 FOREACH_SL (it
, unknowns
) {
770 // don't add files under other directories
772 SCRef
d(f
.dir().path());
774 if (d
== path
|| (path
.isEmpty() && d
== ".")) {
775 TreeEntry
te(f
.fileName(), "", "?");
779 getWorkDirFiles(deleted
, dummy
, RevFile::DELETED
);
781 // if needed fake a working directory tree starting from HEAD tree
782 QString runOutput
, tree(treeSha
);
783 if (treeSha
== ZERO_SHA
) {
784 // HEAD could be empty for just init'ed repositories
785 if (!run("git rev-parse --revs-only HEAD", &tree
))
788 tree
= tree
.trimmed();
790 if (!tree
.isEmpty() && !run("git ls-tree " + tree
, &runOutput
))
793 const QStringList
sl(runOutput
.split('\n', QString::SkipEmptyParts
));
794 FOREACH_SL (it
, sl
) {
796 // append any not deleted file
797 SCRef
fn((*it
).section('\t', 1, 1));
798 SCRef
fp(path
.isEmpty() ? fn
: path
+ '/' + fn
);
800 if (deleted
.empty() || (deleted
.indexOf(fp
) == -1)) {
801 TreeEntry
te(fn
, (*it
).mid(12, 40), (*it
).mid(7, 4));
805 qSort(ti
); // list directories before files
809 void Git::getWorkDirFiles(SList files
, SList dirs
, RevFile::StatusFlag status
) {
813 const RevFile
* f
= getFiles(ZERO_SHA
);
817 for (int i
= 0; i
< f
->count(); i
++) {
819 if (f
->statusCmp(i
, status
)) {
821 SCRef
fp(filePath(*f
, i
));
823 for (int j
= 0, cnt
= fp
.count('/'); j
< cnt
; j
++) {
825 SCRef
dir(fp
.section('/', 0, j
));
826 if (dirs
.indexOf(dir
) == -1)
833 bool Git::isNothingToCommit() {
835 if (!revsFiles
.contains(ZERO_SHA_RAW
))
838 const RevFile
* rf
= revsFiles
[ZERO_SHA_RAW
];
839 return (rf
->count() == workingDirInfo
.otherFiles
.count());
842 bool Git::isTreeModified(SCRef sha
) {
844 const RevFile
* f
= getFiles(sha
);
846 return true; // no files info, stay on the safe side
848 for (int i
= 0; i
< f
->count(); ++i
)
849 if (!f
->statusCmp(i
, RevFile::MODIFIED
))
855 bool Git::isParentOf(SCRef par
, SCRef child
) {
857 const Rev
* c
= revLookup(child
);
858 return (c
&& c
->parentsCount() == 1 && QString(c
->parent(0)) == par
); // no merges
861 bool Git::isSameFiles(SCRef tree1Sha
, SCRef tree2Sha
) {
863 // early skip common case of browsing with up and down arrows, i.e.
864 // going from parent(child) to child(parent). In this case we can
865 // check RevFileMap and skip a costly 'git diff-tree' call.
866 if (isParentOf(tree1Sha
, tree2Sha
))
867 return !isTreeModified(tree2Sha
);
869 if (isParentOf(tree2Sha
, tree1Sha
))
870 return !isTreeModified(tree1Sha
);
872 const QString
runCmd("git diff-tree --no-color -r " + tree1Sha
+ " " + tree2Sha
);
874 if (!run(runCmd
, &runOutput
))
877 bool isChanged
= (runOutput
.indexOf(" A\t") != -1 || runOutput
.indexOf(" D\t") != -1);
881 const QStringList
Git::getDescendantBranches(SCRef sha
, bool shaOnly
) {
884 const Rev
* r
= revLookup(sha
);
885 if (!r
|| (r
->descBrnMaster
== -1))
888 const QVector
<int>& nr
= revLookup(revData
->revOrder
[r
->descBrnMaster
])->descBranches
;
890 for (int i
= 0; i
< nr
.count(); i
++) {
892 const ShaString
& sha
= revData
->revOrder
[nr
[i
]];
897 SCRef cap
= " (" + sha
+ ") ";
898 RefMap::const_iterator
it(refsShaMap
.find(sha
));
899 if (it
== refsShaMap
.constEnd())
902 if (!(*it
).branches
.empty())
903 tl
.append((*it
).branches
.join(" ").append(cap
));
905 if (!(*it
).remoteBranches
.empty())
906 tl
.append((*it
).remoteBranches
.join(" ").append(cap
));
911 const QStringList
Git::getNearTags(bool goDown
, SCRef sha
) {
914 const Rev
* r
= revLookup(sha
);
918 int nearRefsMaster
= (goDown
? r
->descRefsMaster
: r
->ancRefsMaster
);
919 if (nearRefsMaster
== -1)
922 const QVector
<int>& nr
= goDown
? revLookup(revData
->revOrder
[nearRefsMaster
])->descRefs
:
923 revLookup(revData
->revOrder
[nearRefsMaster
])->ancRefs
;
925 for (int i
= 0; i
< nr
.count(); i
++) {
927 const ShaString
& sha
= revData
->revOrder
[nr
[i
]];
928 SCRef cap
= " (" + sha
+ ")";
929 RefMap::const_iterator
it(refsShaMap
.find(sha
));
930 if (it
!= refsShaMap
.constEnd())
931 tl
.append((*it
).tags
.join(cap
).append(cap
));
936 const QString
Git::getLastCommitMsg() {
938 // FIXME: Make sure the amend action is not called when there is
939 // nothing to amend. That is in empty repository or over StGit stack
940 // with nothing applied.
943 if (run("git rev-parse --verify HEAD", &top
))
946 dbs("ASSERT: getLastCommitMsg head is not valid");
950 const Rev
* c
= revLookup(sha
);
952 dbp("ASSERT: getLastCommitMsg sha <%1> not found", sha
);
956 return c
->shortLog() + "\n\n" + c
->longLog().trimmed();
959 const QString
Git::getNewCommitMsg() {
961 const Rev
* c
= revLookup(ZERO_SHA
);
963 dbs("ASSERT: getNewCommitMsg zero_sha not found");
967 QString status
= c
->longLog();
968 status
.prepend('\n').replace(QRegExp("\\n([^#])"), "\n#\\1"); // comment all the lines
972 //CT TODO utility function; can go elsewhere
973 const QString
Git::colorMatch(SCRef txt
, QRegExp
& regExp
) {
975 QString text
= qt4and5escaping(txt
);
977 if (regExp
.isEmpty())
980 SCRef
startCol(QString::fromLatin1("<b><font color=\"red\">"));
981 SCRef
endCol(QString::fromLatin1("</font></b>"));
983 while ((pos
= text
.indexOf(regExp
, pos
)) != -1) {
985 SCRef
match(regExp
.cap(0));
986 const QString
coloredText(startCol
+ match
+ endCol
);
987 text
.replace(pos
, match
.length(), coloredText
);
988 pos
+= coloredText
.length();
993 //CT TODO utility function; can go elsewhere
994 const QString
Git::formatList(SCList sl
, SCRef name
, bool inOneLine
) {
999 QString ls
= "<tr><td class='h'>" + name
+ "</td><td>";
1000 const QString joinStr
= inOneLine
? ", " : "</td></tr>\n" + ls
;
1001 ls
+= sl
.join(joinStr
);
1002 ls
+= "</td></tr>\n";
1006 const QString
Git::getDesc(SCRef sha
, QRegExp
& shortLogRE
, QRegExp
& longLogRE
,
1007 bool showHeader
, FileHistory
* fh
) {
1012 const Rev
* c
= revLookup(sha
, fh
);
1013 if (!c
) // sha of a not loaded revision, as
1014 return ""; // example asked from file history
1018 text
= Qt::convertFromPlainText(c
->longLog());
1020 QTextStream
ts(&text
);
1021 ts
<< "<html><head><style type=\"text/css\">"
1022 "tr.head { background-color: " << QPalette().color(QPalette::Mid
).name() << " }\n"
1023 "td.h { font-weight: bold; }\n"
1024 "table { background-color: " << QPalette().color(QPalette::Button
).name() << "; }\n"
1025 "span.h { font-weight: bold; font-size: medium; }\n"
1026 "div.l { white-space: pre; "
1027 "font-family: " << TYPE_WRITER_FONT
.family() << ";"
1028 "font-size: " << TYPE_WRITER_FONT
.pointSize() << "pt;}\n"
1029 "</style></head><body><div class='t'>\n"
1030 "<table border=0 cellspacing=0 cellpadding=2>";
1032 ts
<< "<tr class='head'> <th></th> <th><span class='h'>"
1033 << colorMatch(c
->shortLog(), shortLogRE
)
1034 << "</span></th></tr>";
1037 if (c
->committer() != c
->author())
1038 ts
<< formatList(QStringList(qt4and5escaping(c
->committer())), "Committer");
1039 ts
<< formatList(QStringList(qt4and5escaping(c
->author())), "Author");
1040 ts
<< formatList(QStringList(getLocalDate(c
->authorDate())), " Author date");
1042 if (c
->isUnApplied
|| c
->isApplied
) {
1044 QStringList
patches(getRefName(sha
, APPLIED
));
1045 patches
+= getRefName(sha
, UN_APPLIED
);
1046 ts
<< formatList(patches
, "Patch");
1048 ts
<< formatList(c
->parents(), "Parent", false);
1049 ts
<< formatList(getChildren(sha
), "Child", false);
1050 ts
<< formatList(getDescendantBranches(sha
), "Branch", false);
1051 ts
<< formatList(getNearTags(!optGoDown
, sha
), "Follows");
1052 ts
<< formatList(getNearTags(optGoDown
, sha
), "Precedes");
1055 QString
longLog(c
->longLog());
1057 longLog
.prepend(QString("\n") + c
->shortLog() + "\n");
1060 QString
log(colorMatch(longLog
, longLogRE
));
1061 log
.replace("\n", "\n ").prepend('\n');
1062 ts
<< "</table></div><div class='l'>" << log
<< "</div></body></html>";
1066 // added to commit logs, we avoid to call git rev-parse for a possible abbreviated
1067 // sha if there isn't a leading trailing space or an open parenthesis and,
1068 // in that case, before the space must not be a ':' character.
1069 // It's an ugly heuristic, but seems to work in most cases.
1070 QRegExp
reSHA("..[0-9a-f]{21,40}|[^:][\\s(][0-9a-f]{6,20}", Qt::CaseInsensitive
);
1071 reSHA
.setMinimal(false);
1073 while ((pos
= text
.indexOf(reSHA
, pos
)) != -1) {
1075 SCRef ref
= reSHA
.cap(0).mid(2);
1076 const Rev
* r
= (ref
.length() == 40 ? revLookup(ref
) : revLookup(getRefSha(ref
)));
1077 if (r
&& r
->sha() != ZERO_SHA_RAW
) {
1078 QString
slog(r
->shortLog());
1079 if (slog
.isEmpty()) // very rare but possible
1081 if (slog
.length() > 60)
1082 slog
= slog
.left(57).trimmed().append("...");
1084 const QString
link("<a href=\"" + r
->sha() + "\">" + slog
.toHtmlEscaped() + "</a>");
1085 text
.replace(pos
+ 2, ref
.length(), link
);
1086 pos
+= link
.length();
1088 pos
+= reSHA
.cap(0).length();
1093 const RevFile
* Git::insertNewFiles(SCRef sha
, SCRef data
) {
1095 /* we use an independent FileNamesLoader to avoid data
1096 * corruption if we are loading file names in background
1100 RevFile
* rf
= new RevFile();
1101 parseDiffFormat(*rf
, data
, fl
);
1104 revsFiles
.insert(toPersistentSha(sha
, revsFilesShaBackupBuf
), rf
);
1108 bool Git::runDiffTreeWithRenameDetection(SCRef runCmd
, QString
* runOutput
) {
1109 /* Under some cases git could warn out:
1111 "too many files, skipping inexact rename detection"
1113 So if this occurs fallback on NO rename detection.
1115 QString
cmd(runCmd
); // runCmd must be without -C option
1116 cmd
.replace("git diff-tree", "git diff-tree -C");
1118 errorReportingEnabled
= false;
1119 bool renameDetectionOk
= run(cmd
, runOutput
);
1120 errorReportingEnabled
= true;
1122 if (!renameDetectionOk
) // retry without rename detection
1123 return run(runCmd
, runOutput
);
1128 const RevFile
* Git::getAllMergeFiles(const Rev
* r
) {
1130 SCRef
mySha(ALL_MERGE_FILES
+ r
->sha());
1131 if (revsFiles
.contains(toTempSha(mySha
)))
1132 return revsFiles
[toTempSha(mySha
)];
1134 EM_PROCESS_EVENTS
; // 'git diff-tree' could be slow
1136 QString
runCmd("git diff-tree --no-color -r -m " + r
->sha()), runOutput
;
1137 if (!runDiffTreeWithRenameDetection(runCmd
, &runOutput
))
1140 return insertNewFiles(mySha
, runOutput
);
1143 const RevFile
* Git::getFiles(SCRef sha
, SCRef diffToSha
, bool allFiles
, SCRef path
) {
1145 const Rev
* r
= revLookup(sha
);
1149 if (r
->parentsCount() == 0) // skip initial rev
1152 if (r
->parentsCount() > 1 && diffToSha
.isEmpty() && allFiles
)
1153 return getAllMergeFiles(r
);
1155 if (!diffToSha
.isEmpty() && (sha
!= ZERO_SHA
)) {
1157 QString
runCmd("git diff-tree --no-color -r -m ");
1158 runCmd
.append(diffToSha
+ " " + sha
);
1159 if (!path
.isEmpty())
1160 runCmd
.append(" " + path
);
1162 EM_PROCESS_EVENTS
; // 'git diff-tree' could be slow
1165 if (!runDiffTreeWithRenameDetection(runCmd
, &runOutput
))
1168 // we insert a dummy revision file object. It will be
1169 // overwritten at each request but we don't care.
1170 return insertNewFiles(CUSTOM_SHA
, runOutput
);
1172 if (revsFiles
.contains(r
->sha()))
1173 return revsFiles
[r
->sha()]; // ZERO_SHA search arrives here
1175 if (sha
== ZERO_SHA
) {
1176 dbs("ASSERT in Git::getFiles, ZERO_SHA not found");
1180 EM_PROCESS_EVENTS
; // 'git diff-tree' could be slow
1182 QString
runCmd("git diff-tree --no-color -r -c " + sha
), runOutput
;
1183 if (!runDiffTreeWithRenameDetection(runCmd
, &runOutput
))
1186 if (revsFiles
.contains(r
->sha())) // has been created in the mean time?
1187 return revsFiles
[r
->sha()];
1189 cacheNeedsUpdate
= true;
1190 return insertNewFiles(sha
, runOutput
);
1193 bool Git::startFileHistory(SCRef sha
, SCRef startingFileName
, FileHistory
* fh
) {
1195 QStringList
args(getDescendantBranches(sha
, true));
1199 QString newestFileName
= getNewestFileName(args
, startingFileName
);
1200 fh
->resetFileNames(newestFileName
);
1202 args
.clear(); // load history from all the branches
1203 args
<< getAllRefSha(BRANCH
| RMT_BRANCH
);
1205 args
<< "--" << newestFileName
;
1206 return startRevList(args
, fh
);
1209 const QString
Git::getNewestFileName(SCList branches
, SCRef fileName
) {
1211 QString
curFileName(fileName
), runOutput
, args
;
1213 args
= branches
.join(" ") + " -- " + curFileName
;
1214 if (!run("git ls-tree " + args
, &runOutput
))
1217 if (!runOutput
.isEmpty())
1220 QString
msg("Retrieving file renames, now at '" + curFileName
+ "'...");
1221 QApplication::postEvent(parent(), new MessageEvent(msg
));
1222 EM_PROCESS_EVENTS_NO_INPUT
;
1224 if (!run("git rev-list -n1 " + args
, &runOutput
))
1227 if (runOutput
.isEmpty()) // try harder
1228 if (!run("git rev-list --full-history -n1 " + args
, &runOutput
))
1231 if (runOutput
.isEmpty())
1234 SCRef sha
= runOutput
.trimmed();
1236 if (!populateRenamedPatches(sha
, QStringList(curFileName
), NULL
, &newCur
, true))
1239 curFileName
= newCur
.first();
1244 void Git::getFileFilter(SCRef path
, ShaSet
& shaSet
) const {
1247 QRegExp
rx(path
, Qt::CaseInsensitive
, QRegExp::Wildcard
);
1248 FOREACH (ShaVect
, it
, revData
->revOrder
) {
1250 if (!revsFiles
.contains(*it
))
1253 // case insensitive, wildcard search
1254 const RevFile
* rf
= revsFiles
[*it
];
1255 for (int i
= 0; i
< rf
->count(); ++i
)
1256 if (filePath(*rf
, i
).contains(rx
)) {
1263 bool Git::getPatchFilter(SCRef exp
, bool isRegExp
, ShaSet
& shaSet
) {
1267 FOREACH (ShaVect
, it
, revData
->revOrder
)
1268 if (*it
!= ZERO_SHA_RAW
)
1269 buf
.append(*it
).append('\n');
1274 EM_PROCESS_EVENTS
; // 'git diff-tree' could be slow
1276 QString
runCmd("git diff-tree --no-color -r -s --stdin "), runOutput
;
1278 runCmd
.append("--pickaxe-regex ");
1280 runCmd
.append(quote("-S" + exp
));
1281 if (!run(runCmd
, &runOutput
, NULL
, buf
))
1284 const QStringList
sl(runOutput
.split('\n', QString::SkipEmptyParts
));
1291 bool Git::resetCommits(int parentDepth
) {
1293 QString
runCmd("git reset --soft HEAD~");
1294 runCmd
.append(QString::number(parentDepth
));
1298 bool Git::applyPatchFile(SCRef patchPath
, bool fold
, bool isDragDrop
) {
1302 bool ok
= run("stg fold " + quote(patchPath
)); // merge in working directory
1304 ok
= run("stg refresh"); // update top patch
1307 return run("stg import --mail " + quote(patchPath
));
1309 QString
runCmd("git am --utf8 --3way ");
1312 const QString
APOpt(settings
.value(AM_P_OPT_KEY
).toString());
1313 if (!APOpt
.isEmpty())
1314 runCmd
.append(APOpt
.trimmed() + " ");
1317 runCmd
.append("--keep ");
1319 if (testFlag(SIGN_PATCH_F
))
1320 runCmd
.append("--signoff ");
1322 return run(runCmd
+ quote(patchPath
));
1325 const QStringList
Git::sortShaListByIndex(SCList shaList
) {
1327 QStringList orderedShaList
;
1328 FOREACH_SL (it
, shaList
)
1329 appendNamesWithId(orderedShaList
, *it
, QStringList(*it
), true);
1331 orderedShaList
.sort();
1332 QStringList::iterator
itN(orderedShaList
.begin());
1333 for ( ; itN
!= orderedShaList
.end(); ++itN
) // strip 'idx'
1334 (*itN
) = (*itN
).section(' ', -1, -1);
1336 return orderedShaList
;
1339 bool Git::formatPatch(SCList shaList
, SCRef dirPath
, SCRef remoteDir
) {
1341 bool remote
= !remoteDir
.isEmpty();
1343 const QString
FPOpt(settings
.value(FMT_P_OPT_KEY
).toString());
1345 QString
runCmd("git format-patch --no-color");
1346 if (testFlag(NUMBERS_F
) && !remote
)
1347 runCmd
.append(" -n");
1350 runCmd
.append(" --keep-subject");
1352 runCmd
.append(" -o " + quote(dirPath
));
1353 if (!FPOpt
.isEmpty())
1354 runCmd
.append(" " + FPOpt
.trimmed());
1356 const QString
tmp(workDir
);
1358 workDir
= remoteDir
; // run() uses workDir value
1360 // shaList is ordered by newest to oldest
1361 runCmd
.append(" " + shaList
.last());
1362 runCmd
.append(QString::fromLatin1("^..") + shaList
.first());
1363 bool ret
= run(runCmd
);
1368 const QStringList
Git::getOtherFiles(SCList selFiles
, bool onlyInIndex
) {
1370 const RevFile
* files
= getFiles(ZERO_SHA
); // files != NULL
1371 QStringList notSelFiles
;
1372 for (int i
= 0; i
< files
->count(); ++i
) {
1373 SCRef fp
= filePath(*files
, i
);
1374 if (selFiles
.indexOf(fp
) == -1) { // not selected...
1375 if (!onlyInIndex
|| files
->statusCmp(i
, RevFile::IN_INDEX
))
1376 notSelFiles
.append(fp
);
1382 bool Git::updateIndex(SCList selFiles
) {
1384 const RevFile
* files
= getFiles(ZERO_SHA
); // files != NULL
1386 QStringList toAdd
, toRemove
;
1387 FOREACH_SL (it
, selFiles
) {
1388 int idx
= findFileIndex(*files
, *it
);
1389 if (files
->statusCmp(idx
, RevFile::DELETED
))
1394 if (!toRemove
.isEmpty() && !run("git rm --cached --ignore-unmatch -- " + quote(toRemove
)))
1397 if (!toAdd
.isEmpty() && !run("git add -- " + quote(toAdd
)))
1403 bool Git::commitFiles(SCList selFiles
, SCRef msg
, bool amend
) {
1405 const QString
msgFile(gitDir
+ "/qgit_cmt_msg.txt");
1406 if (!writeToFile(msgFile
, msg
)) // early skip
1409 // add user selectable commit options
1411 const QString
CMArgs(settings
.value(CMT_ARGS_KEY
).toString());
1414 if (!CMArgs
.isEmpty())
1415 cmtOptions
.append(" " + CMArgs
);
1417 if (testFlag(SIGN_CMT_F
))
1418 cmtOptions
.append(" -s");
1420 if (testFlag(VERIFY_CMT_F
))
1421 cmtOptions
.append(" -v");
1424 cmtOptions
.append(" --amend");
1428 // get not selected files but updated in index to restore at the end
1429 const QStringList
notSel(getOtherFiles(selFiles
, optOnlyInIndex
));
1431 // call git reset to remove not selected files from index
1432 if (!notSel
.empty() && !run("git reset -- " + quote(notSel
)))
1435 // update index with selected files
1436 if (!updateIndex(selFiles
))
1439 // now we can finally commit..
1440 if (!run("git commit" + cmtOptions
+ " -F " + quote(msgFile
)))
1443 // restore not selected files that were already in index
1444 if (!notSel
.empty() && !updateIndex(notSel
))
1450 dir
.remove(msgFile
);
1454 bool Git::mkPatchFromWorkDir(SCRef msg
, SCRef patchFile
, SCList files
) {
1456 /* unfortunately 'git diff' sees only files already
1457 * known to git or already in index, so update index first
1458 * to be sure also unknown files are correctly found
1460 if (!updateIndex(files
))
1464 if (!run("git diff --no-ext-diff -C HEAD -- " + quote(files
), &runOutput
))
1467 const QString
patch("Subject: " + msg
+ "\n---\n" + runOutput
);
1468 return writeToFile(patchFile
, patch
);
1471 bool Git::stgCommit(SCList selFiles
, SCRef msg
, SCRef patchName
, bool fold
) {
1473 /* Here the deal is to use 'stg import' and 'stg fold' to add a new
1474 * patch or refresh the current one respectively. Unfortunately refresh
1475 * does not work with partial selection of files and also does not take
1476 * patch message from a file that is needed to avoid artifacts with '\n'
1481 * - Create a patch file with the changes you want to import/fold in StGit
1482 * - Stash working directory files because import/fold wants a clean directory
1483 * - Import/fold the patch
1484 * - Unstash and merge working directory modified files
1485 * - Restore index with not selected files
1488 /* Step 1: Create a patch file with the changes you want to import/fold */
1490 const QString
patchFile(gitDir
+ "/qgit_tmp_patch.txt");
1492 // in case we don't have files to restore we can shortcut various commands
1493 bool partialSelection
= !getOtherFiles(selFiles
, !optOnlyInIndex
).isEmpty();
1495 // get not selected files but updated in index to restore at the end
1497 if (partialSelection
) // otherwise notSel is for sure empty
1498 notSel
= getOtherFiles(selFiles
, optOnlyInIndex
);
1500 // create a patch with diffs between working directory and HEAD
1501 if (!mkPatchFromWorkDir(msg
, patchFile
, selFiles
))
1504 /* Step 2: Stash working directory modified files */
1505 if (partialSelection
) {
1506 errorReportingEnabled
= false;
1507 run("git stash"); // unfortunately 'git stash' is noisy on stderr
1508 errorReportingEnabled
= true;
1511 /* Step 3: Call stg import/fold */
1513 // setup a clean state
1514 if (!run("stg status --reset"))
1515 goto fail_and_unstash
;
1518 // update patch message before to fold, note that
1519 // command 'stg edit' requires stg version 0.14 or later
1520 if (!msg
.isEmpty() && !run("stg edit --message " + quote(msg
.trimmed())))
1521 goto fail_and_unstash
;
1523 if (!run("stg fold " + quote(patchFile
)))
1524 goto fail_and_unstash
;
1526 if (!run("stg refresh")) // refresh needed after fold
1527 goto fail_and_unstash
;
1529 } else if (!run("stg import --mail --name " + quote(patchName
) + " " + quote(patchFile
)))
1530 goto fail_and_unstash
;
1532 if (partialSelection
) {
1534 /* Step 4: Unstash and merge working directory modified files */
1535 errorReportingEnabled
= false;
1536 run("git stash pop"); // unfortunately 'git stash' is noisy on stderr
1537 errorReportingEnabled
= true;
1539 /* Step 5: restore not selected files that were already in index */
1540 if (!notSel
.empty() && !updateIndex(notSel
))
1549 if (partialSelection
) {
1551 errorReportingEnabled
= false;
1552 run("git stash pop");
1553 errorReportingEnabled
= true;
1558 dir
.remove(patchFile
);
1562 bool Git::makeBranch(SCRef sha
, SCRef branchName
) {
1564 return run("git branch " + branchName
+ " " + sha
);
1567 bool Git::makeTag(SCRef sha
, SCRef tagName
, SCRef msg
) {
1570 return run("git tag " + tagName
+ " " + sha
);
1572 return run("git tag -m \"" + msg
+ "\" " + tagName
+ " " + sha
);
1575 bool Git::deleteTag(SCRef sha
) {
1577 const QStringList
tags(getRefName(sha
, TAG
));
1579 return run("git tag -d " + tags
.first()); // only one
1584 bool Git::stgPush(SCRef sha
) {
1586 const QStringList
patch(getRefName(sha
, UN_APPLIED
));
1587 if (patch
.count() != 1) {
1588 dbp("ASSERT in Git::stgPush, found %1 patches instead of 1", patch
.count());
1591 return run("stg push " + quote(patch
.first()));
1594 bool Git::stgPop(SCRef sha
) {
1596 const QStringList
patch(getRefName(sha
, APPLIED
));
1597 if (patch
.count() != 1) {
1598 dbp("ASSERT in Git::stgPop, found %1 patches instead of 1", patch
.count());
1601 return run("stg pop " + quote(patch
));
1605 //! cache for dates conversion. Common among qgit windows
1606 static QHash
<QString
, QString
> localDates
;
1608 * Accesses a cache that avoids slow date calculation
1611 * the reference from which we want to get the date
1614 * human-readable date
1616 const QString
Git::getLocalDate(SCRef gitDate
) {
1617 QString
localDate(localDates
.value(gitDate
));
1620 if (localDate
.isEmpty()) {
1622 d
.setTime_t(gitDate
.toUInt());
1623 localDate
= d
.toString(Qt::SystemLocaleShortDate
);
1626 localDates
[gitDate
] = localDate
;
1632 const QStringList
Git::getArgs(bool* quit
, bool repoChanged
) {
1635 QStringList arglist
= qApp
->arguments();
1637 // Remove first argument which is the path of the current executable
1638 arglist
.removeFirst();
1641 foreach (QString arg
, arglist
) {
1642 // in arguments with spaces double quotes
1643 // are stripped by Qt, so re-add them
1644 if (arg
.contains(' '))
1645 arg
.prepend('\"').append('\"');
1647 args
.append(arg
+ ' ');
1650 if (testFlag(RANGE_SELECT_F
) && (!startup
|| args
.isEmpty())) {
1652 RangeSelectImpl
rs((QWidget
*)parent(), &args
, repoChanged
, this);
1653 *quit
= (rs
.exec() == QDialog::Rejected
); // modal execution
1655 return QStringList();
1658 return MyProcess::splitArgList(args
);
1661 bool Git::getGitDBDir(SCRef wd
, QString
& gd
, bool& changed
) {
1662 // we could run from a subdirectory, so we need to get correct directories
1664 QString runOutput
, tmp(workDir
);
1666 errorReportingEnabled
= false;
1667 bool success
= run("git rev-parse --git-dir", &runOutput
); // run under newWorkDir
1668 errorReportingEnabled
= true;
1670 runOutput
= runOutput
.trimmed();
1672 // 'git rev-parse --git-dir' output could be a relative
1673 // to working directory (as ex .git) or an absolute path
1674 QDir
d(runOutput
.startsWith("/") ? runOutput
: wd
+ "/" + runOutput
);
1675 changed
= (d
.absolutePath() != gitDir
);
1676 gd
= d
.absolutePath();
1681 bool Git::getBaseDir(SCRef wd
, QString
& bd
, bool& changed
) {
1682 // we could run from a subdirectory, so we need to get correct directories
1684 // We use --show-cdup and not --git-dir for this, in order to take into account configurations
1685 // in which .git is indeed a "symlink", a text file containing the path of the actual .git database dir.
1686 // In that particular case, the parent directory of the one given by --git-dir is *not* necessarily
1687 // the base directory of the repository.
1689 QString runOutput
, tmp(workDir
);
1691 errorReportingEnabled
= false;
1692 bool success
= run("git rev-parse --show-cdup", &runOutput
); // run under newWorkDir
1693 errorReportingEnabled
= true;
1695 runOutput
= runOutput
.trimmed();
1697 // 'git rev-parse --show-cdup' is relative to working directory.
1698 QDir
d(wd
+ "/" + runOutput
);
1699 bd
= d
.absolutePath();
1700 changed
= (bd
!= workDir
);
1709 Git::Reference
* Git::lookupOrAddReference(const ShaString
& sha
) {
1710 RefMap::iterator
it(refsShaMap
.find(sha
));
1711 if (it
== refsShaMap
.end()) it
= refsShaMap
.insert(sha
, Reference());
1715 Git::Reference
* Git::lookupReference(const ShaString
& sha
) {
1716 RefMap::iterator
it(refsShaMap
.find(sha
));
1717 if (it
== refsShaMap
.end()) return 0;
1721 bool Git::getRefs() {
1723 // check for a StGIT stack
1725 QString stgCurBranch
;
1726 if (d
.exists("patches")) { // early skip
1727 errorReportingEnabled
= false;
1728 isStGIT
= run("stg branch", &stgCurBranch
); // slow command
1729 errorReportingEnabled
= true;
1730 stgCurBranch
= stgCurBranch
.trimmed();
1734 // check for a merge and read current branch sha
1735 isMergeHead
= d
.exists("MERGE_HEAD");
1736 QString curBranchSHA
, curBranchName
;
1737 if (!run("git rev-parse --revs-only HEAD", &curBranchSHA
))
1740 if (!run("git branch", &curBranchName
))
1743 curBranchSHA
= curBranchSHA
.trimmed();
1744 curBranchName
= curBranchName
.prepend('\n').section("\n*", 1);
1745 curBranchName
= curBranchName
.section('\n', 0, 0).trimmed();
1747 // read refs, normally unsorted
1749 if (!run("git show-ref -d", &runOutput
))
1753 shaBackupBuf
.clear(); // revs are already empty now
1756 QStringList patchNames
, patchShas
;
1757 const QStringList
rLst(runOutput
.split('\n', QString::SkipEmptyParts
));
1758 FOREACH_SL (it
, rLst
) {
1760 SCRef revSha
= (*it
).left(40);
1761 SCRef refName
= (*it
).mid(41);
1763 if (refName
.startsWith("refs/patches/")) {
1765 // save StGIT patch sha, to be used later
1766 SCRef
patchesDir("refs/patches/" + stgCurBranch
+ "/");
1767 if (refName
.startsWith(patchesDir
)) {
1768 patchNames
.append(refName
.mid(patchesDir
.length()));
1769 patchShas
.append(revSha
);
1771 // StGIT patches should not be added to refs,
1772 // but an applied StGIT patch could be also an head or
1773 // a tag in this case will be added in another loop cycle
1776 // one rev could have many tags
1777 Reference
* cur
= lookupOrAddReference(toPersistentSha(revSha
, shaBackupBuf
));
1779 if (refName
.startsWith("refs/tags/")) {
1781 if (refName
.endsWith("^{}")) { // tag dereference
1783 // we assume that a tag dereference follows strictly
1784 // the corresponding tag object in rLst. So the
1785 // last added tag is a tag object, not a commit object
1786 cur
->tags
.append(refName
.mid(10, refName
.length() - 13));
1788 // store tag object. Will be used to fetching
1789 // tag message (if any) when necessary.
1790 cur
->tagObj
= prevRefSha
;
1792 // tagObj must be removed from ref map
1793 if (!prevRefSha
.isEmpty())
1794 refsShaMap
.remove(toTempSha(prevRefSha
));
1797 cur
->tags
.append(refName
.mid(10));
1801 } else if (refName
.startsWith("refs/heads/")) {
1803 cur
->branches
.append(refName
.mid(11));
1804 cur
->type
|= BRANCH
;
1805 if (curBranchSHA
== revSha
) {
1806 cur
->type
|= CUR_BRANCH
;
1807 cur
->currentBranch
= curBranchName
;
1809 } else if (refName
.startsWith("refs/remotes/") && !refName
.endsWith("HEAD")) {
1811 cur
->remoteBranches
.append(refName
.mid(13));
1812 cur
->type
|= RMT_BRANCH
;
1814 } else if (!refName
.startsWith("refs/bases/") && !refName
.endsWith("HEAD")) {
1816 cur
->refs
.append(refName
);
1819 prevRefSha
= revSha
;
1821 if (isStGIT
&& !patchNames
.isEmpty())
1822 parseStGitPatches(patchNames
, patchShas
);
1824 return !refsShaMap
.empty();
1827 void Git::parseStGitPatches(SCList patchNames
, SCList patchShas
) {
1829 patchesStillToFind
= 0;
1831 // get patch names and status of current branch
1833 if (!run("stg series", &runOutput
))
1836 const QStringList
pl(runOutput
.split('\n', QString::SkipEmptyParts
));
1837 FOREACH_SL (it
, pl
) {
1839 SCRef status
= (*it
).left(1);
1840 SCRef patchName
= (*it
).mid(2);
1842 bool applied
= (status
== "+" || status
== ">");
1843 int pos
= patchNames
.indexOf(patchName
);
1845 dbp("ASSERT in Git::parseStGitPatches(), patch %1 "
1846 "not found in references list.", patchName
);
1849 const ShaString
& ss
= toPersistentSha(patchShas
.at(pos
), shaBackupBuf
);
1850 Reference
* cur
= lookupOrAddReference(ss
);
1851 cur
->stgitPatch
= patchName
;
1852 cur
->type
|= (applied
? APPLIED
: UN_APPLIED
);
1855 patchesStillToFind
++;
1859 const QStringList
Git::getOthersFiles() {
1860 // add files present in working directory but not in git archive
1862 QString
runCmd("git ls-files --others");
1864 QString
exFile(settings
.value(EX_KEY
, EX_DEF
).toString());
1865 if (!exFile
.isEmpty()) {
1866 QString path
= (exFile
.startsWith("/")) ? exFile
: workDir
+ "/" + exFile
;
1867 if (QFile::exists(path
))
1868 runCmd
.append(" --exclude-from=" + quote(exFile
));
1870 QString
exPerDir(settings
.value(EX_PER_DIR_KEY
, EX_PER_DIR_DEF
).toString());
1871 if (!exPerDir
.isEmpty())
1872 runCmd
.append(" --exclude-per-directory=" + quote(exPerDir
));
1875 run(runCmd
, &runOutput
);
1876 return runOutput
.split('\n', QString::SkipEmptyParts
);
1879 Rev
* Git::fakeRevData(SCRef sha
, SCList parents
, SCRef author
, SCRef date
, SCRef log
, SCRef longLog
,
1880 SCRef patch
, int idx
, FileHistory
* fh
) {
1882 QString
data('>' + sha
+ 'X' + parents
.join(" ") + " \n");
1883 data
.append(author
+ '\n' + author
+ '\n' + date
+ '\n');
1884 data
.append(log
+ '\n' + longLog
);
1886 QString
header("log size " + QString::number(QByteArray(data
.toLatin1()).length() - 1) + '\n');
1887 data
.prepend(header
);
1888 if (!patch
.isEmpty())
1889 data
.append('\n' + patch
);
1891 QByteArray
* ba
= new QByteArray(data
.toLatin1());
1894 fh
->rowData
.append(ba
);
1896 Rev
* c
= new Rev(*ba
, 0, idx
, &dummy
, !isMainHistory(fh
));
1900 const Rev
* Git::fakeWorkDirRev(SCRef parent
, SCRef log
, SCRef longLog
, int idx
, FileHistory
* fh
) {
1903 if (!isMainHistory(fh
))
1904 patch
= getWorkDirDiff(fh
->fileNames().first());
1906 QString
date(QString::number(QDateTime::currentDateTime().toTime_t()));
1907 QString
author("-");
1908 QStringList
parents(parent
);
1909 Rev
* c
= fakeRevData(ZERO_SHA
, parents
, author
, date
, log
, longLog
, patch
, idx
, fh
);
1910 c
->isDiffCache
= true;
1911 c
->lanes
.append(EMPTY
);
1915 const RevFile
* Git::fakeWorkDirRevFile(const WorkingDirInfo
& wd
) {
1918 RevFile
* rf
= new RevFile();
1919 parseDiffFormat(*rf
, wd
.diffIndex
, fl
);
1920 rf
->onlyModified
= false;
1922 FOREACH_SL (it
, wd
.otherFiles
) {
1924 appendFileName(*rf
, *it
, fl
);
1925 rf
->status
.append(RevFile::UNKNOWN
);
1926 rf
->mergeParent
.append(1);
1928 RevFile cachedFiles
;
1929 parseDiffFormat(cachedFiles
, wd
.diffIndexCached
, fl
);
1932 for (int i
= 0; i
< rf
->count(); i
++)
1933 if (findFileIndex(cachedFiles
, filePath(*rf
, i
)) != -1)
1934 rf
->status
[i
] |= RevFile::IN_INDEX
;
1938 void Git::getDiffIndex() {
1941 if (!run("git status", &status
)) // git status refreshes the index, run as first
1945 if (!run("git rev-parse --revs-only HEAD", &head
))
1948 head
= head
.trimmed();
1949 if (!head
.isEmpty()) { // repository initialized but still no history
1951 if (!run("git diff-index " + head
, &workingDirInfo
.diffIndex
))
1954 // check for files already updated in cache, we will
1955 // save this information in status third field
1956 if (!run("git diff-index --cached " + head
, &workingDirInfo
.diffIndexCached
))
1959 // get any file not in tree
1960 workingDirInfo
.otherFiles
= getOthersFiles();
1962 // now mockup a RevFile
1963 revsFiles
.insert(ZERO_SHA_RAW
, fakeWorkDirRevFile(workingDirInfo
));
1965 // then mockup the corresponding Rev
1966 SCRef log
= (isNothingToCommit() ? "Nothing to commit" : "Working directory changes");
1967 const Rev
* r
= fakeWorkDirRev(head
, log
, status
, revData
->revOrder
.count(), revData
);
1968 revData
->revs
.insert(ZERO_SHA_RAW
, r
);
1969 revData
->revOrder
.append(ZERO_SHA_RAW
);
1970 revData
->earlyOutputCntBase
= revData
->revOrder
.count();
1972 // finally send it to GUI
1973 emit
newRevsAdded(revData
, revData
->revOrder
);
1976 void Git::parseDiffFormatLine(RevFile
& rf
, SCRef line
, int parNum
, FileNamesLoader
& fl
) {
1978 if (line
[1] == ':') { // it's a combined merge
1980 /* For combined merges rename/copy information is useless
1981 * because nor the original file name, nor similarity info
1982 * is given, just the status tracks that in the left/right
1983 * branch a renamed/copy occurred (as example status could
1984 * be RM or MR). For visualization purposes we could consider
1985 * the file as modified
1987 appendFileName(rf
, line
.section('\t', -1), fl
);
1989 rf
.mergeParent
.append(parNum
);
1990 } else { // faster parsing in normal case
1992 if (line
.at(98) == '\t') {
1993 appendFileName(rf
, line
.mid(99), fl
);
1994 setStatus(rf
, line
.at(97));
1995 rf
.mergeParent
.append(parNum
);
1997 // it's a rename or a copy, we are not in fast path now!
1998 setExtStatus(rf
, line
.mid(97), parNum
, fl
);
2002 //CT TODO can go in RevFile
2003 void Git::setStatus(RevFile
& rf
, SCRef rowSt
) {
2005 char status
= rowSt
.at(0).toLatin1();
2010 rf
.status
.append(RevFile::MODIFIED
);
2013 rf
.status
.append(RevFile::DELETED
);
2014 rf
.onlyModified
= false;
2017 rf
.status
.append(RevFile::NEW
);
2018 rf
.onlyModified
= false;
2021 rf
.status
.append(RevFile::UNKNOWN
);
2022 rf
.onlyModified
= false;
2025 dbp("ASSERT in Git::setStatus, unknown status <%1>. "
2026 "'MODIFIED' will be used instead.", rowSt
);
2027 rf
.status
.append(RevFile::MODIFIED
);
2032 void Git::setExtStatus(RevFile
& rf
, SCRef rowSt
, int parNum
, FileNamesLoader
& fl
) {
2034 const QStringList
sl(rowSt
.split('\t', QString::SkipEmptyParts
));
2035 if (sl
.count() != 3) {
2036 dbp("ASSERT in setExtStatus, unexpected status string %1", rowSt
);
2039 // we want store extra info with format "orig --> dest (Rxx%)"
2040 // but git give us something like "Rxx\t<orig>\t<dest>"
2044 const QString
extStatusInfo(orig
+ " --> " + dest
+ " (" + type
+ "%)");
2047 NOTE: we set rf.extStatus size equal to position of latest
2048 copied/renamed file. So it can have size lower then
2049 rf.count() if after copied/renamed file there are
2050 others. Here we have no possibility to know final
2051 dimension of this RefFile. We are still in parsing.
2054 // simulate new file
2055 appendFileName(rf
, dest
, fl
);
2056 rf
.mergeParent
.append(parNum
);
2057 rf
.status
.append(RevFile::NEW
);
2058 rf
.extStatus
.resize(rf
.status
.size());
2059 rf
.extStatus
[rf
.status
.size() - 1] = extStatusInfo
;
2061 // simulate deleted orig file only in case of rename
2062 if (type
.at(0) == 'R') { // renamed file
2063 appendFileName(rf
, orig
, fl
);
2064 rf
.mergeParent
.append(parNum
);
2065 rf
.status
.append(RevFile::DELETED
);
2066 rf
.extStatus
.resize(rf
.status
.size());
2067 rf
.extStatus
[rf
.status
.size() - 1] = extStatusInfo
;
2069 rf
.onlyModified
= false;
2072 //CT TODO utility function; can go elsewhere
2073 void Git::parseDiffFormat(RevFile
& rf
, SCRef buf
, FileNamesLoader
& fl
) {
2075 int parNum
= 1, startPos
= 0, endPos
= buf
.indexOf('\n');
2076 while (endPos
!= -1) {
2078 SCRef line
= buf
.mid(startPos
, endPos
- startPos
);
2079 if (line
[0] == ':') // avoid sha's in merges output
2080 parseDiffFormatLine(rf
, line
, parNum
, fl
);
2084 startPos
= endPos
+ 1;
2085 endPos
= buf
.indexOf('\n', endPos
+ 99);
2089 bool Git::startParseProc(SCList initCmd
, FileHistory
* fh
, SCRef buf
) {
2091 DataLoader
* dl
= new DataLoader(this, fh
); // auto-deleted when done
2093 connect(this, SIGNAL(cancelLoading(const FileHistory
*)),
2094 dl
, SLOT(on_cancel(const FileHistory
*)));
2096 connect(dl
, SIGNAL(newDataReady(const FileHistory
*)),
2097 this, SLOT(on_newDataReady(const FileHistory
*)));
2099 connect(dl
, SIGNAL(loaded(FileHistory
*, ulong
, int,
2100 bool, const QString
&, const QString
&)), this,
2101 SLOT(on_loaded(FileHistory
*, ulong
, int,
2102 bool, const QString
&, const QString
&)));
2104 return dl
->start(initCmd
, workDir
, buf
);
2107 bool Git::startRevList(SCList args
, FileHistory
* fh
) {
2109 QString
baseCmd("git log --topo-order --no-color "
2112 "--log-size " // FIXME broken on Windows
2114 "--parents --boundary -z "
2115 "--pretty=format:%m%HX%PX%n%cn<%ce>%n%an<%ae>%n%at%n%s%n");
2117 // we don't need log message body for file history
2118 if (isMainHistory(fh
))
2119 baseCmd
.append("%b");
2121 QStringList
initCmd(baseCmd
.split(' '));
2122 if (!isMainHistory(fh
)) {
2124 NOTE: we don't use '--remove-empty' option because
2125 in case a file is deleted and then a new file with
2126 the same name is created again in the same directory
2127 then, with this option, file history is truncated to
2128 the file deletion revision.
2130 initCmd
<< QString("-r -m -p --full-index").split(' ');
2132 {} // initCmd << QString("--early-output"); currently disabled
2134 return startParseProc(initCmd
+ args
, fh
, QString());
2137 bool Git::startUnappliedList() {
2139 QStringList
unAppliedShaList(getAllRefSha(UN_APPLIED
));
2140 if (unAppliedShaList
.isEmpty())
2143 // WARNING: with this command 'git log' could send spurious
2144 // revs so we need some filter out logic during loading
2145 QString
cmd("git log --no-color --parents -z "
2148 "--log-size " // FIXME broken on Windows
2150 "--pretty=format:%m%HX%PX%n%an<%ae>%n%at%n%s%n%b ^HEAD");
2152 QStringList
sl(cmd
.split(' '));
2153 sl
<< unAppliedShaList
;
2154 return startParseProc(sl
, revData
, QString());
2157 void Git::stop(bool saveCache
) {
2158 // normally called when changing directory or closing
2160 EM_RAISE(exGitStopped
);
2162 // stop all data sending from process and asks them
2163 // to terminate. Note that process could still keep
2164 // running for a while although silently
2165 emit
cancelAllProcesses(); // non blocking
2167 // after cancelAllProcesses() procFinished() is not called anymore
2168 // TODO perhaps is better to call procFinished() also if process terminated
2169 // incorrectly as QProcess does. BUt first we need to fix FileView::on_loadCompleted()
2170 emit
fileNamesLoad(1, revsFiles
.count() - filesLoadingStartOfs
);
2172 if (cacheNeedsUpdate
&& saveCache
) {
2174 cacheNeedsUpdate
= false;
2175 if (!filesLoadingCurSha
.isEmpty()) // we are in the middle of a loading
2176 revsFiles
.remove(toTempSha(filesLoadingCurSha
)); // remove partial data
2178 if (!revsFiles
.isEmpty()) {
2179 SHOW_MSG("Saving cache. Please wait...");
2180 if (!Cache::save(gitDir
, revsFiles
, dirNamesVec
, fileNamesVec
))
2181 dbs("ERROR unable to save file names cache");
2186 void Git::clearRevs() {
2189 patchesStillToFind
= 0; // TODO TEST WITH FILTERING
2190 firstNonStGitPatch
= "";
2191 workingDirInfo
.clear();
2192 revsFiles
.remove(ZERO_SHA_RAW
);
2195 void Git::clearFileNames() {
2197 qDeleteAll(revsFiles
);
2199 fileNamesMap
.clear();
2200 dirNamesMap
.clear();
2201 dirNamesVec
.clear();
2202 fileNamesVec
.clear();
2203 revsFilesShaBackupBuf
.clear();
2204 cacheNeedsUpdate
= false;
2207 bool Git::init(SCRef wd
, bool askForRange
, const QStringList
* passedArgs
, bool overwriteArgs
, bool* quit
) {
2208 // normally called when changing git directory. Must be called after stop()
2213 /* we only update filtering info here, original arguments
2214 * are not overwritten. Only getArgs() can update arguments,
2215 * an exception is if flag overwriteArgs is set
2217 loadArguments
.filteredLoading
= (!overwriteArgs
&& passedArgs
!= NULL
);
2218 if (loadArguments
.filteredLoading
)
2219 loadArguments
.filterList
= *passedArgs
;
2221 if (overwriteArgs
) // in this case must be passedArgs != NULL
2222 loadArguments
.args
= *passedArgs
;
2225 setThrowOnStop(true);
2227 const QString
msg1("Path is '" + workDir
+ "' Loading ");
2229 // check if repository is valid
2231 isGIT
= getGitDBDir(wd
, gitDir
, repoChanged
);
2235 getBaseDir(wd
, workDir
, dummy
);
2238 fileCacheAccessed
= false;
2240 SHOW_MSG(msg1
+ "file names cache...");
2245 setThrowOnStop(false);
2250 // update text codec according to repo settings
2252 // QTextCodec::setCodecForCStrings(getTextCodec(&dummy));
2255 SHOW_MSG(msg1
+ "refs...");
2257 dbs("WARNING: no tags or heads found");
2259 // startup input range dialog
2261 if (startup
|| askForRange
) {
2262 loadArguments
.args
= getArgs(quit
, repoChanged
); // must be called with refs loaded
2264 setThrowOnStop(false);
2268 // load StGit unapplied patches, must be after getRefs()
2270 loadingUnAppliedPatches
= startUnappliedList();
2271 if (loadingUnAppliedPatches
) {
2273 SHOW_MSG(msg1
+ "StGIT unapplied patches...");
2274 setThrowOnStop(false);
2276 // we will continue with init2() at
2277 // the end of loading...
2283 setThrowOnStop(false);
2288 setThrowOnStop(false);
2290 if (isThrowOnStopRaised(i
, "initializing 1")) {
2294 const QString
info("Exception \'" + EM_DESC(i
) + "\' "
2295 "not handled in init...re-throw");
2303 const QString
msg1("Path is '" + workDir
+ "' Loading ");
2305 // after loading unapplied patch update base early output offset to
2306 // avoid losing unapplied patches at first early output event
2308 revData
->earlyOutputCntBase
= revData
->revOrder
.count();
2311 setThrowOnStop(true);
2313 // load working directory files
2314 if (!loadArguments
.filteredLoading
&& testFlag(DIFF_INDEX_F
)) {
2315 SHOW_MSG(msg1
+ "working directory changed files...");
2316 getDiffIndex(); // blocking, we could be in setRepository() now
2318 SHOW_MSG(msg1
+ "revisions...");
2320 // build up command line arguments
2321 QStringList
args(loadArguments
.args
);
2322 if (loadArguments
.filteredLoading
) {
2323 if (!args
.contains("--"))
2326 args
<< loadArguments
.filterList
;
2328 if (!startRevList(args
, revData
))
2329 SHOW_MSG("ERROR: unable to start 'git log'");
2331 setThrowOnStop(false);
2335 setThrowOnStop(false);
2337 if (isThrowOnStopRaised(i
, "initializing 2")) {
2341 const QString
info("Exception \'" + EM_DESC(i
) + "\' "
2342 "not handled in init2...re-throw");
2348 void Git::on_newDataReady(const FileHistory
* fh
) {
2350 emit
newRevsAdded(fh
, fh
->revOrder
);
2353 void Git::on_loaded(FileHistory
* fh
, ulong byteSize
, int loadTime
,
2354 bool normalExit
, SCRef cmd
, SCRef errorDesc
) {
2356 if (!errorDesc
.isEmpty()) {
2357 MainExecErrorEvent
* e
= new MainExecErrorEvent(cmd
, errorDesc
);
2358 QApplication::postEvent(parent(), e
);
2360 if (normalExit
) { // do not send anything if killed
2362 on_newDataReady(fh
);
2364 if (!loadingUnAppliedPatches
) {
2366 fh
->loadTime
+= loadTime
;
2368 ulong kb
= byteSize
/ 1024;
2369 double mbs
= (double)byteSize
/ fh
->loadTime
/ 1000;
2371 tmp
.sprintf("Loaded %i revisions (%li KB), "
2372 "time elapsed: %i ms (%.2f MB/s)",
2373 fh
->revs
.count(), kb
, fh
->loadTime
, mbs
);
2375 if (!tryFollowRenames(fh
))
2376 emit
loadCompleted(fh
, tmp
);
2378 if (isMainHistory(fh
))
2379 // wait the dust to settle down before to start
2380 // background file names loading for new revisions
2381 QTimer::singleShot(500, this, SLOT(loadFileNames()));
2384 if (loadingUnAppliedPatches
) {
2385 loadingUnAppliedPatches
= false;
2386 revData
->lns
->clear(); // again to reset lanes
2387 init2(); // continue with loading of remaining revisions
2391 bool Git::tryFollowRenames(FileHistory
* fh
) {
2393 if (isMainHistory(fh
))
2396 QStringList oldNames
;
2397 QMutableStringListIterator
it(fh
->renamedRevs
);
2398 while (it
.hasNext())
2399 if (!populateRenamedPatches(it
.next(), fh
->curFNames
, fh
, &oldNames
, false))
2402 if (fh
->renamedRevs
.isEmpty())
2406 args
<< fh
->renamedRevs
<< "--" << oldNames
;
2407 fh
->fNames
<< oldNames
;
2408 fh
->curFNames
= oldNames
;
2409 fh
->renamedRevs
.clear();
2410 return startRevList(args
, fh
);
2413 bool Git::populateRenamedPatches(SCRef renamedSha
, SCList newNames
, FileHistory
* fh
,
2414 QStringList
* oldNames
, bool backTrack
) {
2417 if (!run("git diff-tree -r -M " + renamedSha
, &runOutput
))
2420 // find the first renamed file with the new file name in renamedFiles list
2422 FOREACH_SL (it
, newNames
) {
2424 line
= runOutput
.section('\t' + *it
+ '\t', 0, 0,
2425 QString::SectionIncludeTrailingSep
);
2428 line
= runOutput
.section('\t' + *it
+ '\n', 0, 0);
2430 if (!line
.isEmpty())
2433 if (line
.contains('\n'))
2434 line
= line
.section('\n', -1, -1);
2436 SCRef status
= line
.section('\t', -2, -2).section(' ', -1, -1);
2437 if (!status
.startsWith('R'))
2441 SCRef nextFile
= runOutput
.section(line
, 1, 1).section('\t', 1, 1);
2442 oldNames
->append(nextFile
.section('\n', 0, 0));
2445 // get the diff betwen two files
2446 SCRef prevFileSha
= line
.section(' ', 2, 2);
2447 SCRef lastFileSha
= line
.section(' ', 3, 3);
2448 if (prevFileSha
== lastFileSha
) // just renamed
2450 else if (!run("git diff --no-ext-diff -r --full-index " + prevFileSha
+ " " + lastFileSha
, &runOutput
))
2453 SCRef prevFile
= line
.section('\t', -1, -1);
2454 if (!oldNames
->contains(prevFile
))
2455 oldNames
->append(prevFile
);
2457 // save the patch, will be used later to create a
2458 // proper graft sha with correct parent info
2460 QString
tmp(!runOutput
.isEmpty() ? runOutput
: "diff --no-ext-diff --\nsimilarity index 100%\n");
2461 fh
->renamedPatches
.insert(renamedSha
, tmp
);
2466 void Git::populateFileNamesMap() {
2468 for (int i
= 0; i
< dirNamesVec
.count(); ++i
)
2469 dirNamesMap
.insert(dirNamesVec
[i
], i
);
2471 for (int i
= 0; i
< fileNamesVec
.count(); ++i
)
2472 fileNamesMap
.insert(fileNamesVec
[i
], i
);
2475 void Git::loadFileCache() {
2477 if (!fileCacheAccessed
) {
2479 fileCacheAccessed
= true;
2481 if (Cache::load(gitDir
, revsFiles
, dirNamesVec
, fileNamesVec
, shaBuf
)) {
2482 revsFilesShaBackupBuf
.append(shaBuf
);
2483 populateFileNamesMap();
2485 dbs("ERROR: unable to load file names cache");
2489 void Git::loadFileNames() {
2491 indexTree(); // we are sure data loading is finished at this point
2494 QString diffTreeBuf
;
2495 FOREACH (ShaVect
, it
, revData
->revOrder
) {
2497 if (!revsFiles
.contains(*it
)) {
2498 const Rev
* c
= revLookup(*it
);
2499 if (c
->parentsCount() == 1) { // skip initials and merges
2500 diffTreeBuf
.append(*it
).append('\n');
2505 if (!diffTreeBuf
.isEmpty()) {
2506 filesLoadingPending
= filesLoadingCurSha
= "";
2507 filesLoadingStartOfs
= revsFiles
.count();
2508 emit
fileNamesLoad(3, revCnt
);
2510 const QString
runCmd("git diff-tree --no-color -r -C --stdin");
2511 runAsync(runCmd
, this, diffTreeBuf
);
2515 bool Git::filterEarlyOutputRev(FileHistory
* fh
, Rev
* rev
) {
2517 if (fh
->earlyOutputCnt
< fh
->revOrder
.count()) {
2519 const ShaString
& sha
= fh
->revOrder
[fh
->earlyOutputCnt
++];
2520 const Rev
* c
= revLookup(sha
, fh
);
2522 if (rev
->sha() != sha
|| rev
->parents() != c
->parents()) {
2523 // mismatch found! set correct value, 'rev' will
2524 // overwrite 'c' upon returning
2525 rev
->orderIdx
= c
->orderIdx
;
2526 revData
->clear(false); // flush the tail
2528 return true; // filter out 'rev'
2531 // we have new revisions, exit from early output state
2532 fh
->setEarlyOutputState(false);
2536 int Git::addChunk(FileHistory
* fh
, const QByteArray
& ba
, int start
) {
2538 RevMap
& r
= fh
->revs
;
2543 // only here we create a new rev
2544 rev
= new Rev(ba
, start
, fh
->revOrder
.count(), &nextStart
, !isMainHistory(fh
));
2546 if (nextStart
== -2) {
2548 fh
->setEarlyOutputState(true);
2549 start
= ba
.indexOf('\n', start
) + 1;
2552 } while (nextStart
== -2);
2554 if (nextStart
== -1) { // half chunk detected
2559 const ShaString
& sha
= rev
->sha();
2561 if (fh
->earlyOutputCnt
!= -1 && filterEarlyOutputRev(fh
, rev
)) {
2567 if (loadingUnAppliedPatches
) { // filter out possible spurious revs
2569 Reference
* rf
= lookupReference(sha
);
2570 if (!(rf
&& (rf
->type
& UN_APPLIED
))) {
2575 // remove StGIT spurious revs filter
2576 if (!firstNonStGitPatch
.isEmpty() && firstNonStGitPatch
== sha
)
2577 firstNonStGitPatch
= "";
2579 // StGIT called with --all option creates spurious revs so filter
2580 // out unknown revs until no more StGIT patches are waited and
2581 // firstNonStGitPatch is reached
2582 if (!(firstNonStGitPatch
.isEmpty() && patchesStillToFind
== 0) &&
2583 !loadingUnAppliedPatches
&& isMainHistory(fh
)) {
2585 Reference
* rf
= lookupReference(sha
);
2586 if (!(rf
&& (rf
->type
& APPLIED
))) {
2591 if (r
.contains(sha
)) {
2592 // StGIT unapplied patches could be sent again by
2593 // 'git log' as example if called with --all option.
2594 if (r
[sha
]->isUnApplied
) {
2598 // could be a side effect of 'git log -m', see below
2599 if (isMainHistory(fh
) || rev
->parentsCount() < 2)
2600 dbp("ASSERT: addChunk sha <%1> already received", sha
);
2603 if (r
.isEmpty() && !isMainHistory(fh
)) {
2604 bool added
= copyDiffIndex(fh
, sha
);
2605 rev
->orderIdx
= added
? 1 : 0;
2607 if ( !isMainHistory(fh
)
2608 && !fh
->renamedPatches
.isEmpty()
2609 && fh
->renamedPatches
.contains(sha
)) {
2611 // this is the new rev with renamed file, the rev is correct but
2612 // the patch, create a new rev with proper patch and use that instead
2613 const Rev
* prevSha
= revLookup(sha
, fh
);
2614 Rev
* c
= fakeRevData(sha
, rev
->parents(), rev
->author(),
2615 rev
->authorDate(), rev
->shortLog(), rev
->longLog(),
2616 fh
->renamedPatches
[sha
], prevSha
->orderIdx
, fh
);
2618 r
.insert(sha
, c
); // overwrite old content
2619 fh
->renamedPatches
.remove(sha
);
2622 if (!isMainHistory(fh
) && rev
->parentsCount() > 1 && r
.contains(sha
)) {
2623 /* In this case git log is called with -m option and merges are splitted
2624 in one commit per parent but all them have the same sha.
2625 So we add only the first to fh->revOrder to display history correctly,
2626 but we nevertheless add all the commits to 'r' so that annotation code
2627 can get the patches.
2632 mergeSha
= QString::number(++i
) + " m " + sha
;
2633 while (r
.contains(toTempSha(mergeSha
)));
2635 const ShaString
& ss
= toPersistentSha(mergeSha
, shaBackupBuf
);
2639 fh
->revOrder
.append(sha
);
2641 if (rev
->parentsCount() == 0 && !isMainHistory(fh
))
2642 fh
->renamedRevs
.append(sha
);
2645 // updateLanes() is called too late, after loadingUnAppliedPatches
2646 // has been reset so update the lanes now.
2647 if (loadingUnAppliedPatches
) {
2649 Rev
* c
= const_cast<Rev
*>(revLookup(sha
, fh
));
2650 c
->isUnApplied
= true;
2651 c
->lanes
.append(UNAPPLIED
);
2653 } else if (patchesStillToFind
> 0 || !isMainHistory(fh
)) { // try to avoid costly lookup
2655 Reference
* rf
= lookupReference(sha
);
2656 if (rf
&& (rf
->type
& APPLIED
)) {
2658 Rev
* c
= const_cast<Rev
*>(revLookup(sha
, fh
));
2659 c
->isApplied
= true;
2660 if (isMainHistory(fh
)) {
2661 patchesStillToFind
--;
2662 if (patchesStillToFind
== 0)
2663 // any rev will be discarded until
2664 // firstNonStGitPatch arrives
2665 firstNonStGitPatch
= c
->parent(0);
2673 bool Git::copyDiffIndex(FileHistory
* fh
, SCRef parent
) {
2674 // must be called with empty revs and empty revOrder
2676 if (!fh
->revOrder
.isEmpty() || !fh
->revs
.isEmpty()) {
2677 dbs("ASSERT in copyDiffIndex: called with wrong context");
2680 const Rev
* r
= revLookup(ZERO_SHA
);
2684 const RevFile
* files
= getFiles(ZERO_SHA
);
2685 if (!files
|| findFileIndex(*files
, fh
->fileNames().first()) == -1)
2688 // insert a custom ZERO_SHA rev with proper parent
2689 const Rev
* rf
= fakeWorkDirRev(parent
, "Working directory changes", "long log\n", 0, fh
);
2690 fh
->revs
.insert(ZERO_SHA_RAW
, rf
);
2691 fh
->revOrder
.append(ZERO_SHA_RAW
);
2695 void Git::setLane(SCRef sha
, FileHistory
* fh
) {
2698 uint i
= fh
->firstFreeLane
;
2699 QVector
<QByteArray
> ba
;
2700 const ShaString
& ss
= toPersistentSha(sha
, ba
);
2701 const ShaVect
& shaVec(fh
->revOrder
);
2703 for (uint cnt
= shaVec
.count(); i
< cnt
; ++i
) {
2705 const ShaString
& curSha
= shaVec
[i
];
2706 Rev
* r
= const_cast<Rev
*>(revLookup(curSha
, fh
));
2707 if (r
->lanes
.count() == 0)
2708 updateLanes(*r
, *l
, curSha
);
2713 fh
->firstFreeLane
= ++i
;
2716 void Git::updateLanes(Rev
& c
, Lanes
& lns
, SCRef sha
) {
2717 // we could get third argument from c.sha(), but we are in fast path here
2718 // and c.sha() involves a deep copy, so we accept a little redundancy
2723 bool isDiscontinuity
;
2724 bool isFork
= lns
.isFork(sha
, isDiscontinuity
);
2725 bool isMerge
= (c
.parentsCount() > 1);
2726 bool isInitial
= (c
.parentsCount() == 0);
2728 if (isDiscontinuity
)
2729 lns
.changeActiveLane(sha
); // uses previous isBoundary state
2731 lns
.setBoundary(c
.isBoundary()); // update must be here
2736 lns
.setMerge(c
.parents());
2742 lns
.getLanes(c
.lanes
); // here lanes are snapshotted
2744 SCRef nextSha
= (isInitial
) ? "" : QString(c
.parent(0));
2746 lns
.nextParent(nextSha
);
2757 // QString tmp = "", tmp2;
2758 // for (uint i = 0; i < c.lanes.count(); i++) {
2759 // tmp2.setNum(c.lanes[i]);
2760 // tmp.append(tmp2 + "-");
2762 // qDebug("%s %s", tmp.toUtf8().data(), sha.toUtf8().data());
2765 void Git::procFinished() {
2767 flushFileNames(fileLoader
);
2768 filesLoadingPending
= filesLoadingCurSha
= "";
2769 emit
fileNamesLoad(1, revsFiles
.count() - filesLoadingStartOfs
);
2772 void Git::procReadyRead(const QByteArray
& fileChunk
) {
2774 if (filesLoadingPending
.isEmpty())
2775 filesLoadingPending
= fileChunk
;
2777 filesLoadingPending
.append(fileChunk
); // add to previous half lines
2780 if (!filesLoadingCurSha
.isEmpty() && revsFiles
.contains(toTempSha(filesLoadingCurSha
)))
2781 rf
= const_cast<RevFile
*>(revsFiles
[toTempSha(filesLoadingCurSha
)]);
2783 int nextEOL
= filesLoadingPending
.indexOf('\n');
2785 while (nextEOL
!= -1) {
2787 SCRef
line(filesLoadingPending
.mid(lastEOL
+ 1, nextEOL
- lastEOL
- 1));
2788 if (line
.at(0) != ':') {
2789 SCRef sha
= line
.left(40);
2790 if (!rf
|| sha
!= filesLoadingCurSha
) { // new commit
2792 revsFiles
.insert(toPersistentSha(sha
, revsFilesShaBackupBuf
), rf
);
2793 filesLoadingCurSha
= sha
;
2794 cacheNeedsUpdate
= true;
2796 dbp("ASSERT: repeated sha %1 in file names loading", sha
);
2797 } else // line.constref(0) == ':'
2798 parseDiffFormatLine(*rf
, line
, 1, fileLoader
);
2801 nextEOL
= filesLoadingPending
.indexOf('\n', lastEOL
+ 1);
2804 filesLoadingPending
.remove(0, lastEOL
+ 1);
2806 emit
fileNamesLoad(2, revsFiles
.count() - filesLoadingStartOfs
);
2809 void Git::flushFileNames(FileNamesLoader
& fl
) {
2814 QByteArray
& b
= fl
.rf
->pathsIdx
;
2815 QVector
<int>& dirs
= fl
.rfDirs
;
2818 b
.resize(2 * dirs
.size() * sizeof(int));
2820 int* d
= (int*)(b
.data());
2822 for (int i
= 0; i
< dirs
.size(); i
++) {
2825 d
[dirs
.size() + i
] = fl
.rfNames
.at(i
);
2832 void Git::appendFileName(RevFile
& rf
, SCRef name
, FileNamesLoader
& fl
) {
2838 int idx
= name
.lastIndexOf('/') + 1;
2839 SCRef dr
= name
.left(idx
);
2840 SCRef nm
= name
.mid(idx
);
2842 QHash
<QString
, int>::const_iterator
it(dirNamesMap
.constFind(dr
));
2843 if (it
== dirNamesMap
.constEnd()) {
2844 int idx
= dirNamesVec
.count();
2845 dirNamesMap
.insert(dr
, idx
);
2846 dirNamesVec
.append(dr
);
2847 fl
.rfDirs
.append(idx
);
2849 fl
.rfDirs
.append(*it
);
2851 it
= fileNamesMap
.constFind(nm
);
2852 if (it
== fileNamesMap
.constEnd()) {
2853 int idx
= fileNamesVec
.count();
2854 fileNamesMap
.insert(nm
, idx
);
2855 fileNamesVec
.append(nm
);
2856 fl
.rfNames
.append(idx
);
2858 fl
.rfNames
.append(*it
);
2861 void Git::updateDescMap(const Rev
* r
,uint idx
, QHash
<QPair
<uint
, uint
>, bool>& dm
,
2862 QHash
<uint
, QVector
<int> >& dv
) {
2864 QVector
<int> descVec
;
2865 if (r
->descRefsMaster
!= -1) {
2867 const Rev
* tmp
= revLookup(revData
->revOrder
[r
->descRefsMaster
]);
2868 const QVector
<int>& nr
= tmp
->descRefs
;
2870 for (int i
= 0; i
< nr
.count(); i
++) {
2872 if (!dv
.contains(nr
[i
])) {
2873 dbp("ASSERT descendant for %1 not found", r
->sha());
2876 const QVector
<int>& dvv
= dv
[nr
[i
]];
2878 // copy the whole vector instead of each element
2879 // in the first iteration of the loop below
2880 descVec
= dvv
; // quick (shared) copy
2882 for (int y
= 0; y
< dvv
.count(); y
++) {
2884 uint v
= (uint
)dvv
[y
];
2885 QPair
<uint
, uint
> key
= qMakePair(idx
, v
);
2886 QPair
<uint
, uint
> keyN
= qMakePair(v
, idx
);
2887 dm
.insert(key
, true);
2888 dm
.insert(keyN
, false);
2890 // we don't want duplicated entry, otherwise 'dvv' grows
2891 // greatly in repos with many tagged development branches
2892 if (i
> 0 && !descVec
.contains(v
)) // i > 0 is rare, no
2893 descVec
.append(v
); // need to optimize
2897 descVec
.append(idx
);
2898 dv
.insert(idx
, descVec
);
2901 void Git::mergeBranches(Rev
* p
, const Rev
* r
) {
2903 int r_descBrnMaster
= (checkRef(r
->sha(), BRANCH
| RMT_BRANCH
) ? r
->orderIdx
: r
->descBrnMaster
);
2905 if (p
->descBrnMaster
== r_descBrnMaster
|| r_descBrnMaster
== -1)
2908 // we want all the descendant branches, so just avoid duplicates
2909 const QVector
<int>& src1
= revLookup(revData
->revOrder
[p
->descBrnMaster
])->descBranches
;
2910 const QVector
<int>& src2
= revLookup(revData
->revOrder
[r_descBrnMaster
])->descBranches
;
2911 QVector
<int> dst(src1
);
2912 for (int i
= 0; i
< src2
.count(); i
++)
2913 if (qFind(src1
.constBegin(), src1
.constEnd(), src2
[i
]) == src1
.constEnd())
2914 dst
.append(src2
[i
]);
2916 p
->descBranches
= dst
;
2917 p
->descBrnMaster
= p
->orderIdx
;
2920 void Git::mergeNearTags(bool down
, Rev
* p
, const Rev
* r
, const QHash
<QPair
<uint
, uint
>, bool>& dm
) {
2922 bool isTag
= checkRef(r
->sha(), TAG
);
2923 int r_descRefsMaster
= isTag
? r
->orderIdx
: r
->descRefsMaster
;
2924 int r_ancRefsMaster
= isTag
? r
->orderIdx
: r
->ancRefsMaster
;
2926 if (down
&& (p
->descRefsMaster
== r_descRefsMaster
|| r_descRefsMaster
== -1))
2929 if (!down
&& (p
->ancRefsMaster
== r_ancRefsMaster
|| r_ancRefsMaster
== -1))
2932 // we want the nearest tag only, so remove any tag
2933 // that is ancestor of any other tag in p U r
2934 const ShaVect
& ro
= revData
->revOrder
;
2935 const ShaString
& sha1
= down
? ro
[p
->descRefsMaster
] : ro
[p
->ancRefsMaster
];
2936 const ShaString
& sha2
= down
? ro
[r_descRefsMaster
] : ro
[r_ancRefsMaster
];
2937 const QVector
<int>& src1
= down
? revLookup(sha1
)->descRefs
: revLookup(sha1
)->ancRefs
;
2938 const QVector
<int>& src2
= down
? revLookup(sha2
)->descRefs
: revLookup(sha2
)->ancRefs
;
2939 QVector
<int> dst(src1
);
2941 for (int s2
= 0; s2
< src2
.count(); s2
++) {
2944 for (int s1
= 0; s1
< src1
.count(); s1
++) {
2946 if (src2
[s2
] == src1
[s1
]) {
2950 QPair
<uint
, uint
> key
= qMakePair((uint
)src2
[s2
], (uint
)src1
[s1
]);
2952 if (!dm
.contains(key
)) { // could be empty if all tags are independent
2953 add
= true; // could be an independent path
2956 add
= (down
&& dm
[key
]) || (!down
&& !dm
[key
]);
2958 dst
[s1
] = -1; // mark for removing
2963 dst
.append(src2
[s2
]);
2965 QVector
<int>& nearRefs
= (down
? p
->descRefs
: p
->ancRefs
);
2966 int& nearRefsMaster
= (down
? p
->descRefsMaster
: p
->ancRefsMaster
);
2969 for (int s2
= 0; s2
< dst
.count(); s2
++)
2971 nearRefs
.append(dst
[s2
]);
2973 nearRefsMaster
= p
->orderIdx
;
2976 void Git::indexTree() {
2978 const ShaVect
& ro
= revData
->revOrder
;
2979 if (ro
.count() == 0)
2982 // we keep the pairs(x, y). Value is true if x is
2983 // ancestor of y or false if y is ancestor of x
2984 QHash
<QPair
<uint
, uint
>, bool> descMap
;
2985 QHash
<uint
, QVector
<int> > descVect
;
2987 // walk down the tree from latest to oldest,
2988 // compute children and nearest descendants
2989 for (uint i
= 0, cnt
= ro
.count(); i
< cnt
; i
++) {
2991 uint type
= checkRef(ro
[i
]);
2992 bool isB
= (type
& (BRANCH
| RMT_BRANCH
));
2993 bool isT
= (type
& TAG
);
2995 const Rev
* r
= revLookup(ro
[i
]);
2998 Rev
* rr
= const_cast<Rev
*>(r
);
2999 if (r
->descBrnMaster
!= -1) {
3000 const ShaString
& sha
= ro
[r
->descBrnMaster
];
3001 rr
->descBranches
= revLookup(sha
)->descBranches
;
3003 rr
->descBranches
.append(i
);
3006 updateDescMap(r
, i
, descMap
, descVect
);
3007 Rev
* rr
= const_cast<Rev
*>(r
);
3008 rr
->descRefs
.clear();
3009 rr
->descRefs
.append(i
);
3011 for (uint y
= 0; y
< r
->parentsCount(); y
++) {
3013 Rev
* p
= const_cast<Rev
*>(revLookup(r
->parent(y
)));
3015 p
->children
.append(i
);
3017 if (p
->descBrnMaster
== -1)
3018 p
->descBrnMaster
= isB
? r
->orderIdx
: r
->descBrnMaster
;
3020 mergeBranches(p
, r
);
3022 if (p
->descRefsMaster
== -1)
3023 p
->descRefsMaster
= isT
? r
->orderIdx
: r
->descRefsMaster
;
3025 mergeNearTags(optGoDown
, p
, r
, descMap
);
3029 // walk backward through the tree and compute nearest tagged ancestors
3030 for (int i
= ro
.count() - 1; i
>= 0; i
--) {
3032 const Rev
* r
= revLookup(ro
[i
]);
3033 bool isTag
= checkRef(ro
[i
], TAG
);
3036 Rev
* rr
= const_cast<Rev
*>(r
);
3037 rr
->ancRefs
.clear();
3038 rr
->ancRefs
.append(i
);
3040 for (int y
= 0; y
< r
->children
.count(); y
++) {
3042 Rev
* c
= const_cast<Rev
*>(revLookup(ro
[r
->children
[y
]]));
3044 if (c
->ancRefsMaster
== -1)
3045 c
->ancRefsMaster
= isTag
? r
->orderIdx
:r
->ancRefsMaster
;
3047 mergeNearTags(!optGoDown
, c
, r
, descMap
);