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 }