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
.toAscii());
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
) {
977 text
= Qt::escape(txt
);
979 if (regExp
.isEmpty())
982 SCRef
startCol(QString::fromLatin1("<b><font color=\"red\">"));
983 SCRef
endCol(QString::fromLatin1("</font></b>"));
985 while ((pos
= text
.indexOf(regExp
, pos
)) != -1) {
987 SCRef
match(regExp
.cap(0));
988 const QString
coloredText(startCol
+ match
+ endCol
);
989 text
.replace(pos
, match
.length(), coloredText
);
990 pos
+= coloredText
.length();
995 //CT TODO utility function; can go elsewhere
996 const QString
Git::formatList(SCList sl
, SCRef name
, bool inOneLine
) {
1001 QString ls
= "<tr><td class='h'>" + name
+ "</td><td>";
1002 const QString joinStr
= inOneLine
? ", " : "</td></tr>\n" + ls
;
1003 ls
+= sl
.join(joinStr
);
1004 ls
+= "</td></tr>\n";
1008 const QString
Git::getDesc(SCRef sha
, QRegExp
& shortLogRE
, QRegExp
& longLogRE
,
1009 bool showHeader
, FileHistory
* fh
) {
1014 const Rev
* c
= revLookup(sha
, fh
);
1015 if (!c
) // sha of a not loaded revision, as
1016 return ""; // example asked from file history
1020 text
= Qt::convertFromPlainText(c
->longLog());
1022 QTextStream
ts(&text
);
1023 ts
<< "<html><head><style type=\"text/css\">"
1024 "tr.head { background-color: " << QPalette().color(QPalette::Mid
).name() << " }\n"
1025 "td.h { font-weight: bold; }\n"
1026 "table { background-color: " << QPalette().color(QPalette::Button
).name() << "; }\n"
1027 "span.h { font-weight: bold; font-size: medium; }\n"
1028 "div.l { white-space: pre; "
1029 "font-family: " << TYPE_WRITER_FONT
.family() << ";"
1030 "font-size: " << TYPE_WRITER_FONT
.pointSize() << "pt;}\n"
1031 "</style></head><body><div class='t'>\n"
1032 "<table border=0 cellspacing=0 cellpadding=2>";
1034 ts
<< "<tr class='head'> <th></th> <th><span class='h'>"
1035 << colorMatch(c
->shortLog(), shortLogRE
)
1036 << "</span></th></tr>";
1039 if (c
->committer() != c
->author())
1040 ts
<< formatList(QStringList(Qt::escape(c
->committer())), "Committer");
1041 ts
<< formatList(QStringList(Qt::escape(c
->author())), "Author");
1042 ts
<< formatList(QStringList(getLocalDate(c
->authorDate())), " Author date");
1044 if (c
->isUnApplied
|| c
->isApplied
) {
1046 QStringList
patches(getRefName(sha
, APPLIED
));
1047 patches
+= getRefName(sha
, UN_APPLIED
);
1048 ts
<< formatList(patches
, "Patch");
1050 ts
<< formatList(c
->parents(), "Parent", false);
1051 ts
<< formatList(getChildren(sha
), "Child", false);
1052 ts
<< formatList(getDescendantBranches(sha
), "Branch", false);
1053 ts
<< formatList(getNearTags(!optGoDown
, sha
), "Follows");
1054 ts
<< formatList(getNearTags(optGoDown
, sha
), "Precedes");
1057 QString
longLog(c
->longLog());
1059 longLog
.prepend(QString("\n") + c
->shortLog() + "\n");
1062 QString
log(colorMatch(longLog
, longLogRE
));
1063 log
.replace("\n", "\n ").prepend('\n');
1064 ts
<< "</table></div><div class='l'>" << log
<< "</div></body></html>";
1068 // added to commit logs, we avoid to call git rev-parse for a possible abbreviated
1069 // sha if there isn't a leading trailing space or an open parenthesis and,
1070 // in that case, before the space must not be a ':' character.
1071 // It's an ugly heuristic, but seems to work in most cases.
1072 QRegExp
reSHA("..[0-9a-f]{21,40}|[^:][\\s(][0-9a-f]{6,20}", Qt::CaseInsensitive
);
1073 reSHA
.setMinimal(false);
1075 while ((pos
= text
.indexOf(reSHA
, pos
)) != -1) {
1077 SCRef ref
= reSHA
.cap(0).mid(2);
1078 const Rev
* r
= (ref
.length() == 40 ? revLookup(ref
) : revLookup(getRefSha(ref
)));
1079 if (r
&& r
->sha() != ZERO_SHA_RAW
) {
1080 QString
slog(r
->shortLog());
1081 if (slog
.isEmpty()) // very rare but possible
1083 if (slog
.length() > 60)
1084 slog
= slog
.left(57).trimmed().append("...");
1086 slog
= Qt::escape(slog
);
1087 const QString
link("<a href=\"" + r
->sha() + "\">" + slog
+ "</a>");
1088 text
.replace(pos
+ 2, ref
.length(), link
);
1089 pos
+= link
.length();
1091 pos
+= reSHA
.cap(0).length();
1096 const RevFile
* Git::insertNewFiles(SCRef sha
, SCRef data
) {
1098 /* we use an independent FileNamesLoader to avoid data
1099 * corruption if we are loading file names in background
1103 RevFile
* rf
= new RevFile();
1104 parseDiffFormat(*rf
, data
, fl
);
1107 revsFiles
.insert(toPersistentSha(sha
, revsFilesShaBackupBuf
), rf
);
1111 bool Git::runDiffTreeWithRenameDetection(SCRef runCmd
, QString
* runOutput
) {
1112 /* Under some cases git could warn out:
1114 "too many files, skipping inexact rename detection"
1116 So if this occurs fallback on NO rename detection.
1118 QString
cmd(runCmd
); // runCmd must be without -C option
1119 cmd
.replace("git diff-tree", "git diff-tree -C");
1121 errorReportingEnabled
= false;
1122 bool renameDetectionOk
= run(cmd
, runOutput
);
1123 errorReportingEnabled
= true;
1125 if (!renameDetectionOk
) // retry without rename detection
1126 return run(runCmd
, runOutput
);
1131 const RevFile
* Git::getAllMergeFiles(const Rev
* r
) {
1133 SCRef
mySha(ALL_MERGE_FILES
+ r
->sha());
1134 if (revsFiles
.contains(toTempSha(mySha
)))
1135 return revsFiles
[toTempSha(mySha
)];
1137 EM_PROCESS_EVENTS
; // 'git diff-tree' could be slow
1139 QString
runCmd("git diff-tree --no-color -r -m " + r
->sha()), runOutput
;
1140 if (!runDiffTreeWithRenameDetection(runCmd
, &runOutput
))
1143 return insertNewFiles(mySha
, runOutput
);
1146 const RevFile
* Git::getFiles(SCRef sha
, SCRef diffToSha
, bool allFiles
, SCRef path
) {
1148 const Rev
* r
= revLookup(sha
);
1152 if (r
->parentsCount() == 0) // skip initial rev
1155 if (r
->parentsCount() > 1 && diffToSha
.isEmpty() && allFiles
)
1156 return getAllMergeFiles(r
);
1158 if (!diffToSha
.isEmpty() && (sha
!= ZERO_SHA
)) {
1160 QString
runCmd("git diff-tree --no-color -r -m ");
1161 runCmd
.append(diffToSha
+ " " + sha
);
1162 if (!path
.isEmpty())
1163 runCmd
.append(" " + path
);
1165 EM_PROCESS_EVENTS
; // 'git diff-tree' could be slow
1168 if (!runDiffTreeWithRenameDetection(runCmd
, &runOutput
))
1171 // we insert a dummy revision file object. It will be
1172 // overwritten at each request but we don't care.
1173 return insertNewFiles(CUSTOM_SHA
, runOutput
);
1175 if (revsFiles
.contains(r
->sha()))
1176 return revsFiles
[r
->sha()]; // ZERO_SHA search arrives here
1178 if (sha
== ZERO_SHA
) {
1179 dbs("ASSERT in Git::getFiles, ZERO_SHA not found");
1183 EM_PROCESS_EVENTS
; // 'git diff-tree' could be slow
1185 QString
runCmd("git diff-tree --no-color -r -c " + sha
), runOutput
;
1186 if (!runDiffTreeWithRenameDetection(runCmd
, &runOutput
))
1189 if (revsFiles
.contains(r
->sha())) // has been created in the mean time?
1190 return revsFiles
[r
->sha()];
1192 cacheNeedsUpdate
= true;
1193 return insertNewFiles(sha
, runOutput
);
1196 bool Git::startFileHistory(SCRef sha
, SCRef startingFileName
, FileHistory
* fh
) {
1198 QStringList
args(getDescendantBranches(sha
, true));
1202 QString newestFileName
= getNewestFileName(args
, startingFileName
);
1203 fh
->resetFileNames(newestFileName
);
1205 args
.clear(); // load history from all the branches
1206 args
<< getAllRefSha(BRANCH
| RMT_BRANCH
);
1208 args
<< "--" << newestFileName
;
1209 return startRevList(args
, fh
);
1212 const QString
Git::getNewestFileName(SCList branches
, SCRef fileName
) {
1214 QString
curFileName(fileName
), runOutput
, args
;
1216 args
= branches
.join(" ") + " -- " + curFileName
;
1217 if (!run("git ls-tree " + args
, &runOutput
))
1220 if (!runOutput
.isEmpty())
1223 QString
msg("Retrieving file renames, now at '" + curFileName
+ "'...");
1224 QApplication::postEvent(parent(), new MessageEvent(msg
));
1225 EM_PROCESS_EVENTS_NO_INPUT
;
1227 if (!run("git rev-list -n1 " + args
, &runOutput
))
1230 if (runOutput
.isEmpty()) // try harder
1231 if (!run("git rev-list --full-history -n1 " + args
, &runOutput
))
1234 if (runOutput
.isEmpty())
1237 SCRef sha
= runOutput
.trimmed();
1239 if (!populateRenamedPatches(sha
, QStringList(curFileName
), NULL
, &newCur
, true))
1242 curFileName
= newCur
.first();
1247 void Git::getFileFilter(SCRef path
, ShaSet
& shaSet
) const {
1250 QRegExp
rx(path
, Qt::CaseInsensitive
, QRegExp::Wildcard
);
1251 FOREACH (ShaVect
, it
, revData
->revOrder
) {
1253 if (!revsFiles
.contains(*it
))
1256 // case insensitive, wildcard search
1257 const RevFile
* rf
= revsFiles
[*it
];
1258 for (int i
= 0; i
< rf
->count(); ++i
)
1259 if (filePath(*rf
, i
).contains(rx
)) {
1266 bool Git::getPatchFilter(SCRef exp
, bool isRegExp
, ShaSet
& shaSet
) {
1270 FOREACH (ShaVect
, it
, revData
->revOrder
)
1271 if (*it
!= ZERO_SHA_RAW
)
1272 buf
.append(*it
).append('\n');
1277 EM_PROCESS_EVENTS
; // 'git diff-tree' could be slow
1279 QString
runCmd("git diff-tree --no-color -r -s --stdin "), runOutput
;
1281 runCmd
.append("--pickaxe-regex ");
1283 runCmd
.append(quote("-S" + exp
));
1284 if (!run(runCmd
, &runOutput
, NULL
, buf
))
1287 const QStringList
sl(runOutput
.split('\n', QString::SkipEmptyParts
));
1294 bool Git::resetCommits(int parentDepth
) {
1296 QString
runCmd("git reset --soft HEAD~");
1297 runCmd
.append(QString::number(parentDepth
));
1301 bool Git::applyPatchFile(SCRef patchPath
, bool fold
, bool isDragDrop
) {
1305 bool ok
= run("stg fold " + quote(patchPath
)); // merge in working directory
1307 ok
= run("stg refresh"); // update top patch
1310 return run("stg import --mail " + quote(patchPath
));
1312 QString
runCmd("git am --utf8 --3way ");
1315 const QString
APOpt(settings
.value(AM_P_OPT_KEY
).toString());
1316 if (!APOpt
.isEmpty())
1317 runCmd
.append(APOpt
.trimmed() + " ");
1320 runCmd
.append("--keep ");
1322 if (testFlag(SIGN_PATCH_F
))
1323 runCmd
.append("--signoff ");
1325 return run(runCmd
+ quote(patchPath
));
1328 const QStringList
Git::sortShaListByIndex(SCList shaList
) {
1330 QStringList orderedShaList
;
1331 FOREACH_SL (it
, shaList
)
1332 appendNamesWithId(orderedShaList
, *it
, QStringList(*it
), true);
1334 orderedShaList
.sort();
1335 QStringList::iterator
itN(orderedShaList
.begin());
1336 for ( ; itN
!= orderedShaList
.end(); ++itN
) // strip 'idx'
1337 (*itN
) = (*itN
).section(' ', -1, -1);
1339 return orderedShaList
;
1342 bool Git::formatPatch(SCList shaList
, SCRef dirPath
, SCRef remoteDir
) {
1344 bool remote
= !remoteDir
.isEmpty();
1346 const QString
FPOpt(settings
.value(FMT_P_OPT_KEY
).toString());
1348 QString
runCmd("git format-patch --no-color");
1349 if (testFlag(NUMBERS_F
) && !remote
)
1350 runCmd
.append(" -n");
1353 runCmd
.append(" --keep-subject");
1355 runCmd
.append(" -o " + quote(dirPath
));
1356 if (!FPOpt
.isEmpty())
1357 runCmd
.append(" " + FPOpt
.trimmed());
1359 const QString
tmp(workDir
);
1361 workDir
= remoteDir
; // run() uses workDir value
1363 // shaList is ordered by newest to oldest
1364 runCmd
.append(" " + shaList
.last());
1365 runCmd
.append(QString::fromLatin1("^..") + shaList
.first());
1366 bool ret
= run(runCmd
);
1371 const QStringList
Git::getOtherFiles(SCList selFiles
, bool onlyInIndex
) {
1373 const RevFile
* files
= getFiles(ZERO_SHA
); // files != NULL
1374 QStringList notSelFiles
;
1375 for (int i
= 0; i
< files
->count(); ++i
) {
1376 SCRef fp
= filePath(*files
, i
);
1377 if (selFiles
.indexOf(fp
) == -1) { // not selected...
1378 if (!onlyInIndex
|| files
->statusCmp(i
, RevFile::IN_INDEX
))
1379 notSelFiles
.append(fp
);
1385 bool Git::updateIndex(SCList selFiles
) {
1387 const RevFile
* files
= getFiles(ZERO_SHA
); // files != NULL
1389 QStringList toAdd
, toRemove
;
1390 FOREACH_SL (it
, selFiles
) {
1391 int idx
= findFileIndex(*files
, *it
);
1392 if (files
->statusCmp(idx
, RevFile::DELETED
))
1397 if (!toRemove
.isEmpty() && !run("git rm --cached --ignore-unmatch -- " + quote(toRemove
)))
1400 if (!toAdd
.isEmpty() && !run("git add -- " + quote(toAdd
)))
1406 bool Git::commitFiles(SCList selFiles
, SCRef msg
, bool amend
) {
1408 const QString
msgFile(gitDir
+ "/qgit_cmt_msg.txt");
1409 if (!writeToFile(msgFile
, msg
)) // early skip
1412 // add user selectable commit options
1414 const QString
CMArgs(settings
.value(CMT_ARGS_KEY
).toString());
1417 if (!CMArgs
.isEmpty())
1418 cmtOptions
.append(" " + CMArgs
);
1420 if (testFlag(SIGN_CMT_F
))
1421 cmtOptions
.append(" -s");
1423 if (testFlag(VERIFY_CMT_F
))
1424 cmtOptions
.append(" -v");
1427 cmtOptions
.append(" --amend");
1431 // get not selected files but updated in index to restore at the end
1432 const QStringList
notSel(getOtherFiles(selFiles
, optOnlyInIndex
));
1434 // call git reset to remove not selected files from index
1435 if (!notSel
.empty() && !run("git reset -- " + quote(notSel
)))
1438 // update index with selected files
1439 if (!updateIndex(selFiles
))
1442 // now we can finally commit..
1443 if (!run("git commit" + cmtOptions
+ " -F " + quote(msgFile
)))
1446 // restore not selected files that were already in index
1447 if (!notSel
.empty() && !updateIndex(notSel
))
1453 dir
.remove(msgFile
);
1457 bool Git::mkPatchFromWorkDir(SCRef msg
, SCRef patchFile
, SCList files
) {
1459 /* unfortunately 'git diff' sees only files already
1460 * known to git or already in index, so update index first
1461 * to be sure also unknown files are correctly found
1463 if (!updateIndex(files
))
1467 if (!run("git diff --no-ext-diff -C HEAD -- " + quote(files
), &runOutput
))
1470 const QString
patch("Subject: " + msg
+ "\n---\n" + runOutput
);
1471 return writeToFile(patchFile
, patch
);
1474 bool Git::stgCommit(SCList selFiles
, SCRef msg
, SCRef patchName
, bool fold
) {
1476 /* Here the deal is to use 'stg import' and 'stg fold' to add a new
1477 * patch or refresh the current one respectively. Unfortunately refresh
1478 * does not work with partial selection of files and also does not take
1479 * patch message from a file that is needed to avoid artifacts with '\n'
1484 * - Create a patch file with the changes you want to import/fold in StGit
1485 * - Stash working directory files because import/fold wants a clean directory
1486 * - Import/fold the patch
1487 * - Unstash and merge working directory modified files
1488 * - Restore index with not selected files
1491 /* Step 1: Create a patch file with the changes you want to import/fold */
1493 const QString
patchFile(gitDir
+ "/qgit_tmp_patch.txt");
1495 // in case we don't have files to restore we can shortcut various commands
1496 bool partialSelection
= !getOtherFiles(selFiles
, !optOnlyInIndex
).isEmpty();
1498 // get not selected files but updated in index to restore at the end
1500 if (partialSelection
) // otherwise notSel is for sure empty
1501 notSel
= getOtherFiles(selFiles
, optOnlyInIndex
);
1503 // create a patch with diffs between working directory and HEAD
1504 if (!mkPatchFromWorkDir(msg
, patchFile
, selFiles
))
1507 /* Step 2: Stash working directory modified files */
1508 if (partialSelection
) {
1509 errorReportingEnabled
= false;
1510 run("git stash"); // unfortunately 'git stash' is noisy on stderr
1511 errorReportingEnabled
= true;
1514 /* Step 3: Call stg import/fold */
1516 // setup a clean state
1517 if (!run("stg status --reset"))
1518 goto fail_and_unstash
;
1521 // update patch message before to fold, note that
1522 // command 'stg edit' requires stg version 0.14 or later
1523 if (!msg
.isEmpty() && !run("stg edit --message " + quote(msg
.trimmed())))
1524 goto fail_and_unstash
;
1526 if (!run("stg fold " + quote(patchFile
)))
1527 goto fail_and_unstash
;
1529 if (!run("stg refresh")) // refresh needed after fold
1530 goto fail_and_unstash
;
1532 } else if (!run("stg import --mail --name " + quote(patchName
) + " " + quote(patchFile
)))
1533 goto fail_and_unstash
;
1535 if (partialSelection
) {
1537 /* Step 4: Unstash and merge working directory modified files */
1538 errorReportingEnabled
= false;
1539 run("git stash pop"); // unfortunately 'git stash' is noisy on stderr
1540 errorReportingEnabled
= true;
1542 /* Step 5: restore not selected files that were already in index */
1543 if (!notSel
.empty() && !updateIndex(notSel
))
1552 if (partialSelection
) {
1554 errorReportingEnabled
= false;
1555 run("git stash pop");
1556 errorReportingEnabled
= true;
1561 dir
.remove(patchFile
);
1565 bool Git::makeBranch(SCRef sha
, SCRef branchName
) {
1567 return run("git branch " + branchName
+ " " + sha
);
1570 bool Git::makeTag(SCRef sha
, SCRef tagName
, SCRef msg
) {
1573 return run("git tag " + tagName
+ " " + sha
);
1575 return run("git tag -m \"" + msg
+ "\" " + tagName
+ " " + sha
);
1578 bool Git::deleteTag(SCRef sha
) {
1580 const QStringList
tags(getRefName(sha
, TAG
));
1582 return run("git tag -d " + tags
.first()); // only one
1587 bool Git::stgPush(SCRef sha
) {
1589 const QStringList
patch(getRefName(sha
, UN_APPLIED
));
1590 if (patch
.count() != 1) {
1591 dbp("ASSERT in Git::stgPush, found %1 patches instead of 1", patch
.count());
1594 return run("stg push " + quote(patch
.first()));
1597 bool Git::stgPop(SCRef sha
) {
1599 const QStringList
patch(getRefName(sha
, APPLIED
));
1600 if (patch
.count() != 1) {
1601 dbp("ASSERT in Git::stgPop, found %1 patches instead of 1", patch
.count());
1604 return run("stg pop " + quote(patch
));
1608 //! cache for dates conversion. Common among qgit windows
1609 static QHash
<QString
, QString
> localDates
;
1611 * Accesses a cache that avoids slow date calculation
1614 * the reference from which we want to get the date
1617 * human-readable date
1619 const QString
Git::getLocalDate(SCRef gitDate
) {
1620 QString
localDate(localDates
.value(gitDate
));
1623 if (localDate
.isEmpty()) {
1625 d
.setTime_t(gitDate
.toUInt());
1626 localDate
= d
.toString(Qt::SystemLocaleShortDate
);
1629 localDates
[gitDate
] = localDate
;
1635 const QStringList
Git::getArgs(bool* quit
, bool repoChanged
) {
1639 for (int i
= 1; i
< qApp
->argc(); i
++) {
1640 // in arguments with spaces double quotes
1641 // are stripped by Qt, so re-add them
1642 QString
arg(qApp
->argv()[i
]);
1643 if (arg
.contains(' '))
1644 arg
.prepend('\"').append('\"');
1646 args
.append(arg
+ ' ');
1649 if (testFlag(RANGE_SELECT_F
) && (!startup
|| args
.isEmpty())) {
1651 RangeSelectImpl
rs((QWidget
*)parent(), &args
, repoChanged
, this);
1652 *quit
= (rs
.exec() == QDialog::Rejected
); // modal execution
1654 return QStringList();
1657 return MyProcess::splitArgList(args
);
1660 bool Git::getGitDBDir(SCRef wd
, QString
& gd
, bool& changed
) {
1661 // we could run from a subdirectory, so we need to get correct directories
1663 QString runOutput
, tmp(workDir
);
1665 errorReportingEnabled
= false;
1666 bool success
= run("git rev-parse --git-dir", &runOutput
); // run under newWorkDir
1667 errorReportingEnabled
= true;
1669 runOutput
= runOutput
.trimmed();
1671 // 'git rev-parse --git-dir' output could be a relative
1672 // to working directory (as ex .git) or an absolute path
1673 QDir
d(runOutput
.startsWith("/") ? runOutput
: wd
+ "/" + runOutput
);
1674 changed
= (d
.absolutePath() != gitDir
);
1675 gd
= d
.absolutePath();
1680 bool Git::getBaseDir(SCRef wd
, QString
& bd
, bool& changed
) {
1681 // we could run from a subdirectory, so we need to get correct directories
1683 // We use --show-cdup and not --git-dir for this, in order to take into account configurations
1684 // in which .git is indeed a "symlink", a text file containing the path of the actual .git database dir.
1685 // In that particular case, the parent directory of the one given by --git-dir is *not* necessarily
1686 // the base directory of the repository.
1688 QString runOutput
, tmp(workDir
);
1690 errorReportingEnabled
= false;
1691 bool success
= run("git rev-parse --show-cdup", &runOutput
); // run under newWorkDir
1692 errorReportingEnabled
= true;
1694 runOutput
= runOutput
.trimmed();
1696 // 'git rev-parse --show-cdup' is relative to working directory.
1697 QDir
d(wd
+ "/" + runOutput
);
1698 bd
= d
.absolutePath();
1699 changed
= (bd
!= workDir
);
1708 Git::Reference
* Git::lookupOrAddReference(const ShaString
& sha
) {
1709 RefMap::iterator
it(refsShaMap
.find(sha
));
1710 if (it
== refsShaMap
.end()) it
= refsShaMap
.insert(sha
, Reference());
1714 Git::Reference
* Git::lookupReference(const ShaString
& sha
) {
1715 RefMap::iterator
it(refsShaMap
.find(sha
));
1716 if (it
== refsShaMap
.end()) return 0;
1720 bool Git::getRefs() {
1722 // check for a StGIT stack
1724 QString stgCurBranch
;
1725 if (d
.exists("patches")) { // early skip
1726 errorReportingEnabled
= false;
1727 isStGIT
= run("stg branch", &stgCurBranch
); // slow command
1728 errorReportingEnabled
= true;
1729 stgCurBranch
= stgCurBranch
.trimmed();
1733 // check for a merge and read current branch sha
1734 isMergeHead
= d
.exists("MERGE_HEAD");
1735 QString curBranchSHA
, curBranchName
;
1736 if (!run("git rev-parse --revs-only HEAD", &curBranchSHA
))
1739 if (!run("git branch", &curBranchName
))
1742 curBranchSHA
= curBranchSHA
.trimmed();
1743 curBranchName
= curBranchName
.prepend('\n').section("\n*", 1);
1744 curBranchName
= curBranchName
.section('\n', 0, 0).trimmed();
1746 // read refs, normally unsorted
1748 if (!run("git show-ref -d", &runOutput
))
1752 shaBackupBuf
.clear(); // revs are already empty now
1755 QStringList patchNames
, patchShas
;
1756 const QStringList
rLst(runOutput
.split('\n', QString::SkipEmptyParts
));
1757 FOREACH_SL (it
, rLst
) {
1759 SCRef revSha
= (*it
).left(40);
1760 SCRef refName
= (*it
).mid(41);
1762 if (refName
.startsWith("refs/patches/")) {
1764 // save StGIT patch sha, to be used later
1765 SCRef
patchesDir("refs/patches/" + stgCurBranch
+ "/");
1766 if (refName
.startsWith(patchesDir
)) {
1767 patchNames
.append(refName
.mid(patchesDir
.length()));
1768 patchShas
.append(revSha
);
1770 // StGIT patches should not be added to refs,
1771 // but an applied StGIT patch could be also an head or
1772 // a tag in this case will be added in another loop cycle
1775 // one rev could have many tags
1776 Reference
* cur
= lookupOrAddReference(toPersistentSha(revSha
, shaBackupBuf
));
1778 if (refName
.startsWith("refs/tags/")) {
1780 if (refName
.endsWith("^{}")) { // tag dereference
1782 // we assume that a tag dereference follows strictly
1783 // the corresponding tag object in rLst. So the
1784 // last added tag is a tag object, not a commit object
1785 cur
->tags
.append(refName
.mid(10, refName
.length() - 13));
1787 // store tag object. Will be used to fetching
1788 // tag message (if any) when necessary.
1789 cur
->tagObj
= prevRefSha
;
1791 // tagObj must be removed from ref map
1792 if (!prevRefSha
.isEmpty())
1793 refsShaMap
.remove(toTempSha(prevRefSha
));
1796 cur
->tags
.append(refName
.mid(10));
1800 } else if (refName
.startsWith("refs/heads/")) {
1802 cur
->branches
.append(refName
.mid(11));
1803 cur
->type
|= BRANCH
;
1804 if (curBranchSHA
== revSha
) {
1805 cur
->type
|= CUR_BRANCH
;
1806 cur
->currentBranch
= curBranchName
;
1808 } else if (refName
.startsWith("refs/remotes/") && !refName
.endsWith("HEAD")) {
1810 cur
->remoteBranches
.append(refName
.mid(13));
1811 cur
->type
|= RMT_BRANCH
;
1813 } else if (!refName
.startsWith("refs/bases/") && !refName
.endsWith("HEAD")) {
1815 cur
->refs
.append(refName
);
1818 prevRefSha
= revSha
;
1820 if (isStGIT
&& !patchNames
.isEmpty())
1821 parseStGitPatches(patchNames
, patchShas
);
1823 return !refsShaMap
.empty();
1826 void Git::parseStGitPatches(SCList patchNames
, SCList patchShas
) {
1828 patchesStillToFind
= 0;
1830 // get patch names and status of current branch
1832 if (!run("stg series", &runOutput
))
1835 const QStringList
pl(runOutput
.split('\n', QString::SkipEmptyParts
));
1836 FOREACH_SL (it
, pl
) {
1838 SCRef status
= (*it
).left(1);
1839 SCRef patchName
= (*it
).mid(2);
1841 bool applied
= (status
== "+" || status
== ">");
1842 int pos
= patchNames
.indexOf(patchName
);
1844 dbp("ASSERT in Git::parseStGitPatches(), patch %1 "
1845 "not found in references list.", patchName
);
1848 const ShaString
& ss
= toPersistentSha(patchShas
.at(pos
), shaBackupBuf
);
1849 Reference
* cur
= lookupOrAddReference(ss
);
1850 cur
->stgitPatch
= patchName
;
1851 cur
->type
|= (applied
? APPLIED
: UN_APPLIED
);
1854 patchesStillToFind
++;
1858 const QStringList
Git::getOthersFiles() {
1859 // add files present in working directory but not in git archive
1861 QString
runCmd("git ls-files --others");
1863 QString
exFile(settings
.value(EX_KEY
, EX_DEF
).toString());
1864 if (!exFile
.isEmpty()) {
1865 QString path
= (exFile
.startsWith("/")) ? exFile
: workDir
+ "/" + exFile
;
1866 if (QFile::exists(path
))
1867 runCmd
.append(" --exclude-from=" + quote(exFile
));
1869 QString
exPerDir(settings
.value(EX_PER_DIR_KEY
, EX_PER_DIR_DEF
).toString());
1870 if (!exPerDir
.isEmpty())
1871 runCmd
.append(" --exclude-per-directory=" + quote(exPerDir
));
1874 run(runCmd
, &runOutput
);
1875 return runOutput
.split('\n', QString::SkipEmptyParts
);
1878 Rev
* Git::fakeRevData(SCRef sha
, SCList parents
, SCRef author
, SCRef date
, SCRef log
, SCRef longLog
,
1879 SCRef patch
, int idx
, FileHistory
* fh
) {
1881 QString
data('>' + sha
+ 'X' + parents
.join(" ") + " \n");
1882 data
.append(author
+ '\n' + author
+ '\n' + date
+ '\n');
1883 data
.append(log
+ '\n' + longLog
);
1885 QString
header("log size " + QString::number(QByteArray(data
.toAscii()).length() - 1) + '\n');
1886 data
.prepend(header
);
1887 if (!patch
.isEmpty())
1888 data
.append('\n' + patch
);
1890 QByteArray
* ba
= new QByteArray(data
.toAscii());
1893 fh
->rowData
.append(ba
);
1895 Rev
* c
= new Rev(*ba
, 0, idx
, &dummy
, !isMainHistory(fh
));
1899 const Rev
* Git::fakeWorkDirRev(SCRef parent
, SCRef log
, SCRef longLog
, int idx
, FileHistory
* fh
) {
1902 if (!isMainHistory(fh
))
1903 patch
= getWorkDirDiff(fh
->fileNames().first());
1905 QString
date(QString::number(QDateTime::currentDateTime().toTime_t()));
1906 QString
author("-");
1907 QStringList
parents(parent
);
1908 Rev
* c
= fakeRevData(ZERO_SHA
, parents
, author
, date
, log
, longLog
, patch
, idx
, fh
);
1909 c
->isDiffCache
= true;
1910 c
->lanes
.append(EMPTY
);
1914 const RevFile
* Git::fakeWorkDirRevFile(const WorkingDirInfo
& wd
) {
1917 RevFile
* rf
= new RevFile();
1918 parseDiffFormat(*rf
, wd
.diffIndex
, fl
);
1919 rf
->onlyModified
= false;
1921 FOREACH_SL (it
, wd
.otherFiles
) {
1923 appendFileName(*rf
, *it
, fl
);
1924 rf
->status
.append(RevFile::UNKNOWN
);
1925 rf
->mergeParent
.append(1);
1927 RevFile cachedFiles
;
1928 parseDiffFormat(cachedFiles
, wd
.diffIndexCached
, fl
);
1931 for (int i
= 0; i
< rf
->count(); i
++)
1932 if (findFileIndex(cachedFiles
, filePath(*rf
, i
)) != -1)
1933 rf
->status
[i
] |= RevFile::IN_INDEX
;
1937 void Git::getDiffIndex() {
1940 if (!run("git status", &status
)) // git status refreshes the index, run as first
1944 if (!run("git rev-parse --revs-only HEAD", &head
))
1947 head
= head
.trimmed();
1948 if (!head
.isEmpty()) { // repository initialized but still no history
1950 if (!run("git diff-index " + head
, &workingDirInfo
.diffIndex
))
1953 // check for files already updated in cache, we will
1954 // save this information in status third field
1955 if (!run("git diff-index --cached " + head
, &workingDirInfo
.diffIndexCached
))
1958 // get any file not in tree
1959 workingDirInfo
.otherFiles
= getOthersFiles();
1961 // now mockup a RevFile
1962 revsFiles
.insert(ZERO_SHA_RAW
, fakeWorkDirRevFile(workingDirInfo
));
1964 // then mockup the corresponding Rev
1965 SCRef log
= (isNothingToCommit() ? "Nothing to commit" : "Working directory changes");
1966 const Rev
* r
= fakeWorkDirRev(head
, log
, status
, revData
->revOrder
.count(), revData
);
1967 revData
->revs
.insert(ZERO_SHA_RAW
, r
);
1968 revData
->revOrder
.append(ZERO_SHA_RAW
);
1969 revData
->earlyOutputCntBase
= revData
->revOrder
.count();
1971 // finally send it to GUI
1972 emit
newRevsAdded(revData
, revData
->revOrder
);
1975 void Git::parseDiffFormatLine(RevFile
& rf
, SCRef line
, int parNum
, FileNamesLoader
& fl
) {
1977 if (line
[1] == ':') { // it's a combined merge
1979 /* For combined merges rename/copy information is useless
1980 * because nor the original file name, nor similarity info
1981 * is given, just the status tracks that in the left/right
1982 * branch a renamed/copy occurred (as example status could
1983 * be RM or MR). For visualization purposes we could consider
1984 * the file as modified
1986 appendFileName(rf
, line
.section('\t', -1), fl
);
1988 rf
.mergeParent
.append(parNum
);
1989 } else { // faster parsing in normal case
1991 if (line
.at(98) == '\t') {
1992 appendFileName(rf
, line
.mid(99), fl
);
1993 setStatus(rf
, line
.at(97));
1994 rf
.mergeParent
.append(parNum
);
1996 // it's a rename or a copy, we are not in fast path now!
1997 setExtStatus(rf
, line
.mid(97), parNum
, fl
);
2001 //CT TODO can go in RevFile
2002 void Git::setStatus(RevFile
& rf
, SCRef rowSt
) {
2004 char status
= rowSt
.at(0).toLatin1();
2009 rf
.status
.append(RevFile::MODIFIED
);
2012 rf
.status
.append(RevFile::DELETED
);
2013 rf
.onlyModified
= false;
2016 rf
.status
.append(RevFile::NEW
);
2017 rf
.onlyModified
= false;
2020 rf
.status
.append(RevFile::UNKNOWN
);
2021 rf
.onlyModified
= false;
2024 dbp("ASSERT in Git::setStatus, unknown status <%1>. "
2025 "'MODIFIED' will be used instead.", rowSt
);
2026 rf
.status
.append(RevFile::MODIFIED
);
2031 void Git::setExtStatus(RevFile
& rf
, SCRef rowSt
, int parNum
, FileNamesLoader
& fl
) {
2033 const QStringList
sl(rowSt
.split('\t', QString::SkipEmptyParts
));
2034 if (sl
.count() != 3) {
2035 dbp("ASSERT in setExtStatus, unexpected status string %1", rowSt
);
2038 // we want store extra info with format "orig --> dest (Rxx%)"
2039 // but git give us something like "Rxx\t<orig>\t<dest>"
2043 const QString
extStatusInfo(orig
+ " --> " + dest
+ " (" + type
+ "%)");
2046 NOTE: we set rf.extStatus size equal to position of latest
2047 copied/renamed file. So it can have size lower then
2048 rf.count() if after copied/renamed file there are
2049 others. Here we have no possibility to know final
2050 dimension of this RefFile. We are still in parsing.
2053 // simulate new file
2054 appendFileName(rf
, dest
, fl
);
2055 rf
.mergeParent
.append(parNum
);
2056 rf
.status
.append(RevFile::NEW
);
2057 rf
.extStatus
.resize(rf
.status
.size());
2058 rf
.extStatus
[rf
.status
.size() - 1] = extStatusInfo
;
2060 // simulate deleted orig file only in case of rename
2061 if (type
.at(0) == 'R') { // renamed file
2062 appendFileName(rf
, orig
, fl
);
2063 rf
.mergeParent
.append(parNum
);
2064 rf
.status
.append(RevFile::DELETED
);
2065 rf
.extStatus
.resize(rf
.status
.size());
2066 rf
.extStatus
[rf
.status
.size() - 1] = extStatusInfo
;
2068 rf
.onlyModified
= false;
2071 //CT TODO utility function; can go elsewhere
2072 void Git::parseDiffFormat(RevFile
& rf
, SCRef buf
, FileNamesLoader
& fl
) {
2074 int parNum
= 1, startPos
= 0, endPos
= buf
.indexOf('\n');
2075 while (endPos
!= -1) {
2077 SCRef line
= buf
.mid(startPos
, endPos
- startPos
);
2078 if (line
[0] == ':') // avoid sha's in merges output
2079 parseDiffFormatLine(rf
, line
, parNum
, fl
);
2083 startPos
= endPos
+ 1;
2084 endPos
= buf
.indexOf('\n', endPos
+ 99);
2088 bool Git::startParseProc(SCList initCmd
, FileHistory
* fh
, SCRef buf
) {
2090 DataLoader
* dl
= new DataLoader(this, fh
); // auto-deleted when done
2092 connect(this, SIGNAL(cancelLoading(const FileHistory
*)),
2093 dl
, SLOT(on_cancel(const FileHistory
*)));
2095 connect(dl
, SIGNAL(newDataReady(const FileHistory
*)),
2096 this, SLOT(on_newDataReady(const FileHistory
*)));
2098 connect(dl
, SIGNAL(loaded(FileHistory
*, ulong
, int,
2099 bool, const QString
&, const QString
&)), this,
2100 SLOT(on_loaded(FileHistory
*, ulong
, int,
2101 bool, const QString
&, const QString
&)));
2103 return dl
->start(initCmd
, workDir
, buf
);
2106 bool Git::startRevList(SCList args
, FileHistory
* fh
) {
2108 QString
baseCmd("git log --topo-order --no-color "
2111 "--log-size " // FIXME broken on Windows
2113 "--parents --boundary -z "
2114 "--pretty=format:%m%HX%PX%n%cn<%ce>%n%an<%ae>%n%at%n%s%n");
2116 // we don't need log message body for file history
2117 if (isMainHistory(fh
))
2118 baseCmd
.append("%b");
2120 QStringList
initCmd(baseCmd
.split(' '));
2121 if (!isMainHistory(fh
)) {
2123 NOTE: we don't use '--remove-empty' option because
2124 in case a file is deleted and then a new file with
2125 the same name is created again in the same directory
2126 then, with this option, file history is truncated to
2127 the file deletion revision.
2129 initCmd
<< QString("-r -m -p --full-index").split(' ');
2131 {} // initCmd << QString("--early-output"); currently disabled
2133 return startParseProc(initCmd
+ args
, fh
, QString());
2136 bool Git::startUnappliedList() {
2138 QStringList
unAppliedShaList(getAllRefSha(UN_APPLIED
));
2139 if (unAppliedShaList
.isEmpty())
2142 // WARNING: with this command 'git log' could send spurious
2143 // revs so we need some filter out logic during loading
2144 QString
cmd("git log --no-color --parents -z "
2147 "--log-size " // FIXME broken on Windows
2149 "--pretty=format:%m%HX%PX%n%an<%ae>%n%at%n%s%n%b ^HEAD");
2151 QStringList
sl(cmd
.split(' '));
2152 sl
<< unAppliedShaList
;
2153 return startParseProc(sl
, revData
, QString());
2156 void Git::stop(bool saveCache
) {
2157 // normally called when changing directory or closing
2159 EM_RAISE(exGitStopped
);
2161 // stop all data sending from process and asks them
2162 // to terminate. Note that process could still keep
2163 // running for a while although silently
2164 emit
cancelAllProcesses(); // non blocking
2166 // after cancelAllProcesses() procFinished() is not called anymore
2167 // TODO perhaps is better to call procFinished() also if process terminated
2168 // incorrectly as QProcess does. BUt first we need to fix FileView::on_loadCompleted()
2169 emit
fileNamesLoad(1, revsFiles
.count() - filesLoadingStartOfs
);
2171 if (cacheNeedsUpdate
&& saveCache
) {
2173 cacheNeedsUpdate
= false;
2174 if (!filesLoadingCurSha
.isEmpty()) // we are in the middle of a loading
2175 revsFiles
.remove(toTempSha(filesLoadingCurSha
)); // remove partial data
2177 if (!revsFiles
.isEmpty()) {
2178 SHOW_MSG("Saving cache. Please wait...");
2179 if (!Cache::save(gitDir
, revsFiles
, dirNamesVec
, fileNamesVec
))
2180 dbs("ERROR unable to save file names cache");
2185 void Git::clearRevs() {
2188 patchesStillToFind
= 0; // TODO TEST WITH FILTERING
2189 firstNonStGitPatch
= "";
2190 workingDirInfo
.clear();
2191 revsFiles
.remove(ZERO_SHA_RAW
);
2194 void Git::clearFileNames() {
2196 qDeleteAll(revsFiles
);
2198 fileNamesMap
.clear();
2199 dirNamesMap
.clear();
2200 dirNamesVec
.clear();
2201 fileNamesVec
.clear();
2202 revsFilesShaBackupBuf
.clear();
2203 cacheNeedsUpdate
= false;
2206 bool Git::init(SCRef wd
, bool askForRange
, const QStringList
* passedArgs
, bool overwriteArgs
, bool* quit
) {
2207 // normally called when changing git directory. Must be called after stop()
2212 /* we only update filtering info here, original arguments
2213 * are not overwritten. Only getArgs() can update arguments,
2214 * an exception is if flag overwriteArgs is set
2216 loadArguments
.filteredLoading
= (!overwriteArgs
&& passedArgs
!= NULL
);
2217 if (loadArguments
.filteredLoading
)
2218 loadArguments
.filterList
= *passedArgs
;
2220 if (overwriteArgs
) // in this case must be passedArgs != NULL
2221 loadArguments
.args
= *passedArgs
;
2224 setThrowOnStop(true);
2226 const QString
msg1("Path is '" + workDir
+ "' Loading ");
2228 // check if repository is valid
2230 isGIT
= getGitDBDir(wd
, gitDir
, repoChanged
);
2234 getBaseDir(wd
, workDir
, dummy
);
2237 fileCacheAccessed
= false;
2239 SHOW_MSG(msg1
+ "file names cache...");
2244 setThrowOnStop(false);
2249 // update text codec according to repo settings
2251 QTextCodec::setCodecForCStrings(getTextCodec(&dummy
));
2254 SHOW_MSG(msg1
+ "refs...");
2256 dbs("WARNING: no tags or heads found");
2258 // startup input range dialog
2260 if (startup
|| askForRange
) {
2261 loadArguments
.args
= getArgs(quit
, repoChanged
); // must be called with refs loaded
2263 setThrowOnStop(false);
2267 // load StGit unapplied patches, must be after getRefs()
2269 loadingUnAppliedPatches
= startUnappliedList();
2270 if (loadingUnAppliedPatches
) {
2272 SHOW_MSG(msg1
+ "StGIT unapplied patches...");
2273 setThrowOnStop(false);
2275 // we will continue with init2() at
2276 // the end of loading...
2282 setThrowOnStop(false);
2287 setThrowOnStop(false);
2289 if (isThrowOnStopRaised(i
, "initializing 1")) {
2293 const QString
info("Exception \'" + EM_DESC(i
) + "\' "
2294 "not handled in init...re-throw");
2302 const QString
msg1("Path is '" + workDir
+ "' Loading ");
2304 // after loading unapplied patch update base early output offset to
2305 // avoid losing unapplied patches at first early output event
2307 revData
->earlyOutputCntBase
= revData
->revOrder
.count();
2310 setThrowOnStop(true);
2312 // load working directory files
2313 if (!loadArguments
.filteredLoading
&& testFlag(DIFF_INDEX_F
)) {
2314 SHOW_MSG(msg1
+ "working directory changed files...");
2315 getDiffIndex(); // blocking, we could be in setRepository() now
2317 SHOW_MSG(msg1
+ "revisions...");
2319 // build up command line arguments
2320 QStringList
args(loadArguments
.args
);
2321 if (loadArguments
.filteredLoading
) {
2322 if (!args
.contains("--"))
2325 args
<< loadArguments
.filterList
;
2327 if (!startRevList(args
, revData
))
2328 SHOW_MSG("ERROR: unable to start 'git log'");
2330 setThrowOnStop(false);
2334 setThrowOnStop(false);
2336 if (isThrowOnStopRaised(i
, "initializing 2")) {
2340 const QString
info("Exception \'" + EM_DESC(i
) + "\' "
2341 "not handled in init2...re-throw");
2347 void Git::on_newDataReady(const FileHistory
* fh
) {
2349 emit
newRevsAdded(fh
, fh
->revOrder
);
2352 void Git::on_loaded(FileHistory
* fh
, ulong byteSize
, int loadTime
,
2353 bool normalExit
, SCRef cmd
, SCRef errorDesc
) {
2355 if (!errorDesc
.isEmpty()) {
2356 MainExecErrorEvent
* e
= new MainExecErrorEvent(cmd
, errorDesc
);
2357 QApplication::postEvent(parent(), e
);
2359 if (normalExit
) { // do not send anything if killed
2361 on_newDataReady(fh
);
2363 if (!loadingUnAppliedPatches
) {
2365 fh
->loadTime
+= loadTime
;
2367 ulong kb
= byteSize
/ 1024;
2368 double mbs
= (double)byteSize
/ fh
->loadTime
/ 1000;
2370 tmp
.sprintf("Loaded %i revisions (%li KB), "
2371 "time elapsed: %i ms (%.2f MB/s)",
2372 fh
->revs
.count(), kb
, fh
->loadTime
, mbs
);
2374 if (!tryFollowRenames(fh
))
2375 emit
loadCompleted(fh
, tmp
);
2377 if (isMainHistory(fh
))
2378 // wait the dust to settle down before to start
2379 // background file names loading for new revisions
2380 QTimer::singleShot(500, this, SLOT(loadFileNames()));
2383 if (loadingUnAppliedPatches
) {
2384 loadingUnAppliedPatches
= false;
2385 revData
->lns
->clear(); // again to reset lanes
2386 init2(); // continue with loading of remaining revisions
2390 bool Git::tryFollowRenames(FileHistory
* fh
) {
2392 if (isMainHistory(fh
))
2395 QStringList oldNames
;
2396 QMutableStringListIterator
it(fh
->renamedRevs
);
2397 while (it
.hasNext())
2398 if (!populateRenamedPatches(it
.next(), fh
->curFNames
, fh
, &oldNames
, false))
2401 if (fh
->renamedRevs
.isEmpty())
2405 args
<< fh
->renamedRevs
<< "--" << oldNames
;
2406 fh
->fNames
<< oldNames
;
2407 fh
->curFNames
= oldNames
;
2408 fh
->renamedRevs
.clear();
2409 return startRevList(args
, fh
);
2412 bool Git::populateRenamedPatches(SCRef renamedSha
, SCList newNames
, FileHistory
* fh
,
2413 QStringList
* oldNames
, bool backTrack
) {
2416 if (!run("git diff-tree -r -M " + renamedSha
, &runOutput
))
2419 // find the first renamed file with the new file name in renamedFiles list
2421 FOREACH_SL (it
, newNames
) {
2423 line
= runOutput
.section('\t' + *it
+ '\t', 0, 0,
2424 QString::SectionIncludeTrailingSep
);
2427 line
= runOutput
.section('\t' + *it
+ '\n', 0, 0);
2429 if (!line
.isEmpty())
2432 if (line
.contains('\n'))
2433 line
= line
.section('\n', -1, -1);
2435 SCRef status
= line
.section('\t', -2, -2).section(' ', -1, -1);
2436 if (!status
.startsWith('R'))
2440 SCRef nextFile
= runOutput
.section(line
, 1, 1).section('\t', 1, 1);
2441 oldNames
->append(nextFile
.section('\n', 0, 0));
2444 // get the diff betwen two files
2445 SCRef prevFileSha
= line
.section(' ', 2, 2);
2446 SCRef lastFileSha
= line
.section(' ', 3, 3);
2447 if (prevFileSha
== lastFileSha
) // just renamed
2449 else if (!run("git diff --no-ext-diff -r --full-index " + prevFileSha
+ " " + lastFileSha
, &runOutput
))
2452 SCRef prevFile
= line
.section('\t', -1, -1);
2453 if (!oldNames
->contains(prevFile
))
2454 oldNames
->append(prevFile
);
2456 // save the patch, will be used later to create a
2457 // proper graft sha with correct parent info
2459 QString
tmp(!runOutput
.isEmpty() ? runOutput
: "diff --no-ext-diff --\nsimilarity index 100%\n");
2460 fh
->renamedPatches
.insert(renamedSha
, tmp
);
2465 void Git::populateFileNamesMap() {
2467 for (int i
= 0; i
< dirNamesVec
.count(); ++i
)
2468 dirNamesMap
.insert(dirNamesVec
[i
], i
);
2470 for (int i
= 0; i
< fileNamesVec
.count(); ++i
)
2471 fileNamesMap
.insert(fileNamesVec
[i
], i
);
2474 void Git::loadFileCache() {
2476 if (!fileCacheAccessed
) {
2478 fileCacheAccessed
= true;
2480 if (Cache::load(gitDir
, revsFiles
, dirNamesVec
, fileNamesVec
, shaBuf
)) {
2481 revsFilesShaBackupBuf
.append(shaBuf
);
2482 populateFileNamesMap();
2484 dbs("ERROR: unable to load file names cache");
2488 void Git::loadFileNames() {
2490 indexTree(); // we are sure data loading is finished at this point
2493 QString diffTreeBuf
;
2494 FOREACH (ShaVect
, it
, revData
->revOrder
) {
2496 if (!revsFiles
.contains(*it
)) {
2497 const Rev
* c
= revLookup(*it
);
2498 if (c
->parentsCount() == 1) { // skip initials and merges
2499 diffTreeBuf
.append(*it
).append('\n');
2504 if (!diffTreeBuf
.isEmpty()) {
2505 filesLoadingPending
= filesLoadingCurSha
= "";
2506 filesLoadingStartOfs
= revsFiles
.count();
2507 emit
fileNamesLoad(3, revCnt
);
2509 const QString
runCmd("git diff-tree --no-color -r -C --stdin");
2510 runAsync(runCmd
, this, diffTreeBuf
);
2514 bool Git::filterEarlyOutputRev(FileHistory
* fh
, Rev
* rev
) {
2516 if (fh
->earlyOutputCnt
< fh
->revOrder
.count()) {
2518 const ShaString
& sha
= fh
->revOrder
[fh
->earlyOutputCnt
++];
2519 const Rev
* c
= revLookup(sha
, fh
);
2521 if (rev
->sha() != sha
|| rev
->parents() != c
->parents()) {
2522 // mismatch found! set correct value, 'rev' will
2523 // overwrite 'c' upon returning
2524 rev
->orderIdx
= c
->orderIdx
;
2525 revData
->clear(false); // flush the tail
2527 return true; // filter out 'rev'
2530 // we have new revisions, exit from early output state
2531 fh
->setEarlyOutputState(false);
2535 int Git::addChunk(FileHistory
* fh
, const QByteArray
& ba
, int start
) {
2537 RevMap
& r
= fh
->revs
;
2542 // only here we create a new rev
2543 rev
= new Rev(ba
, start
, fh
->revOrder
.count(), &nextStart
, !isMainHistory(fh
));
2545 if (nextStart
== -2) {
2547 fh
->setEarlyOutputState(true);
2548 start
= ba
.indexOf('\n', start
) + 1;
2551 } while (nextStart
== -2);
2553 if (nextStart
== -1) { // half chunk detected
2558 const ShaString
& sha
= rev
->sha();
2560 if (fh
->earlyOutputCnt
!= -1 && filterEarlyOutputRev(fh
, rev
)) {
2566 if (loadingUnAppliedPatches
) { // filter out possible spurious revs
2568 Reference
* rf
= lookupReference(sha
);
2569 if (!(rf
&& (rf
->type
& UN_APPLIED
))) {
2574 // remove StGIT spurious revs filter
2575 if (!firstNonStGitPatch
.isEmpty() && firstNonStGitPatch
== sha
)
2576 firstNonStGitPatch
= "";
2578 // StGIT called with --all option creates spurious revs so filter
2579 // out unknown revs until no more StGIT patches are waited and
2580 // firstNonStGitPatch is reached
2581 if (!(firstNonStGitPatch
.isEmpty() && patchesStillToFind
== 0) &&
2582 !loadingUnAppliedPatches
&& isMainHistory(fh
)) {
2584 Reference
* rf
= lookupReference(sha
);
2585 if (!(rf
&& (rf
->type
& APPLIED
))) {
2590 if (r
.contains(sha
)) {
2591 // StGIT unapplied patches could be sent again by
2592 // 'git log' as example if called with --all option.
2593 if (r
[sha
]->isUnApplied
) {
2597 // could be a side effect of 'git log -m', see below
2598 if (isMainHistory(fh
) || rev
->parentsCount() < 2)
2599 dbp("ASSERT: addChunk sha <%1> already received", sha
);
2602 if (r
.isEmpty() && !isMainHistory(fh
)) {
2603 bool added
= copyDiffIndex(fh
, sha
);
2604 rev
->orderIdx
= added
? 1 : 0;
2606 if ( !isMainHistory(fh
)
2607 && !fh
->renamedPatches
.isEmpty()
2608 && fh
->renamedPatches
.contains(sha
)) {
2610 // this is the new rev with renamed file, the rev is correct but
2611 // the patch, create a new rev with proper patch and use that instead
2612 const Rev
* prevSha
= revLookup(sha
, fh
);
2613 Rev
* c
= fakeRevData(sha
, rev
->parents(), rev
->author(),
2614 rev
->authorDate(), rev
->shortLog(), rev
->longLog(),
2615 fh
->renamedPatches
[sha
], prevSha
->orderIdx
, fh
);
2617 r
.insert(sha
, c
); // overwrite old content
2618 fh
->renamedPatches
.remove(sha
);
2621 if (!isMainHistory(fh
) && rev
->parentsCount() > 1 && r
.contains(sha
)) {
2622 /* In this case git log is called with -m option and merges are splitted
2623 in one commit per parent but all them have the same sha.
2624 So we add only the first to fh->revOrder to display history correctly,
2625 but we nevertheless add all the commits to 'r' so that annotation code
2626 can get the patches.
2631 mergeSha
= QString::number(++i
) + " m " + sha
;
2632 while (r
.contains(toTempSha(mergeSha
)));
2634 const ShaString
& ss
= toPersistentSha(mergeSha
, shaBackupBuf
);
2638 fh
->revOrder
.append(sha
);
2640 if (rev
->parentsCount() == 0 && !isMainHistory(fh
))
2641 fh
->renamedRevs
.append(sha
);
2644 // updateLanes() is called too late, after loadingUnAppliedPatches
2645 // has been reset so update the lanes now.
2646 if (loadingUnAppliedPatches
) {
2648 Rev
* c
= const_cast<Rev
*>(revLookup(sha
, fh
));
2649 c
->isUnApplied
= true;
2650 c
->lanes
.append(UNAPPLIED
);
2652 } else if (patchesStillToFind
> 0 || !isMainHistory(fh
)) { // try to avoid costly lookup
2654 Reference
* rf
= lookupReference(sha
);
2655 if (rf
&& (rf
->type
& APPLIED
)) {
2657 Rev
* c
= const_cast<Rev
*>(revLookup(sha
, fh
));
2658 c
->isApplied
= true;
2659 if (isMainHistory(fh
)) {
2660 patchesStillToFind
--;
2661 if (patchesStillToFind
== 0)
2662 // any rev will be discarded until
2663 // firstNonStGitPatch arrives
2664 firstNonStGitPatch
= c
->parent(0);
2672 bool Git::copyDiffIndex(FileHistory
* fh
, SCRef parent
) {
2673 // must be called with empty revs and empty revOrder
2675 if (!fh
->revOrder
.isEmpty() || !fh
->revs
.isEmpty()) {
2676 dbs("ASSERT in copyDiffIndex: called with wrong context");
2679 const Rev
* r
= revLookup(ZERO_SHA
);
2683 const RevFile
* files
= getFiles(ZERO_SHA
);
2684 if (!files
|| findFileIndex(*files
, fh
->fileNames().first()) == -1)
2687 // insert a custom ZERO_SHA rev with proper parent
2688 const Rev
* rf
= fakeWorkDirRev(parent
, "Working directory changes", "long log\n", 0, fh
);
2689 fh
->revs
.insert(ZERO_SHA_RAW
, rf
);
2690 fh
->revOrder
.append(ZERO_SHA_RAW
);
2694 void Git::setLane(SCRef sha
, FileHistory
* fh
) {
2697 uint i
= fh
->firstFreeLane
;
2698 QVector
<QByteArray
> ba
;
2699 const ShaString
& ss
= toPersistentSha(sha
, ba
);
2700 const ShaVect
& shaVec(fh
->revOrder
);
2702 for (uint cnt
= shaVec
.count(); i
< cnt
; ++i
) {
2704 const ShaString
& curSha
= shaVec
[i
];
2705 Rev
* r
= const_cast<Rev
*>(revLookup(curSha
, fh
));
2706 if (r
->lanes
.count() == 0)
2707 updateLanes(*r
, *l
, curSha
);
2712 fh
->firstFreeLane
= ++i
;
2715 void Git::updateLanes(Rev
& c
, Lanes
& lns
, SCRef sha
) {
2716 // we could get third argument from c.sha(), but we are in fast path here
2717 // and c.sha() involves a deep copy, so we accept a little redundancy
2722 bool isDiscontinuity
;
2723 bool isFork
= lns
.isFork(sha
, isDiscontinuity
);
2724 bool isMerge
= (c
.parentsCount() > 1);
2725 bool isInitial
= (c
.parentsCount() == 0);
2727 if (isDiscontinuity
)
2728 lns
.changeActiveLane(sha
); // uses previous isBoundary state
2730 lns
.setBoundary(c
.isBoundary()); // update must be here
2735 lns
.setMerge(c
.parents());
2741 lns
.getLanes(c
.lanes
); // here lanes are snapshotted
2743 SCRef nextSha
= (isInitial
) ? "" : QString(c
.parent(0));
2745 lns
.nextParent(nextSha
);
2756 // QString tmp = "", tmp2;
2757 // for (uint i = 0; i < c.lanes.count(); i++) {
2758 // tmp2.setNum(c.lanes[i]);
2759 // tmp.append(tmp2 + "-");
2761 // qDebug("%s %s", tmp.toUtf8().data(), sha.toUtf8().data());
2764 void Git::procFinished() {
2766 flushFileNames(fileLoader
);
2767 filesLoadingPending
= filesLoadingCurSha
= "";
2768 emit
fileNamesLoad(1, revsFiles
.count() - filesLoadingStartOfs
);
2771 void Git::procReadyRead(const QByteArray
& fileChunk
) {
2773 if (filesLoadingPending
.isEmpty())
2774 filesLoadingPending
= fileChunk
;
2776 filesLoadingPending
.append(fileChunk
); // add to previous half lines
2779 if (!filesLoadingCurSha
.isEmpty() && revsFiles
.contains(toTempSha(filesLoadingCurSha
)))
2780 rf
= const_cast<RevFile
*>(revsFiles
[toTempSha(filesLoadingCurSha
)]);
2782 int nextEOL
= filesLoadingPending
.indexOf('\n');
2784 while (nextEOL
!= -1) {
2786 SCRef
line(filesLoadingPending
.mid(lastEOL
+ 1, nextEOL
- lastEOL
- 1));
2787 if (line
.at(0) != ':') {
2788 SCRef sha
= line
.left(40);
2789 if (!rf
|| sha
!= filesLoadingCurSha
) { // new commit
2791 revsFiles
.insert(toPersistentSha(sha
, revsFilesShaBackupBuf
), rf
);
2792 filesLoadingCurSha
= sha
;
2793 cacheNeedsUpdate
= true;
2795 dbp("ASSERT: repeated sha %1 in file names loading", sha
);
2796 } else // line.constref(0) == ':'
2797 parseDiffFormatLine(*rf
, line
, 1, fileLoader
);
2800 nextEOL
= filesLoadingPending
.indexOf('\n', lastEOL
+ 1);
2803 filesLoadingPending
.remove(0, lastEOL
+ 1);
2805 emit
fileNamesLoad(2, revsFiles
.count() - filesLoadingStartOfs
);
2808 void Git::flushFileNames(FileNamesLoader
& fl
) {
2813 QByteArray
& b
= fl
.rf
->pathsIdx
;
2814 QVector
<int>& dirs
= fl
.rfDirs
;
2817 b
.resize(2 * dirs
.size() * sizeof(int));
2819 int* d
= (int*)(b
.data());
2821 for (int i
= 0; i
< dirs
.size(); i
++) {
2824 d
[dirs
.size() + i
] = fl
.rfNames
.at(i
);
2831 void Git::appendFileName(RevFile
& rf
, SCRef name
, FileNamesLoader
& fl
) {
2837 int idx
= name
.lastIndexOf('/') + 1;
2838 SCRef dr
= name
.left(idx
);
2839 SCRef nm
= name
.mid(idx
);
2841 QHash
<QString
, int>::const_iterator
it(dirNamesMap
.constFind(dr
));
2842 if (it
== dirNamesMap
.constEnd()) {
2843 int idx
= dirNamesVec
.count();
2844 dirNamesMap
.insert(dr
, idx
);
2845 dirNamesVec
.append(dr
);
2846 fl
.rfDirs
.append(idx
);
2848 fl
.rfDirs
.append(*it
);
2850 it
= fileNamesMap
.constFind(nm
);
2851 if (it
== fileNamesMap
.constEnd()) {
2852 int idx
= fileNamesVec
.count();
2853 fileNamesMap
.insert(nm
, idx
);
2854 fileNamesVec
.append(nm
);
2855 fl
.rfNames
.append(idx
);
2857 fl
.rfNames
.append(*it
);
2860 void Git::updateDescMap(const Rev
* r
,uint idx
, QHash
<QPair
<uint
, uint
>, bool>& dm
,
2861 QHash
<uint
, QVector
<int> >& dv
) {
2863 QVector
<int> descVec
;
2864 if (r
->descRefsMaster
!= -1) {
2866 const Rev
* tmp
= revLookup(revData
->revOrder
[r
->descRefsMaster
]);
2867 const QVector
<int>& nr
= tmp
->descRefs
;
2869 for (int i
= 0; i
< nr
.count(); i
++) {
2871 if (!dv
.contains(nr
[i
])) {
2872 dbp("ASSERT descendant for %1 not found", r
->sha());
2875 const QVector
<int>& dvv
= dv
[nr
[i
]];
2877 // copy the whole vector instead of each element
2878 // in the first iteration of the loop below
2879 descVec
= dvv
; // quick (shared) copy
2881 for (int y
= 0; y
< dvv
.count(); y
++) {
2883 uint v
= (uint
)dvv
[y
];
2884 QPair
<uint
, uint
> key
= qMakePair(idx
, v
);
2885 QPair
<uint
, uint
> keyN
= qMakePair(v
, idx
);
2886 dm
.insert(key
, true);
2887 dm
.insert(keyN
, false);
2889 // we don't want duplicated entry, otherwise 'dvv' grows
2890 // greatly in repos with many tagged development branches
2891 if (i
> 0 && !descVec
.contains(v
)) // i > 0 is rare, no
2892 descVec
.append(v
); // need to optimize
2896 descVec
.append(idx
);
2897 dv
.insert(idx
, descVec
);
2900 void Git::mergeBranches(Rev
* p
, const Rev
* r
) {
2902 int r_descBrnMaster
= (checkRef(r
->sha(), BRANCH
| RMT_BRANCH
) ? r
->orderIdx
: r
->descBrnMaster
);
2904 if (p
->descBrnMaster
== r_descBrnMaster
|| r_descBrnMaster
== -1)
2907 // we want all the descendant branches, so just avoid duplicates
2908 const QVector
<int>& src1
= revLookup(revData
->revOrder
[p
->descBrnMaster
])->descBranches
;
2909 const QVector
<int>& src2
= revLookup(revData
->revOrder
[r_descBrnMaster
])->descBranches
;
2910 QVector
<int> dst(src1
);
2911 for (int i
= 0; i
< src2
.count(); i
++)
2912 if (qFind(src1
.constBegin(), src1
.constEnd(), src2
[i
]) == src1
.constEnd())
2913 dst
.append(src2
[i
]);
2915 p
->descBranches
= dst
;
2916 p
->descBrnMaster
= p
->orderIdx
;
2919 void Git::mergeNearTags(bool down
, Rev
* p
, const Rev
* r
, const QHash
<QPair
<uint
, uint
>, bool>& dm
) {
2921 bool isTag
= checkRef(r
->sha(), TAG
);
2922 int r_descRefsMaster
= isTag
? r
->orderIdx
: r
->descRefsMaster
;
2923 int r_ancRefsMaster
= isTag
? r
->orderIdx
: r
->ancRefsMaster
;
2925 if (down
&& (p
->descRefsMaster
== r_descRefsMaster
|| r_descRefsMaster
== -1))
2928 if (!down
&& (p
->ancRefsMaster
== r_ancRefsMaster
|| r_ancRefsMaster
== -1))
2931 // we want the nearest tag only, so remove any tag
2932 // that is ancestor of any other tag in p U r
2933 const ShaVect
& ro
= revData
->revOrder
;
2934 const ShaString
& sha1
= down
? ro
[p
->descRefsMaster
] : ro
[p
->ancRefsMaster
];
2935 const ShaString
& sha2
= down
? ro
[r_descRefsMaster
] : ro
[r_ancRefsMaster
];
2936 const QVector
<int>& src1
= down
? revLookup(sha1
)->descRefs
: revLookup(sha1
)->ancRefs
;
2937 const QVector
<int>& src2
= down
? revLookup(sha2
)->descRefs
: revLookup(sha2
)->ancRefs
;
2938 QVector
<int> dst(src1
);
2940 for (int s2
= 0; s2
< src2
.count(); s2
++) {
2943 for (int s1
= 0; s1
< src1
.count(); s1
++) {
2945 if (src2
[s2
] == src1
[s1
]) {
2949 QPair
<uint
, uint
> key
= qMakePair((uint
)src2
[s2
], (uint
)src1
[s1
]);
2951 if (!dm
.contains(key
)) { // could be empty if all tags are independent
2952 add
= true; // could be an independent path
2955 add
= (down
&& dm
[key
]) || (!down
&& !dm
[key
]);
2957 dst
[s1
] = -1; // mark for removing
2962 dst
.append(src2
[s2
]);
2964 QVector
<int>& nearRefs
= (down
? p
->descRefs
: p
->ancRefs
);
2965 int& nearRefsMaster
= (down
? p
->descRefsMaster
: p
->ancRefsMaster
);
2968 for (int s2
= 0; s2
< dst
.count(); s2
++)
2970 nearRefs
.append(dst
[s2
]);
2972 nearRefsMaster
= p
->orderIdx
;
2975 void Git::indexTree() {
2977 const ShaVect
& ro
= revData
->revOrder
;
2978 if (ro
.count() == 0)
2981 // we keep the pairs(x, y). Value is true if x is
2982 // ancestor of y or false if y is ancestor of x
2983 QHash
<QPair
<uint
, uint
>, bool> descMap
;
2984 QHash
<uint
, QVector
<int> > descVect
;
2986 // walk down the tree from latest to oldest,
2987 // compute children and nearest descendants
2988 for (uint i
= 0, cnt
= ro
.count(); i
< cnt
; i
++) {
2990 uint type
= checkRef(ro
[i
]);
2991 bool isB
= (type
& (BRANCH
| RMT_BRANCH
));
2992 bool isT
= (type
& TAG
);
2994 const Rev
* r
= revLookup(ro
[i
]);
2997 Rev
* rr
= const_cast<Rev
*>(r
);
2998 if (r
->descBrnMaster
!= -1) {
2999 const ShaString
& sha
= ro
[r
->descBrnMaster
];
3000 rr
->descBranches
= revLookup(sha
)->descBranches
;
3002 rr
->descBranches
.append(i
);
3005 updateDescMap(r
, i
, descMap
, descVect
);
3006 Rev
* rr
= const_cast<Rev
*>(r
);
3007 rr
->descRefs
.clear();
3008 rr
->descRefs
.append(i
);
3010 for (uint y
= 0; y
< r
->parentsCount(); y
++) {
3012 Rev
* p
= const_cast<Rev
*>(revLookup(r
->parent(y
)));
3014 p
->children
.append(i
);
3016 if (p
->descBrnMaster
== -1)
3017 p
->descBrnMaster
= isB
? r
->orderIdx
: r
->descBrnMaster
;
3019 mergeBranches(p
, r
);
3021 if (p
->descRefsMaster
== -1)
3022 p
->descRefsMaster
= isT
? r
->orderIdx
: r
->descRefsMaster
;
3024 mergeNearTags(optGoDown
, p
, r
, descMap
);
3028 // walk backward through the tree and compute nearest tagged ancestors
3029 for (int i
= ro
.count() - 1; i
>= 0; i
--) {
3031 const Rev
* r
= revLookup(ro
[i
]);
3032 bool isTag
= checkRef(ro
[i
], TAG
);
3035 Rev
* rr
= const_cast<Rev
*>(r
);
3036 rr
->ancRefs
.clear();
3037 rr
->ancRefs
.append(i
);
3039 for (int y
= 0; y
< r
->children
.count(); y
++) {
3041 Rev
* c
= const_cast<Rev
*>(revLookup(ro
[r
->children
[y
]]));
3043 if (c
->ancRefsMaster
== -1)
3044 c
->ancRefsMaster
= isTag
? r
->orderIdx
:r
->ancRefsMaster
;
3046 mergeNearTags(!optGoDown
, c
, r
, descMap
);