File indexing completed on 2024-05-19 05:45:41

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 "statuscache.h"
0008 #include "qdebug.h"
0009 
0010 #include <git2.h>
0011 
0012 namespace Impl
0013 {
0014 struct wrapper {
0015     QMap<QString, KVersionControlPlugin::ItemVersion> files;
0016     QStringList ignores;
0017     QString currentPath;
0018     QString rootPath;
0019     QString prefix;
0020     bool allIgnored{false};
0021 };
0022 
0023 KVersionControlPlugin::ItemVersion convertToItemVersion(unsigned int status_flags)
0024 {
0025     KVersionControlPlugin::ItemVersion status;
0026     if (status_flags & GIT_STATUS_WT_NEW)
0027         status = KVersionControlPlugin::ItemVersion::AddedVersion;
0028     else if (status_flags & GIT_STATUS_WT_MODIFIED)
0029         status = KVersionControlPlugin::ItemVersion::LocallyModifiedVersion;
0030     else if (status_flags & GIT_STATUS_WT_DELETED)
0031         status = KVersionControlPlugin::ItemVersion::RemovedVersion;
0032     else if (status_flags & GIT_STATUS_WT_RENAMED)
0033         status = KVersionControlPlugin::ItemVersion::ConflictingVersion;
0034     else if (status_flags & GIT_STATUS_IGNORED)
0035         status = KVersionControlPlugin::ItemVersion::IgnoredVersion;
0036     else
0037         status = KVersionControlPlugin::ItemVersion::UnversionedVersion;
0038 
0039     return status;
0040 }
0041 
0042 QString removeSlashAtEnd(const QString &s)
0043 {
0044     if (s.endsWith("/"))
0045         return s.mid(0, s.length() - 1);
0046     return s;
0047 }
0048 
0049 auto callback(const char *pa, unsigned int status_flags, void *payload) -> int
0050 {
0051     auto w = reinterpret_cast<wrapper *>(payload);
0052 
0053     auto path = QString{pa};
0054 
0055     if (w->prefix.startsWith(path) && status_flags & GIT_STATUS_IGNORED) {
0056         w->allIgnored = true;
0057         return 1;
0058     }
0059     if (!path.startsWith(w->currentPath)) {
0060         return 0;
0061     }
0062 
0063     auto status = convertToItemVersion(status_flags);
0064 
0065     QString entryName;
0066     QString childPath;
0067     if (path.indexOf("/", w->prefix.length()) == -1) {
0068         entryName = path.mid(w->prefix.length());
0069     } else {
0070         entryName = path.mid(w->prefix.length(), path.indexOf("/", w->prefix.length()) + 1 - w->prefix.length());
0071         childPath = path.mid(w->prefix.length() + entryName.length());
0072     }
0073 
0074     qDebug() << path << entryName << childPath;
0075     if (childPath.isEmpty()) {
0076         w->files.insert(removeSlashAtEnd(entryName), status);
0077     } else {
0078         if (status != KVersionControlPlugin::ItemVersion::IgnoredVersion) {
0079             //            if (entryName.count('/') == 1 && entryName.endsWith("/"))
0080             //                w->files.insert(childPath, KVersionControlPlugin::ItemVersion::IgnoredVersion);
0081             //        } else {
0082             auto st = w->files.value(entryName, KVersionControlPlugin::ItemVersion::NormalVersion);
0083 
0084             if (st != status) {
0085                 if (st == KVersionControlPlugin::ItemVersion::NormalVersion)
0086                     w->files.insert(removeSlashAtEnd(entryName), status);
0087                 else
0088                     w->files.insert(removeSlashAtEnd(entryName), KVersionControlPlugin::ItemVersion::LocallyModifiedVersion);
0089             }
0090         }
0091     }
0092     return 0;
0093 };
0094 }
0095 
0096 StatusCache::StatusCache() = default;
0097 
0098 KVersionControlPlugin::ItemVersion StatusCache::status(const QString &name)
0099 {
0100     if (mCurrentPathIsIgnored || name.startsWith(".git/") || name == ".git")
0101         return KVersionControlPlugin::IgnoredVersion;
0102 
0103     return mStatuses.value(name, KVersionControlPlugin::ItemVersion::NormalVersion);
0104 }
0105 
0106 bool StatusCache::setPath(const QString &path)
0107 {
0108     if (mPath == path)
0109         return true;
0110 
0111     mPath = path;
0112 
0113     git_repository *repo{nullptr};
0114     int n = git_repository_open_ext(&repo, path.toUtf8().data(), 0, NULL);
0115 
0116     if (n) {
0117         git_repository_free(repo);
0118         return false;
0119     }
0120 
0121     mRepoRootPath = git_repository_workdir(repo);
0122     if (path.startsWith(mRepoRootPath + ".git/")) {
0123         mCurrentPathIsIgnored = true;
0124 
0125         git_repository_free(repo);
0126         return true;
0127     }
0128 
0129     Impl::wrapper w;
0130     w.currentPath = path;
0131     if (!w.currentPath.endsWith("/"))
0132         w.currentPath.append("/");
0133     w.rootPath = mRepoRootPath;
0134     if (!w.rootPath.endsWith("/"))
0135         w.rootPath.append("/");
0136     w.prefix = w.currentPath.replace(w.rootPath, "");
0137 
0138     git_status_options opts;
0139     git_status_options_init(&opts, GIT_STATUS_OPTIONS_VERSION);
0140     opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
0141     opts.flags = GIT_STATUS_OPT_DEFAULTS;
0142 
0143     //    if (!w.prefix.isEmpty()) {
0144     //        char *paths[] = {w.prefix.mid(0, w.prefix.length() - 1).toLocal8Bit().data()};
0145     //        git_strarray array = {paths, 1};
0146     //        opts.pathspec = array;
0147     //    }
0148 
0149     git_status_foreach_ext(repo, &opts, Impl::callback, &w);
0150     mStatuses = w.files;
0151     mCurrentPathIsIgnored = w.allIgnored;
0152 
0153     git_repository_free(repo);
0154 
0155     return true;
0156 }