diff options
Diffstat (limited to 'src/index.cc')
-rw-r--r-- | src/index.cc | 455 |
1 files changed, 0 insertions, 455 deletions
diff --git a/src/index.cc b/src/index.cc deleted file mode 100644 index ae8ca54c..00000000 --- a/src/index.cc +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright 2019 Roman Perepelitsa. -// -// This file is part of GitStatus. -// -// GitStatus is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// GitStatus is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with GitStatus. If not, see <https://www.gnu.org/licenses/>. - -#include "index.h" - -#include <dirent.h> -#include <fcntl.h> -#include <unistd.h> - -#include <algorithm> -#include <condition_variable> -#include <cstdint> -#include <cstring> -#include <iomanip> -#include <iterator> -#include <mutex> -#include <stack> - -#include "algorithm.h" -#include "check.h" -#include "dir.h" -#include "git.h" -#include "index.h" -#include "print.h" -#include "scope_guard.h" -#include "stat.h" -#include "string_cmp.h" -#include "thread_pool.h" - -namespace gitstatus { - -namespace { - -void CommonDir(Str<> str, const char* a, const char* b, size_t* dir_len, size_t* dir_depth) { - *dir_len = 0; - *dir_depth = 0; - for (size_t i = 1; str.Eq(*a, *b) && *a; ++i, ++a, ++b) { - if (*a == '/') { - *dir_len = i; - ++*dir_depth; - } - } -} - -size_t Weight(const IndexDir& dir) { return 1 + dir.subdirs.size() + dir.files.size(); } - -bool MTimeEq(const git_index_time& index, const struct timespec& workdir) { - if (index.seconds != workdir.tv_sec) return false; - if (int64_t{index.nanoseconds} == workdir.tv_nsec) return true; -#ifdef GITSTATUS_ZERO_NSEC - return index.nanoseconds == 0; -#else - return false; -#endif -} - -bool IsModified(const git_index_entry* entry, const struct stat& st, const RepoCaps& caps) { - mode_t mode = st.st_mode; - if (S_ISREG(mode)) { - if (!caps.has_symlinks && S_ISLNK(entry->mode)) { - mode = entry->mode; - } else if (!caps.trust_filemode) { - mode = entry->mode; - } else { - mode = S_IFREG | (mode & 0100 ? 0755 : 0644); - } - } else { - mode &= S_IFMT; - } - - bool res = false; - -#define COND(field, cond...) \ - if (cond) { \ - } else \ - res = true, \ - LOG(DEBUG) << "Dirty candidate (modified): " << Print(entry->path) << ": " #field " " - - COND(ino, !entry->ino || entry->ino == static_cast<std::uint32_t>(st.st_ino)) - << entry->ino << " => " << static_cast<std::uint32_t>(st.st_ino); - - COND(stage, GIT_INDEX_ENTRY_STAGE(entry) == 0) << "=> " << GIT_INDEX_ENTRY_STAGE(entry); - COND(fsize, int64_t{entry->file_size} == st.st_size) << entry->file_size << " => " << st.st_size; - COND(mtime, MTimeEq(entry->mtime, MTim(st))) << Print(entry->mtime) << " => " << Print(MTim(st)); - COND(mode, entry->mode == mode) << std::oct << entry->mode << " => " << std::oct << mode; - -#undef COND - - return res; -} - -int OpenDir(int parent_fd, const char* name) { - return openat(parent_fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC); -} - -void OpenTail(int* fds, size_t nfds, int root_fd, StringView dirname, Arena& arena) { - CHECK(fds && nfds && root_fd >= 0); - std::fill(fds, fds + nfds, -1); - if (!dirname.len) return; - CHECK(dirname.len > 1); - CHECK(dirname.ptr[0] != '/'); - CHECK(dirname.ptr[dirname.len - 1] == '/'); - - char* begin = arena.StrDup(dirname.ptr, dirname.len - 1); - WithArena<std::vector<const char*>> subdirs(&arena); - subdirs.reserve(nfds + 1); - - for (char* sep = begin + dirname.len - 1; subdirs.size() < nfds;) { - sep = FindLast(begin, sep, '/'); - if (sep == begin) break; - *sep = 0; - subdirs.push_back(sep + 1); - } - subdirs.push_back(begin); - if (subdirs.size() < nfds + 1) subdirs.push_back("."); - CHECK(subdirs.size() <= nfds + 1); - - for (size_t i = subdirs.size(); i != 1; --i) { - const char* path = subdirs[i - 1]; - if ((root_fd = OpenDir(root_fd, path)) < 0) { - for (; i != subdirs.size(); ++i) { - CHECK(!close(fds[i - 1])) << Errno(); - fds[i - 1] = -1; - } - return; - } - fds[i - 2] = root_fd; - } -} - -std::vector<const char*> ScanDirs(git_index* index, int root_fd, IndexDir* const* begin, - IndexDir* const* end, const RepoCaps& caps, - const ScanOpts& opts) { - const Str<> str(caps.case_sensitive); - - Arena arena; - std::vector<const char*> dirty_candidates; - std::vector<char*> entries; - entries.reserve(128); - - auto AddCandidate = [&](const char* kind, const char* path) { - if (kind) LOG(DEBUG) << "Dirty candidate (" << kind << "): " << Print(path); - dirty_candidates.push_back(path); - }; - - constexpr ssize_t kDirStackSize = 5; - int dir_fd[kDirStackSize]; - std::fill(std::begin(dir_fd), std::end(dir_fd), -1); - auto Close = [](int& fd) { - if (fd >= 0) { - CHECK(!close(fd)) << Errno(); - fd = -1; - } - }; - auto CloseAll = [&] { std::for_each(std::begin(dir_fd), std::end(dir_fd), Close); }; - ON_SCOPE_EXIT(&) { CloseAll(); }; - if (begin != end) OpenTail(dir_fd, kDirStackSize, root_fd, (*begin)->path, arena); - - for (IndexDir* const* it = begin; it != end; ++it) { - IndexDir& dir = **it; - - auto Basename = [&](const git_index_entry* e) { return e->path + dir.path.len; }; - - auto AddUnmached = [&](StringView basename) { - if (!basename.len) { - dir.st = {}; - dir.unmatched.clear(); - dir.arena.Reuse(); - } else if (str.Eq(basename, StringView(".git/"))) { - return; - } - char* path = dir.arena.StrCat(dir.path, basename); - dir.unmatched.push_back(path); - AddCandidate(basename.len ? "new" : "unreadable", path); - }; - - auto StatFiles = [&]() { - struct stat st; - for (const git_index_entry* file : dir.files) { - if (fstatat(*dir_fd, Basename(file), &st, AT_SYMLINK_NOFOLLOW)) { - AddCandidate(errno == ENOENT ? "deleted" : "unreadable", file->path); - } else if (IsModified(file, st, caps)) { - AddCandidate(nullptr, file->path); - } - } - }; - - ssize_t d = 0; - if ((it == begin || (d = it[-1]->depth + 1 - dir.depth) < kDirStackSize) && dir_fd[d] >= 0) { - CHECK(d >= 0); - int fd = OpenDir(dir_fd[d], arena.StrDup(dir.basename.ptr, dir.basename.len)); - for (ssize_t i = 0; i != d; ++i) Close(dir_fd[i]); - std::rotate(dir_fd, dir_fd + (d ? d : kDirStackSize) - 1, dir_fd + kDirStackSize); - Close(*dir_fd); - *dir_fd = fd; - } else { - CloseAll(); - if (dir.path.len) { - CHECK(dir.path.ptr[0] != '/'); - CHECK(dir.path.ptr[dir.path.len - 1] == '/'); - *dir_fd = OpenDir(root_fd, arena.StrDup(dir.path.ptr, dir.path.len - 1)); - } else { - VERIFY((*dir_fd = dup(root_fd)) >= 0) << Errno(); - } - } - if (*dir_fd < 0) { - CloseAll(); - AddUnmached(""); - continue; - } - - if (!opts.include_untracked) { - StatFiles(); - continue; - } - - if (opts.untracked_cache != Tribool::kFalse) { - struct stat st; - if (fstat(*dir_fd, &st)) { - AddUnmached(""); - continue; - } - if (opts.untracked_cache == Tribool::kTrue && StatEq(st, dir.st)) { - StatFiles(); - for (const char* path : dir.unmatched) AddCandidate("new", path); - continue; - } - dir.st = st; - } - - arena.Reuse(); - if (!ListDir(*dir_fd, arena, entries, caps.precompose_unicode, caps.case_sensitive)) { - AddUnmached(""); - continue; - } - dir.unmatched.clear(); - dir.arena.Reuse(); - - const git_index_entry* const* file = dir.files.data(); - const git_index_entry* const* file_end = file + dir.files.size(); - const StringView* subdir = dir.subdirs.data(); - const StringView* subdir_end = subdir + dir.subdirs.size(); - - for (char* entry : entries) { - bool matched = false; - - for (; file != file_end; ++file) { - int cmp = str.Cmp(Basename(*file), entry); - if (cmp < 0) { - AddCandidate("deleted", (*file)->path); - } else if (cmp == 0) { - struct stat st; - if (fstatat(*dir_fd, entry, &st, AT_SYMLINK_NOFOLLOW)) { - AddCandidate("unreadable", (*file)->path); - } else if (IsModified(*file, st, caps)) { - AddCandidate(nullptr, (*file)->path); - } - matched = true; - ++file; - break; - } else { - break; - } - } - - if (matched) continue; - - for (; subdir != subdir_end; ++subdir) { - int cmp = str.Cmp(*subdir, entry); - if (cmp > 0) break; - if (cmp == 0) { - matched = true; - ++subdir; - break; - } - } - - if (!matched) { - StringView basename(entry); - if (entry[-1] == DT_DIR) entry[basename.len++] = '/'; - AddUnmached(basename); - } - } - - for (; file != file_end; ++file) AddCandidate("deleted", (*file)->path); - } - - return dirty_candidates; -} - -} // namespace - -RepoCaps::RepoCaps(git_repository* repo, git_index* index) { - trust_filemode = git_index_is_filemode_trustworthy(index); - has_symlinks = git_index_supports_symlinks(index); - case_sensitive = git_index_is_case_sensitive(index); - precompose_unicode = git_index_precompose_unicode(index); - LOG(DEBUG) << "Repository capabilities for " << Print(git_repository_workdir(repo)) << ": " - << "is_filemode_trustworthy = " << std::boolalpha << trust_filemode << ", " - << "index_supports_symlinks = " << std::boolalpha << has_symlinks << ", " - << "index_is_case_sensitive = " << std::boolalpha << case_sensitive << ", " - << "precompose_unicode = " << std::boolalpha << precompose_unicode; -} - -Index::Index(git_repository* repo, git_index* index) - : dirs_(&arena_), - splits_(&arena_), - git_index_(index), - root_dir_(git_repository_workdir(repo)), - caps_(repo, index) { - size_t total_weight = InitDirs(index); - InitSplits(total_weight); -} - -size_t Index::InitDirs(git_index* index) { - const Str<> str(git_index_is_case_sensitive(index)); - const size_t index_size = git_index_entrycount(index); - dirs_.reserve(index_size / 8); - std::stack<IndexDir*> stack; - stack.push(arena_.DirectInit<IndexDir>(&arena_)); - - size_t total_weight = 0; - auto PopDir = [&] { - CHECK(!stack.empty()); - IndexDir* top = stack.top(); - CHECK(top->depth + 1 == stack.size()); - if (!std::is_sorted(top->subdirs.begin(), top->subdirs.end(), str.Lt)) { - StrSort(top->subdirs.begin(), top->subdirs.end(), str.case_sensitive); - } - total_weight += Weight(*top); - dirs_.push_back(top); - stack.pop(); - }; - - for (size_t i = 0; i != index_size; ++i) { - const git_index_entry* entry = git_index_get_byindex_no_sort(index, i); - IndexDir* prev = stack.top(); - size_t common_len, common_depth; - CommonDir(str, prev->path.ptr, entry->path, &common_len, &common_depth); - CHECK(common_depth <= prev->depth); - - for (size_t i = common_depth; i != prev->depth; ++i) PopDir(); - - for (const char* p = entry->path + common_len; (p = std::strchr(p, '/')); ++p) { - IndexDir* top = stack.top(); - StringView subdir(entry->path + top->path.len, p); - top->subdirs.push_back(subdir); - IndexDir* dir = arena_.DirectInit<IndexDir>(&arena_); - dir->path = StringView(entry->path, p - entry->path + 1); - dir->basename = subdir; - dir->depth = stack.size(); - CHECK(dir->path.ptr[dir->path.len - 1] == '/'); - stack.push(dir); - } - - CHECK(!stack.empty()); - IndexDir* dir = stack.top(); - dir->files.push_back(entry); - } - - CHECK(!stack.empty()); - do { - PopDir(); - } while (!stack.empty()); - std::reverse(dirs_.begin(), dirs_.end()); - - return total_weight; -} - -void Index::InitSplits(size_t total_weight) { - constexpr size_t kMinShardWeight = 512; - const size_t kNumShards = 16 * GlobalThreadPool()->num_threads(); - const size_t shard_weight = std::max(kMinShardWeight, total_weight / kNumShards); - - splits_.reserve(kNumShards + 1); - splits_.push_back(0); - - for (size_t i = 0, w = 0; i != dirs_.size(); ++i) { - w += Weight(*dirs_[i]); - if (w >= shard_weight) { - w = 0; - splits_.push_back(i + 1); - } - } - - if (splits_.back() != dirs_.size()) splits_.push_back(dirs_.size()); - CHECK(splits_.size() <= kNumShards + 1); - CHECK(std::is_sorted(splits_.begin(), splits_.end())); - CHECK(std::adjacent_find(splits_.begin(), splits_.end()) == splits_.end()); -} - -std::vector<const char*> Index::GetDirtyCandidates(const ScanOpts& opts) { - int root_fd = open(root_dir_, O_RDONLY | O_DIRECTORY | O_CLOEXEC); - VERIFY(root_fd >= 0); - ON_SCOPE_EXIT(&) { CHECK(!close(root_fd)) << Errno(); }; - - CHECK(!splits_.empty()); - - std::mutex mutex; - std::condition_variable cv; - size_t inflight = splits_.size() - 1; - bool error = false; - std::vector<const char*> res; - - for (size_t i = 0; i != splits_.size() - 1; ++i) { - size_t from = splits_[i]; - size_t to = splits_[i + 1]; - - GlobalThreadPool()->Schedule([&, from, to]() { - ON_SCOPE_EXIT(&) { - std::unique_lock<std::mutex> lock(mutex); - CHECK(inflight); - if (--inflight == 0) cv.notify_one(); - }; - try { - std::vector<const char*> candidates = - ScanDirs(git_index_, root_fd, dirs_.data() + from, dirs_.data() + to, caps_, opts); - if (!candidates.empty()) { - std::unique_lock<std::mutex> lock(mutex); - res.insert(res.end(), candidates.begin(), candidates.end()); - } - } catch (const Exception&) { - std::unique_lock<std::mutex> lock(mutex); - error = true; - } - }); - } - - { - std::unique_lock<std::mutex> lock(mutex); - while (inflight) cv.wait(lock); - } - - VERIFY(!error); - StrSort(res.begin(), res.end(), git_index_is_case_sensitive(git_index_)); - auto StrEq = [](const char* a, const char* b) { return !strcmp(a, b); }; - res.erase(std::unique(res.begin(), res.end(), StrEq), res.end()); - return res; -} - -} // namespace gitstatus |