File indexing completed on 2025-01-19 04:22:44
0001 /* 0002 SPDX-FileCopyrightText: 2021 Hamed Masafi <hamed.masfi@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-3.0-or-later 0005 */ 0006 0007 #include "logsmodel.h" 0008 #include "authorsmodel.h" 0009 #include "gitlog.h" 0010 #include "gitmanager.h" 0011 0012 #include <KLocalizedString> 0013 0014 namespace Git 0015 { 0016 0017 namespace Impl 0018 { 0019 0020 void readLine(const QString &line, const QString &separator, QList<QString *> list) 0021 { 0022 const auto parts = line.split(separator); 0023 if (parts.size() != list.size()) 0024 return; 0025 0026 for (auto i = 0; i < parts.size(); ++i) 0027 *list[i] = parts.at(i); 0028 } 0029 struct LanesFactory { 0030 QStringList _hashes; 0031 0032 QList<int> findByChild(const QString &hash) 0033 { 0034 int index{0}; 0035 QList<int> ret; 0036 for (auto const &h : std::as_const(_hashes)) { 0037 if (hash == h) 0038 ret.append(index); 0039 index++; 0040 } 0041 return ret; 0042 } 0043 0044 int indexOfChild(const QString &hash) 0045 { 0046 int index{0}; 0047 for (auto const &h : std::as_const(_hashes)) { 0048 if (hash == h) 0049 return index; 0050 index++; 0051 } 0052 return -1; 0053 } 0054 0055 QVector<GraphLane> initLanes(const QString &myHash, int &myIndex) 0056 { 0057 if (_hashes.empty()) 0058 return {}; 0059 0060 while (!_hashes.empty() && _hashes.last() == QString()) 0061 _hashes.removeLast(); 0062 0063 int index{0}; 0064 QVector<GraphLane> lanes; 0065 lanes.reserve(_hashes.size()); 0066 for (const auto &hash : std::as_const(_hashes)) { 0067 if (hash == QString()) { 0068 lanes.append(GraphLane::Transparent); 0069 } else { 0070 if (hash == myHash) { 0071 lanes.append(GraphLane::Node); 0072 myIndex = index; 0073 } else { 0074 lanes.append(hash == myHash ? GraphLane::Node : GraphLane::Pipe); 0075 } 0076 } 0077 index++; 0078 } 0079 return lanes; 0080 } 0081 0082 QList<int> setHashes(const QStringList &children, int myIndex) 0083 { 0084 QList<int> ret; 0085 bool myIndexSet{myIndex == -1}; 0086 int index; 0087 0088 for (const auto &h : children) { 0089 index = -1; 0090 if (!myIndexSet) { 0091 index = myIndex; 0092 myIndexSet = true; 0093 } 0094 if (index == -1) 0095 index = indexOfChild(QString()); 0096 0097 if (index == -1) { 0098 _hashes.append(h); 0099 index = _hashes.size() - 1; 0100 } else { 0101 _hashes.replace(index, h); 0102 } 0103 ret.append(index); 0104 } 0105 return ret; 0106 } 0107 0108 void start(const QString &hash, QVector<GraphLane> &lanes) 0109 { 0110 Q_UNUSED(hash) 0111 _hashes.append(QString()); 0112 set(_hashes.size() - 1, GraphLane::Start, lanes); 0113 } 0114 0115 void join(const QString &hash, QVector<GraphLane> &lanes, int &myIndex) 0116 { 0117 int firstIndex{-1}; 0118 const auto list = findByChild(hash); 0119 0120 for (auto i = list.begin(); i != list.end(); ++i) { 0121 if (firstIndex == -1) { 0122 firstIndex = *i; 0123 set(*i, list.contains(myIndex) ? GraphLane::Node : GraphLane::End, lanes); 0124 } else { 0125 auto lane = lanes.at(*i); 0126 lane.mBottomJoins.append(firstIndex); 0127 lane.mType = GraphLane::Transparent; 0128 set(*i, lane, lanes); 0129 } 0130 _hashes.replace(*i, QString()); 0131 } 0132 myIndex = firstIndex; 0133 } 0134 0135 void fork(const QStringList &childrenList, QVector<GraphLane> &lanes, int myInedx) 0136 { 0137 auto list = setHashes(childrenList, -1); 0138 auto children = childrenList; 0139 lanes.reserve(_hashes.size()); 0140 0141 if (myInedx != -1 && lanes.size() <= myInedx) 0142 lanes.resize(myInedx + 1); 0143 0144 if (myInedx != -1 && childrenList.size() == 1) { 0145 auto &l = lanes[list.first()]; 0146 0147 if (list.first() == myInedx) { 0148 if (l.type() == GraphLane::None) 0149 l.mType = GraphLane::Transparent; 0150 if (l.type() == GraphLane::End) 0151 l.mType = GraphLane::Node; 0152 } else { 0153 l.mUpJoins.append(myInedx); 0154 lanes[myInedx].mType = GraphLane::End; 0155 } 0156 0157 return; 0158 } 0159 for (int &i : list) { 0160 if (lanes.size() <= i) 0161 lanes.resize(i + 1); 0162 0163 auto &l = lanes[i]; 0164 if (i == myInedx) { 0165 l.mType = GraphLane::Node; 0166 } else { 0167 if (l.type() == GraphLane::None) 0168 l.mType = GraphLane::Transparent; 0169 if (l.type() == GraphLane::End) 0170 l.mType = GraphLane::Node; 0171 0172 l.mUpJoins.append(myInedx); 0173 } 0174 _hashes.replace(i, children.takeFirst()); 0175 } 0176 } 0177 0178 void set(int index, const GraphLane &lane, QVector<GraphLane> &lanes) 0179 { 0180 if (index < lanes.size()) 0181 lanes.replace(index, lane); 0182 else 0183 lanes.append(lane); 0184 } 0185 QVector<GraphLane> apply(Log *log) 0186 { 0187 int myIndex = -1; 0188 QVector<GraphLane> lanes = initLanes(log->commitHash(), myIndex); 0189 0190 if (!log->parents().empty()) 0191 join(log->commitHash(), lanes, myIndex); 0192 else if (!log->childs().empty()) { 0193 start(log->childs().first(), lanes); 0194 myIndex = _hashes.size() - 1; 0195 } 0196 0197 if (!log->childs().empty()) { 0198 fork(log->childs(), lanes, myIndex); 0199 } else if (myIndex != -1) { 0200 lanes[myIndex].mType = GraphLane::End; 0201 } 0202 0203 return lanes; 0204 } 0205 }; 0206 0207 } // namespace Impl 0208 0209 LogsModel::LogsModel(Manager *git, AuthorsModel *authorsModel, QObject *parent) 0210 : AbstractGitItemsModel(git, parent) 0211 , mAuthorsModel(authorsModel) 0212 { 0213 } 0214 0215 LogsModel::~LogsModel() 0216 { 0217 qDeleteAll(mData); 0218 mData.clear(); 0219 } 0220 0221 const QString &LogsModel::branch() const 0222 { 0223 return mBranch; 0224 } 0225 0226 void LogsModel::setBranch(const QString &newBranch) 0227 { 0228 mBranch = newBranch; 0229 0230 beginResetModel(); 0231 fill(); 0232 endResetModel(); 0233 } 0234 0235 int LogsModel::rowCount(const QModelIndex &parent) const 0236 { 0237 Q_UNUSED(parent) 0238 return mData.size(); 0239 } 0240 0241 0242 Log *LogsModel::at(int index) const 0243 { 0244 if (index < 0 || index >= mData.size()) 0245 return nullptr; 0246 0247 return mData.at(index); 0248 } 0249 0250 QVariant LogsModel::data(const QModelIndex &index, int role) const 0251 { 0252 if(!index.isValid()) 0253 return {}; 0254 0255 auto log = fromIndex(index); 0256 if (!log) 0257 return {}; 0258 0259 switch (role) { 0260 case Subject: 0261 return log->subject(); 0262 case Date: { 0263 if (mCalendar.isValid()) 0264 return log->commitDate().toLocalTime().toString(QStringLiteral("yyyy-MM-dd HH:mm:ss"), mCalendar); 0265 0266 return log->commitDate(); 0267 } 0268 case Author: 0269 return log->authorName(); 0270 case ShortHash: 0271 return log->commitShortHash(); 0272 case Hash: 0273 return log->commitHash(); 0274 case Message: 0275 return log->message(); 0276 case Body: 0277 return log->body(); 0278 } 0279 0280 0281 return {}; 0282 } 0283 0284 Log *LogsModel::fromIndex(const QModelIndex &index) const 0285 { 0286 if (!index.isValid() || index.row() < 0 || index.row() >= mData.size()) 0287 return nullptr; 0288 0289 return mData.at(index.row()); 0290 } 0291 0292 QModelIndex LogsModel::findIndexByHash(const QString &hash) const 0293 { 0294 int idx{0}; 0295 for (auto &log : mData) 0296 if (log->commitHash() == hash) 0297 return index(idx); 0298 else 0299 idx++; 0300 return {}; 0301 } 0302 0303 Log *LogsModel::findLogByHash(const QString &hash, LogMatchType matchType) const 0304 { 0305 QList<Log *>::ConstIterator i; 0306 0307 switch (matchType) { 0308 case LogMatchType::ExactMatch: 0309 i = std::find_if(mData.begin(), mData.end(), [&hash](Log *log) { 0310 return log->commitHash() == hash; 0311 }); 0312 break; 0313 case LogMatchType::BeginMatch: 0314 i = std::find_if(mData.begin(), mData.end(), [&hash](Log *log) { 0315 return log->commitHash().startsWith(hash); 0316 }); 0317 break; 0318 } 0319 if (i == mData.end()) 0320 return nullptr; 0321 return *i; 0322 } 0323 0324 void LogsModel::fill() 0325 { 0326 qDeleteAll(mData); 0327 mData.clear(); 0328 mDataByCommitHashLong.clear(); 0329 0330 mBranches = mGit->branches(); 0331 0332 QStringList args{QStringLiteral("--no-pager"), 0333 QStringLiteral("log"), 0334 QStringLiteral("--topo-order"), 0335 QStringLiteral("--no-color"), 0336 QStringLiteral("--parents"), 0337 QStringLiteral("--boundary"), 0338 QStringLiteral("--pretty=format:'SEP%m%HX%hX%P%n" 0339 "%cnX%ceX%cI%n" 0340 "%anX%aeX%aI%n" 0341 "%d%n" 0342 "%at%n" 0343 "%s%n" 0344 "%b%n'")}; 0345 0346 if (mBranch.size()) 0347 args.insert(2, mBranch); 0348 0349 auto ret = QString(mGit->runGit(args)); 0350 if (ret.startsWith(QStringLiteral("fatal:"))) 0351 return; 0352 0353 const auto parts = ret.split(QStringLiteral("SEP>")); 0354 0355 for (const auto &p : parts) { 0356 auto lines = p.split(QLatin1Char('\n')); 0357 if (lines.size() < 4) 0358 continue; 0359 0360 auto d = new Log; 0361 QString commitDate; 0362 QString authDate; 0363 QString parentHash; 0364 Impl::readLine(lines.at(0), QStringLiteral("X"), {&d->mCommitHash, &d->mCommitShortHash, &parentHash}); 0365 Impl::readLine(lines.at(1), QStringLiteral("X"), {&d->mCommitterName, &d->mCommitterEmail, &commitDate}); 0366 Impl::readLine(lines.at(2), QStringLiteral("X"), {&d->mAuthorName, &d->mAuthorEmail, &authDate}); 0367 0368 if (!parentHash.isEmpty()) 0369 d->mParentHash = parentHash.split(QLatin1Char(' ')); 0370 d->mRefLog = lines.at(3); 0371 d->mSubject = lines.at(5); 0372 d->mCommitDate = QDateTime::fromString(commitDate, Qt::ISODate); 0373 d->mAuthDate = QDateTime::fromString(authDate, Qt::ISODate); 0374 d->mBody = lines.mid(5).join(QLatin1Char('\n')); 0375 mData.append(d); 0376 mDataByCommitHashLong.insert(d->commitHash(), d); 0377 mDataByCommitHashShort.insert(d->commitShortHash(), d); 0378 0379 if (mAuthorsModel) { 0380 mAuthorsModel->findOrCreate(d->committerName(), d->committerEmail(), d->commitDate(), AuthorsModel::Commit); 0381 mAuthorsModel->findOrCreate(d->authorName(), d->authorEmail(), d->authDate(), AuthorsModel::AuthoredCommit); 0382 } 0383 } 0384 // std::sort(begin(), end(), [](GitLog *log1,GitLog *log2){ 0385 // return log1->commitDate() < log2->commitDate(); 0386 // }); 0387 // initChilds(); 0388 // initGraph(); 0389 } 0390 0391 void LogsModel::initChilds() 0392 { 0393 for (auto i = mData.rbegin(); i != mData.rend(); i++) { 0394 auto &log = *i; 0395 for (auto &p : log->parents()) 0396 mDataByCommitHashLong.value(p)->mChilds.append(log->commitHash()); 0397 } 0398 } 0399 0400 void LogsModel::initGraph() 0401 { 0402 Impl::LanesFactory factory; 0403 for (auto i = mData.rbegin(); i != mData.rend(); i++) { 0404 auto &log = *i; 0405 log->mLanes = factory.apply(log); 0406 } 0407 } 0408 0409 QString LogsModel::calendarType() const 0410 { 0411 return mCalendar.name(); 0412 } 0413 0414 void LogsModel::setCalendarType(const QString &newCalendarType) 0415 { 0416 if (mCalendar.name() != newCalendarType) { 0417 beginResetModel(); 0418 mCalendar = QCalendar(newCalendarType); 0419 endResetModel(); 0420 } 0421 } 0422 } 0423 0424 0425 QHash<int, QByteArray> Git::LogsModel::roleNames() const 0426 { 0427 return {{Subject, "subject"}, 0428 {Message, "message"}, 0429 {Date, "date"}, 0430 {Author, "author"}, 0431 {Hash, "hash"}, 0432 {ShortHash, "shortHash"}, 0433 {Body, "body"} 0434 }; 0435 }