├── .gitignore ├── gitstatus ├── deps │ └── .gitkeep ├── usrbin │ └── .gitkeep ├── .clang-format ├── .gitignore ├── src │ ├── time.h │ ├── stat.h │ ├── tribool.h │ ├── bits.h │ ├── serialization.h │ ├── timer.h │ ├── strings.h │ ├── algorithm.h │ ├── check_dir_mtime.h │ ├── request.h │ ├── response.h │ ├── repo_cache.h │ ├── scope_guard.h │ ├── response.cc │ ├── strings.cc │ ├── check.h │ ├── tag_db.h │ ├── dir.h │ ├── thread_pool.h │ ├── timer.cc │ ├── index.h │ ├── thread_pool.cc │ ├── string_view.h │ ├── print.h │ ├── options.h │ ├── git.h │ ├── logging.h │ ├── repo.h │ ├── arena.cc │ ├── request.cc │ ├── logging.cc │ ├── string_cmp.h │ ├── check_dir_mtime.cc │ ├── repo_cache.cc │ ├── dir.cc │ ├── gitstatus.cc │ ├── git.cc │ ├── arena.h │ └── tag_db.cc ├── .gitattributes ├── .vscode │ ├── c_cpp_properties.json │ └── settings.json ├── build.info ├── Makefile ├── install.info ├── gitstatus.prompt.sh └── gitstatus.prompt.zsh ├── powerlevel10k.png ├── powerlevel9k.zsh-theme ├── prompt_powerlevel10k_setup ├── prompt_powerlevel9k_setup ├── .gitattributes ├── Makefile ├── LICENSE ├── internal ├── configure.zsh ├── notes.md ├── worker.zsh └── parser.zsh ├── powerlevel10k.zsh-theme ├── config ├── p10k-robbyrussell.zsh └── p10k-pure.zsh └── font.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.zwc 2 | -------------------------------------------------------------------------------- /gitstatus/deps/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /gitstatus/usrbin/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /powerlevel10k.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/romkatv/powerlevel10k/HEAD/powerlevel10k.png -------------------------------------------------------------------------------- /powerlevel9k.zsh-theme: -------------------------------------------------------------------------------- 1 | 'builtin' 'source' "${POWERLEVEL9K_INSTALLATION_DIR:-${${(%):-%x}:A:h}}/powerlevel10k.zsh-theme" 2 | -------------------------------------------------------------------------------- /prompt_powerlevel10k_setup: -------------------------------------------------------------------------------- 1 | 'builtin' 'source' "${POWERLEVEL9K_INSTALLATION_DIR:-${${(%):-%x}:A:h}}/powerlevel10k.zsh-theme" 2 | -------------------------------------------------------------------------------- /prompt_powerlevel9k_setup: -------------------------------------------------------------------------------- 1 | 'builtin' 'source' "${POWERLEVEL9K_INSTALLATION_DIR:-${${(%):-%x}:A:h}}/powerlevel10k.zsh-theme" 2 | -------------------------------------------------------------------------------- /gitstatus/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | ColumnLimit: 100 3 | DerivePointerAlignment: false 4 | PointerAlignment: Left 5 | -------------------------------------------------------------------------------- /gitstatus/.gitignore: -------------------------------------------------------------------------------- 1 | *.zwc 2 | /core 3 | /deps/libgit2-*.tar.gz 4 | /locks 5 | /logs 6 | /obj 7 | /usrbin/gitstatusd* 8 | /.vscode/ipch 9 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.zsh text eol=lf 3 | *.zsh-theme text eol=lf 4 | /prompt_powerlevel9k_setup text eol=lf 5 | /prompt_powerlevel10k_setup text eol=lf 6 | -------------------------------------------------------------------------------- /gitstatus/src/time.h: -------------------------------------------------------------------------------- 1 | #ifndef ROMKATV_GITSTATUS_TIME_H_ 2 | #define ROMKATV_GITSTATUS_TIME_H_ 3 | 4 | #include 5 | 6 | namespace gitstatus { 7 | 8 | using Clock = std::chrono::steady_clock; 9 | using Time = Clock::time_point; 10 | using Duration = Clock::duration; 11 | 12 | } // namespace gitstatus 13 | 14 | #endif // ROMKATV_GITSTATUS_TIME_H_ 15 | -------------------------------------------------------------------------------- /gitstatus/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | 3 | *.cc text eol=lf 4 | *.h text eol=lf 5 | *.info text eol=lf 6 | *.json text eol=lf 7 | *.md text eol=lf 8 | *.sh text eol=lf 9 | *.zsh text eol=lf 10 | 11 | /.clang-format text eol=lf 12 | /LICENSE text eol=lf 13 | /Makefile text eol=lf 14 | /build text eol=lf 15 | /install text eol=lf 16 | /mbuild text eol=lf 17 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | ZSH := $(shell command -v zsh 2> /dev/null) 2 | 3 | all: 4 | 5 | zwc: 6 | $(MAKE) -C gitstatus zwc 7 | $(or $(ZSH),:) -fc 'for f in *.zsh-theme internal/*.zsh; do zcompile -R -- $$f.zwc $$f || exit; done' 8 | 9 | minify: 10 | $(MAKE) -C gitstatus minify 11 | rm -rf -- .git .gitattributes .gitignore LICENSE Makefile README.md font.md powerlevel10k.png 12 | 13 | pkg: zwc 14 | $(MAKE) -C gitstatus pkg 15 | -------------------------------------------------------------------------------- /gitstatus/.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/src" 7 | ], 8 | "defines": [ 9 | ], 10 | "compilerPath": "/usr/bin/g++", 11 | "cStandard": "c11", 12 | "cppStandard": "c++17", 13 | "intelliSenseMode": "gcc-x64" 14 | } 15 | ], 16 | "version": 4 17 | } 18 | -------------------------------------------------------------------------------- /gitstatus/src/stat.h: -------------------------------------------------------------------------------- 1 | #ifndef ROMKATV_GITSTATUS_STAT_H_ 2 | #define ROMKATV_GITSTATUS_STAT_H_ 3 | 4 | #include 5 | 6 | namespace gitstatus { 7 | 8 | inline const struct timespec& MTim(const struct stat& s) { 9 | #ifdef __APPLE__ 10 | return s.st_mtimespec; 11 | #else 12 | return s.st_mtim; 13 | #endif 14 | } 15 | 16 | inline bool StatEq(const struct stat& x, const struct stat& y) { 17 | return MTim(x).tv_sec == MTim(y).tv_sec && MTim(x).tv_nsec == MTim(y).tv_nsec && 18 | x.st_size == y.st_size && x.st_ino == y.st_ino && x.st_mode == y.st_mode; 19 | } 20 | 21 | } // namespace gitstatus 22 | 23 | #endif // ROMKATV_GITSTATUS_STAT_H_ 24 | -------------------------------------------------------------------------------- /gitstatus/build.info: -------------------------------------------------------------------------------- 1 | # This value gets embedded in gitstatusd at build time. It is 2 | # read by ./Makefile. `gitstatusd --version` reports it back. 3 | # 4 | # This value is also read by shell bindings (indirectly, through 5 | # ./install) when using GITSTATUS_DAEMON or usrbin/gitstatusd. 6 | gitstatus_version="v1.5.5" 7 | 8 | # libgit2 is a build time dependency of gitstatusd. The values of 9 | # libgit2_version and libgit2_sha256 are read by ./build. 10 | # 11 | # If ./deps/libgit2-${libgit2_version}.tar.gz doesn't exist, build 12 | # downloads it from the following location: 13 | # 14 | # https://github.com/romkatv/libgit2/archive/${libgit2_version}.tar.gz 15 | # 16 | # Once downloaded, the tarball is stored at the path indicated 17 | # above so that repeated builds don't consume network bandwidth. 18 | # 19 | # If sha256 of ./deps/libgit2-${libgit2_version}.tar.gz doesn't match, 20 | # build gets aborted. 21 | libgit2_version="tag-2ecf33948a4df9ef45a66c68b8ef24a5e60eaac6" 22 | libgit2_sha256="4ce11d71ee576dbbc410b9fa33a9642809cc1fa687b315f7c23eeb825b251e93" 23 | -------------------------------------------------------------------------------- /gitstatus/src/tribool.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_TRIBOOL_H_ 19 | #define ROMKATV_GITSTATUS_TRIBOOL_H_ 20 | 21 | namespace gitstatus { 22 | 23 | enum class Tribool : int { kFalse = 0, kTrue = 1, kUnknown = -1 }; 24 | 25 | } // namespace gitstatus 26 | 27 | #endif // ROMKATV_GITSTATUS_TRIBOOL_H_ 28 | -------------------------------------------------------------------------------- /gitstatus/src/bits.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_BITS_H_ 19 | #define ROMKATV_GITSTATUS_BITS_H_ 20 | 21 | #include 22 | 23 | namespace gitstatus { 24 | 25 | inline size_t NextPow2(size_t n) { return n < 2 ? 1 : (~size_t{0} >> __builtin_clzll(n - 1)) + 1; } 26 | 27 | } // namespace gitstatus 28 | 29 | #endif // ROMKATV_GITSTATUS_BITS_H_ 30 | -------------------------------------------------------------------------------- /gitstatus/src/serialization.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_SERIALIZATION_H_ 19 | #define ROMKATV_GITSTATUS_SERIALIZATION_H_ 20 | 21 | namespace gitstatus { 22 | 23 | constexpr char kFieldSep = 31; // ascii 31 is unit separator 24 | constexpr char kMsgSep = 30; // ascii 30 is record separator 25 | 26 | } // namespace gitstatus 27 | 28 | #endif // ROMKATV_GITSTATUS_SERIALIZATION_H_ 29 | -------------------------------------------------------------------------------- /gitstatus/src/timer.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_TIMER_H_ 19 | #define ROMKATV_GITSTATUS_TIMER_H_ 20 | 21 | namespace gitstatus { 22 | 23 | class Timer { 24 | public: 25 | Timer() { Start(); } 26 | void Start(); 27 | void Report(const char* msg); 28 | 29 | private: 30 | double cpu_; 31 | double wall_; 32 | }; 33 | 34 | } // namespace gitstatus 35 | 36 | #endif // ROMKATV_GITSTATUS_TIMER_H_ 37 | -------------------------------------------------------------------------------- /gitstatus/src/strings.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_STRINGS_H_ 19 | #define ROMKATV_GITSTATUS_STRINGS_H_ 20 | 21 | #include 22 | 23 | namespace gitstatus { 24 | 25 | // If the pointers are null, prints nothing. 26 | // 27 | // Requires: !begin == !end. 28 | void CEscape(std::ostream& strm, const char* begin, const char* end); 29 | 30 | // If the pointers are null, prints null without quotes. 31 | // 32 | // Requires: !begin == !end. 33 | void Quote(std::ostream& strm, const char* begin, const char* end); 34 | 35 | } // namespace gitstatus 36 | 37 | #endif // ROMKATV_GITSTATUS_STRING_VIEW_H_ 38 | -------------------------------------------------------------------------------- /gitstatus/src/algorithm.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_ALGORITHM_H_ 19 | #define ROMKATV_GITSTATUS_ALGORITHM_H_ 20 | 21 | #include 22 | 23 | namespace gitstatus { 24 | 25 | // Requires: Iter is a BidirectionalIterator. 26 | // 27 | // Returns iterator pointing to the last value in [begin, end) that compares equal to the value, or 28 | // begin if none compare equal. 29 | template 30 | Iter FindLast(Iter begin, Iter end, const T& val) { 31 | while (begin != end && !(*--end == val)) {} 32 | return end; 33 | } 34 | 35 | } // namespace gitstatus 36 | 37 | #endif // ROMKATV_GITSTATUS_ALGORITHM_H_ 38 | -------------------------------------------------------------------------------- /gitstatus/src/check_dir_mtime.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_CHECK_DIR_MTIME_H_ 19 | #define ROMKATV_GITSTATUS_CHECK_DIR_MTIME_H_ 20 | 21 | namespace gitstatus { 22 | 23 | // Similar to `git update-index --test-untracked-cache` but performs all tests 24 | // in parallel, so the total testing time is one second regardless of the number 25 | // of tests. It also performs fewer tests because gitstatus imposes fewer 26 | // requirements on the filesystem in order to take advantage of untracked cache. 27 | bool CheckDirMtime(const char* root_dir); 28 | 29 | } // namespace gitstatus 30 | 31 | #endif // ROMKATV_GITSTATUS_CHECK_DIR_MTIME_H_ 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2014 Robby Russell and contributors (see https://github.com/robbyrussell/oh-my-zsh/contributors) 2 | Copyright (c) 2014-2017 Ben Hilburn 3 | Copyright (c) 2019 Roman Perepelitsa and contributors (see https://github.com/romkatv/powerlevel10k/contributors) 4 | 5 | MIT LICENSE 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /gitstatus/src/request.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_REQUEST_H_ 19 | #define ROMKATV_GITSTATUS_REQUEST_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | namespace gitstatus { 26 | 27 | struct Request { 28 | std::string id; 29 | std::string dir; 30 | bool from_dotgit = false; 31 | bool diff = true; 32 | }; 33 | 34 | std::ostream& operator<<(std::ostream& strm, const Request& req); 35 | 36 | class RequestReader { 37 | public: 38 | RequestReader(int fd, int lock_fd, int parent_pid); 39 | bool ReadRequest(Request& req); 40 | 41 | private: 42 | int fd_; 43 | int lock_fd_; 44 | int parent_pid_; 45 | std::deque read_; 46 | }; 47 | 48 | } // namespace gitstatus 49 | 50 | #endif // ROMKATV_GITSTATUS_REQUEST_H_ 51 | -------------------------------------------------------------------------------- /gitstatus/src/response.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_RESPONSE_H_ 19 | #define ROMKATV_GITSTATUS_RESPONSE_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "string_view.h" 27 | 28 | namespace gitstatus { 29 | 30 | class ResponseWriter { 31 | public: 32 | ResponseWriter(std::string request_id); 33 | ResponseWriter(ResponseWriter&&) = delete; 34 | ~ResponseWriter(); 35 | 36 | void Print(ssize_t val); 37 | void Print(StringView val); 38 | void Print(const char* val) { Print(StringView(val)); } 39 | 40 | void Dump(const char* log); 41 | 42 | private: 43 | bool done_ = false; 44 | std::string request_id_; 45 | std::ostringstream strm_; 46 | }; 47 | 48 | } // namespace gitstatus 49 | 50 | #endif // ROMKATV_GITSTATUS_RESPONSE_H_ 51 | -------------------------------------------------------------------------------- /gitstatus/src/repo_cache.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_REPO_CACHE_H_ 19 | #define ROMKATV_GITSTATUS_REPO_CACHE_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | 29 | #include "options.h" 30 | #include "repo.h" 31 | #include "time.h" 32 | 33 | namespace gitstatus { 34 | 35 | class RepoCache { 36 | public: 37 | explicit RepoCache(Limits lim) : lim_(std::move(lim)) {} 38 | Repo* Open(const std::string& dir, bool from_dotgit); 39 | void Free(Time cutoff); 40 | 41 | private: 42 | struct Entry; 43 | using Cache = std::unordered_map>; 44 | using LRU = std::multimap; 45 | 46 | void Erase(Cache::iterator it); 47 | 48 | Limits lim_; 49 | Cache cache_; 50 | LRU lru_; 51 | 52 | struct Entry : Repo { 53 | using Repo::Repo; 54 | LRU::iterator lru; 55 | }; 56 | }; 57 | 58 | } // namespace gitstatus 59 | 60 | #endif // ROMKATV_GITSTATUS_REPO_CACHE_H_ 61 | -------------------------------------------------------------------------------- /gitstatus/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.exclude": { 3 | "*.zwc": true, 4 | "core": true, 5 | "locks/": true, 6 | "logs/": true, 7 | "obj/": true, 8 | "usrbin/": true, 9 | }, 10 | "files.associations": { 11 | "array": "cpp", 12 | "atomic": "cpp", 13 | "*.tcc": "cpp", 14 | "cctype": "cpp", 15 | "chrono": "cpp", 16 | "clocale": "cpp", 17 | "cmath": "cpp", 18 | "complex": "cpp", 19 | "condition_variable": "cpp", 20 | "cstddef": "cpp", 21 | "cstdint": "cpp", 22 | "cstdio": "cpp", 23 | "cstdlib": "cpp", 24 | "cstring": "cpp", 25 | "ctime": "cpp", 26 | "cwchar": "cpp", 27 | "cwctype": "cpp", 28 | "deque": "cpp", 29 | "unordered_map": "cpp", 30 | "unordered_set": "cpp", 31 | "vector": "cpp", 32 | "exception": "cpp", 33 | "fstream": "cpp", 34 | "functional": "cpp", 35 | "future": "cpp", 36 | "initializer_list": "cpp", 37 | "iomanip": "cpp", 38 | "iosfwd": "cpp", 39 | "iostream": "cpp", 40 | "istream": "cpp", 41 | "limits": "cpp", 42 | "memory": "cpp", 43 | "mutex": "cpp", 44 | "new": "cpp", 45 | "numeric": "cpp", 46 | "optional": "cpp", 47 | "ostream": "cpp", 48 | "ratio": "cpp", 49 | "sstream": "cpp", 50 | "stdexcept": "cpp", 51 | "streambuf": "cpp", 52 | "string_view": "cpp", 53 | "system_error": "cpp", 54 | "thread": "cpp", 55 | "type_traits": "cpp", 56 | "tuple": "cpp", 57 | "typeinfo": "cpp", 58 | "utility": "cpp", 59 | "variant": "cpp", 60 | "cstdarg": "cpp", 61 | "charconv": "cpp", 62 | "algorithm": "cpp", 63 | "cinttypes": "cpp", 64 | "iterator": "cpp", 65 | "map": "cpp", 66 | "memory_resource": "cpp", 67 | "random": "cpp", 68 | "string": "cpp", 69 | "bit": "cpp", 70 | "netfwd": "cpp" 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /gitstatus/Makefile: -------------------------------------------------------------------------------- 1 | APPNAME ?= gitstatusd 2 | OBJDIR ?= obj 3 | 4 | CXX ?= g++ 5 | ZSH := $(shell command -v zsh 2> /dev/null) 6 | 7 | VERSION ?= $(shell . ./build.info && printf "%s" "$$gitstatus_version") 8 | 9 | # Note: -fsized-deallocation is not used to avoid binary compatibility issues on macOS. 10 | # 11 | # Sized delete is implemented as __ZdlPvm in /usr/lib/libc++.1.dylib but this symbol is 12 | # missing in macOS prior to 10.13. 13 | CXXFLAGS += -std=c++14 -funsigned-char -O3 -DNDEBUG -DGITSTATUS_VERSION=$(VERSION) # -Wall -g -fsanitize=thread 14 | LDFLAGS += -pthread # -fsanitize=thread 15 | LDLIBS += -lgit2 # -lprofiler -lunwind 16 | 17 | SRCS := $(shell find src -name "*.cc") 18 | OBJS := $(patsubst src/%.cc, $(OBJDIR)/%.o, $(SRCS)) 19 | 20 | all: $(APPNAME) 21 | 22 | $(APPNAME): usrbin/$(APPNAME) 23 | 24 | usrbin/$(APPNAME): $(OBJS) 25 | $(CXX) $(OBJS) $(LDFLAGS) $(LDLIBS) -o $@ 26 | 27 | $(OBJDIR): 28 | mkdir -p -- $(OBJDIR) 29 | 30 | $(OBJDIR)/%.o: src/%.cc Makefile build.info | $(OBJDIR) 31 | $(CXX) $(CXXFLAGS) -MM -MT $@ src/$*.cc >$(OBJDIR)/$*.dep 32 | $(CXX) $(CXXFLAGS) -Wall -c -o $@ src/$*.cc 33 | 34 | clean: 35 | rm -rf -- $(OBJDIR) 36 | 37 | zwc: 38 | $(or $(ZSH),:) -fc 'for f in *.zsh install; do zcompile -R -- $$f.zwc $$f || exit; done' 39 | 40 | minify: 41 | rm -rf -- .clang-format .git .gitattributes .gitignore .vscode deps docs src usrbin/.gitkeep LICENSE Makefile README.md build mbuild 42 | 43 | pkg: zwc 44 | GITSTATUS_DAEMON= GITSTATUS_CACHE_DIR=$(shell pwd)/usrbin ./install -f 45 | 46 | -include $(OBJS:.o=.dep) 47 | 48 | .PHONY: help 49 | 50 | help: 51 | @echo "Usage: make [TARGET]" 52 | @echo "Available targets:" 53 | @echo " all Build $(APPNAME) (default target)" 54 | @echo " clean Remove generated files and directories" 55 | @echo " zwc Compile Zsh files" 56 | @echo " minify Remove unnecessary files and folders" 57 | @echo " pkg Create a package" 58 | -------------------------------------------------------------------------------- /gitstatus/src/scope_guard.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_SCOPE_GUARD_H_ 19 | #define ROMKATV_GITSTATUS_SCOPE_GUARD_H_ 20 | 21 | #include 22 | 23 | #define ON_SCOPE_EXIT(capture...) \ 24 | auto GITSTATUS_INTERNAL_CAT(_gitstatus_scope_guard_, __COUNTER__) = \ 25 | ::gitstatus::internal_scope_guard::ScopeGuardGenerator() = [capture]() 26 | 27 | #define GITSTATUS_INTERNAL_CAT_I(x, y) x##y 28 | #define GITSTATUS_INTERNAL_CAT(x, y) GITSTATUS_INTERNAL_CAT_I(x, y) 29 | 30 | namespace gitstatus { 31 | namespace internal_scope_guard { 32 | 33 | void Undefined(); 34 | 35 | template 36 | class ScopeGuard { 37 | public: 38 | explicit ScopeGuard(F f) : f_(std::move(f)) {} 39 | ~ScopeGuard() { std::move(f_)(); } 40 | ScopeGuard(ScopeGuard&& other) : f_(std::move(other.f_)) { Undefined(); } 41 | 42 | private: 43 | F f_; 44 | }; 45 | 46 | struct ScopeGuardGenerator { 47 | template 48 | ScopeGuard operator=(F f) const { 49 | return ScopeGuard(std::move(f)); 50 | } 51 | }; 52 | 53 | } // namespace internal_scope_guard 54 | } // namespace gitstatus 55 | 56 | #endif // ROMKATV_GITSTATUS_SCOPE_GUARD_H_ 57 | -------------------------------------------------------------------------------- /gitstatus/src/response.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include "response.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include "check.h" 25 | #include "serialization.h" 26 | 27 | namespace gitstatus { 28 | 29 | namespace { 30 | 31 | constexpr char kUnreadable = '?'; 32 | 33 | void SafePrint(std::ostream& strm, StringView s) { 34 | for (size_t i = 0; i != s.len; ++i) { 35 | char c = s.ptr[i]; 36 | strm << (c > 127 || std::isprint(c) ? c : kUnreadable); 37 | } 38 | } 39 | 40 | } // namespace 41 | 42 | ResponseWriter::ResponseWriter(std::string request_id) : request_id_(std::move(request_id)) { 43 | SafePrint(strm_, request_id_); 44 | Print(1); 45 | } 46 | 47 | ResponseWriter::~ResponseWriter() { 48 | if (!done_) { 49 | strm_.str(""); 50 | SafePrint(strm_, request_id_); 51 | Print("0"); 52 | Dump("without git status"); 53 | } 54 | } 55 | 56 | void ResponseWriter::Print(ssize_t val) { 57 | strm_ << kFieldSep; 58 | strm_ << val; 59 | } 60 | 61 | void ResponseWriter::Print(StringView val) { 62 | strm_ << kFieldSep; 63 | SafePrint(strm_, val); 64 | } 65 | 66 | void ResponseWriter::Dump(const char* log) { 67 | CHECK(!done_); 68 | done_ = true; 69 | LOG(INFO) << "Replying " << log; 70 | std::cout << strm_.str() << kMsgSep << std::flush; 71 | } 72 | 73 | } // namespace gitstatus 74 | -------------------------------------------------------------------------------- /gitstatus/src/strings.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include 19 | 20 | #include "strings.h" 21 | 22 | namespace gitstatus { 23 | 24 | void CEscape(std::ostream& strm, const char* begin, const char* end) { 25 | assert(!begin == !end); 26 | if (!begin) return; 27 | for (; begin != end; ++begin) { 28 | const unsigned char c = *begin; 29 | switch (c) { 30 | case '\t': 31 | strm << "\\t"; 32 | continue; 33 | case '\n': 34 | strm << "\\n"; 35 | continue; 36 | case '\r': 37 | strm << "\\r"; 38 | continue; 39 | case '"': 40 | strm << "\\\""; 41 | continue; 42 | case '\'': 43 | strm << "\\'"; 44 | continue; 45 | case '\\': 46 | strm << "\\\\"; 47 | continue; 48 | } 49 | if (c > 31 && c < 127) { 50 | strm << c; 51 | continue; 52 | } 53 | strm << '\\'; 54 | strm << static_cast('0' + ((c >> 6) & 7)); 55 | strm << static_cast('0' + ((c >> 3) & 7)); 56 | strm << static_cast('0' + ((c >> 0) & 7)); 57 | } 58 | } 59 | 60 | void Quote(std::ostream& strm, const char* begin, const char* end) { 61 | assert(!begin == !end); 62 | if (!begin) { 63 | strm << "null"; 64 | return; 65 | } 66 | strm << '"'; 67 | CEscape(strm, begin, end); 68 | strm << '"'; 69 | } 70 | 71 | } // namespace gitstatus 72 | -------------------------------------------------------------------------------- /gitstatus/src/check.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_CHECK_H_ 19 | #define ROMKATV_GITSTATUS_CHECK_H_ 20 | 21 | #include "logging.h" 22 | 23 | #include 24 | 25 | // The argument must be an expression convertible to bool. 26 | // Does nothing if the expression evaluates to true. Otherwise 27 | // it's equivalent to LOG(FATAL). 28 | #define CHECK(cond...) \ 29 | static_cast(0), (!!(cond)) ? static_cast(0) : LOG(FATAL) << #cond << ": " 30 | 31 | #define VERIFY(cond...) \ 32 | static_cast(0), ::gitstatus::internal_check::Thrower(!(cond)) \ 33 | ? static_cast(0) \ 34 | : LOG(ERROR) << #cond << ": " 35 | 36 | namespace gitstatus { 37 | 38 | struct Exception : std::exception { 39 | const char* what() const noexcept override { return "Exception"; } 40 | }; 41 | 42 | namespace internal_check { 43 | 44 | class Thrower { 45 | public: 46 | Thrower(bool should_throw) : throw_(should_throw) {} 47 | Thrower(Thrower&&) = delete; 48 | explicit operator bool() const { return !throw_; } 49 | ~Thrower() noexcept(false) { 50 | if (throw_) throw Exception(); 51 | } 52 | 53 | private: 54 | bool throw_; 55 | }; 56 | 57 | } // namespace internal_check 58 | 59 | } // namespace gitstatus 60 | 61 | #endif // ROMKATV_GITSTATUS_CHECK_H_ 62 | -------------------------------------------------------------------------------- /gitstatus/src/tag_db.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_TAG_DB_H_ 19 | #define ROMKATV_GITSTATUS_TAG_DB_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "arena.h" 34 | 35 | namespace gitstatus { 36 | 37 | struct Tag { 38 | const char* name; 39 | git_oid id; 40 | }; 41 | 42 | class TagDb { 43 | public: 44 | explicit TagDb(git_repository* repo); 45 | TagDb(TagDb&&) = delete; 46 | ~TagDb(); 47 | 48 | std::string TagForCommit(const git_oid& oid); 49 | 50 | private: 51 | void ReadLooseTags(); 52 | void UpdatePack(); 53 | void ParsePack(); 54 | void Wait(); 55 | 56 | bool IsLooseTag(const char* name) const; 57 | 58 | bool TagHasTarget(const char* name, const git_oid* target) const; 59 | 60 | git_repository* const repo_; 61 | git_refdb* const refdb_; 62 | 63 | Arena pack_arena_; 64 | struct stat pack_stat_ = {}; 65 | WithArena pack_; 66 | WithArena> name2id_; 67 | WithArena> id2name_; 68 | 69 | Arena loose_arena_; 70 | std::vector loose_tags_; 71 | 72 | std::mutex mutex_; 73 | std::condition_variable cv_; 74 | bool id2name_dirty_ = false; 75 | }; 76 | 77 | } // namespace gitstatus 78 | 79 | #endif // ROMKATV_GITSTATUS_TAG_DB_H_ 80 | -------------------------------------------------------------------------------- /gitstatus/src/dir.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_DIR_H_ 19 | #define ROMKATV_GITSTATUS_DIR_H_ 20 | 21 | #include 22 | #include 23 | 24 | #include "arena.h" 25 | 26 | namespace gitstatus { 27 | 28 | // On error, leaves entries unchanged and returns false. Does not throw. 29 | // 30 | // On success, appends names of files from the specified directory to entries and returns true. 31 | // Every appended entry is a null-terminated string. At -1 offset is its d_type. All elements 32 | // point into the arena. They are sorted either by strcmp or strcasecmp depending on case_sensitive. 33 | // 34 | // Does not close dir_fd. 35 | // 36 | // There are two distinct implementations of ListDir -- one for Linux and another for everything 37 | // else. The linux-specific implementation is 20% faster. 38 | // 39 | // The reason sorting is bundled with directory listing is performance on Linux. The API of 40 | // getdents64 allows for much faster sorting than what can be done with a plain vector. 41 | // For the POSIX implementation there is no need to bundle sorting in this way. In fact, it's 42 | // done at the end with a generic StrSort() call. 43 | // 44 | // For best results, reuse the arena and vector for multiple calls to avoid heap allocations. 45 | bool ListDir(int dir_fd, Arena& arena, std::vector& entries, bool precompose_unicode, 46 | bool case_sensitive); 47 | 48 | } // namespace gitstatus 49 | 50 | #endif // ROMKATV_GITSTATUS_DIR_H_ 51 | -------------------------------------------------------------------------------- /gitstatus/src/thread_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef ROMKATV_GITSTATUS_THREAD_POOL_H_ 2 | #define ROMKATV_GITSTATUS_THREAD_POOL_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "time.h" 15 | 16 | namespace gitstatus { 17 | 18 | class ThreadPool { 19 | public: 20 | explicit ThreadPool(size_t num_threads); 21 | ThreadPool(ThreadPool&&) = delete; 22 | 23 | // Waits for the currently running functions to finish. 24 | // Does NOT wait for the queue of functions to drain. 25 | // If you want the latter, call Wait() manually. 26 | ~ThreadPool(); 27 | 28 | // Runs `f` on one of the threads at or after time `t`. Can be called 29 | // from any thread. Can be called concurrently. 30 | // 31 | // Does not block. 32 | void Schedule(Time t, std::function f); 33 | 34 | void Schedule(std::function f) { Schedule(Clock::now(), std::move(f)); } 35 | 36 | // Blocks until the work queue is empty and there are no currently 37 | // running functions. 38 | void Wait(); 39 | 40 | size_t num_threads() const { return threads_.size(); } 41 | 42 | private: 43 | struct Work { 44 | bool operator<(const Work& w) const { return std::tie(w.t, w.idx) < std::tie(t, idx); } 45 | Time t; 46 | int64_t idx; 47 | mutable std::function f; 48 | }; 49 | 50 | void Loop(size_t tid); 51 | 52 | int64_t last_idx_ = 0; 53 | int64_t num_inflight_; 54 | bool exit_ = false; 55 | // Do we have a thread waiting on sleeper_cv_? 56 | bool have_sleeper_ = false; 57 | std::mutex mutex_; 58 | // Any number of threads can wait on this condvar. Always without a timeout. 59 | std::condition_variable cv_; 60 | // At most one thread can wait on this condvar at a time. Always with a timeout. 61 | std::condition_variable sleeper_cv_; 62 | // Signalled when the work queue is empty and there is nothing inflight. 63 | std::condition_variable idle_cv_; 64 | std::priority_queue work_; 65 | std::vector threads_; 66 | }; 67 | 68 | void InitGlobalThreadPool(size_t num_threads); 69 | 70 | ThreadPool* GlobalThreadPool(); 71 | 72 | } // namespace gitstatus 73 | 74 | #endif // ROMKATV_GITSTATUS_THREAD_POOL_H_ 75 | -------------------------------------------------------------------------------- /gitstatus/src/timer.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include "timer.h" 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #include "check.h" 28 | #include "logging.h" 29 | 30 | namespace gitstatus { 31 | 32 | namespace { 33 | 34 | double CpuTimeMs() { 35 | auto ToMs = [](const timeval& tv) { return 1e3 * tv.tv_sec + 1e-3 * tv.tv_usec; }; 36 | rusage usage = {}; 37 | CHECK(getrusage(RUSAGE_SELF, &usage) == 0) << Errno(); 38 | return ToMs(usage.ru_utime) + ToMs(usage.ru_stime); 39 | } 40 | 41 | double WallTimeMs() { 42 | // An attempt to call clock_gettime on an ancient version of MacOS fails at runtime. 43 | // It's possible to detect the presence of clock_gettime at runtime but I don't have 44 | // an ancient MacOS to test the code. Hence this. 45 | #ifdef __APPLE__ 46 | return std::numeric_limits::quiet_NaN(); 47 | #else 48 | struct timespec ts; 49 | clock_gettime(CLOCK_MONOTONIC, &ts); 50 | return 1e3 * ts.tv_sec + 1e-6 * ts.tv_nsec; 51 | #endif 52 | } 53 | 54 | } // namespace 55 | 56 | void Timer::Start() { 57 | cpu_ = CpuTimeMs(); 58 | wall_ = WallTimeMs(); 59 | } 60 | 61 | void Timer::Report(const char* msg) { 62 | double cpu = CpuTimeMs() - cpu_; 63 | if (std::isnan(wall_)) { 64 | LOG(INFO) << "Timing for: " << msg << ": " << cpu << "ms cpu"; 65 | } else { 66 | double wall = WallTimeMs() - wall_; 67 | LOG(INFO) << "Timing for: " << msg << ": " << cpu << "ms cpu, " << wall << "ms wall"; 68 | } 69 | Start(); 70 | } 71 | 72 | } // namespace gitstatus 73 | -------------------------------------------------------------------------------- /gitstatus/src/index.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_INDEX_H_ 19 | #define ROMKATV_GITSTATUS_INDEX_H_ 20 | 21 | #include 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include "arena.h" 30 | #include "options.h" 31 | #include "string_view.h" 32 | #include "tribool.h" 33 | 34 | namespace gitstatus { 35 | 36 | struct RepoCaps { 37 | RepoCaps(git_repository* repo, git_index* index); 38 | 39 | bool trust_filemode; 40 | bool has_symlinks; 41 | bool case_sensitive; 42 | bool precompose_unicode; 43 | }; 44 | 45 | struct ScanOpts { 46 | bool include_untracked; 47 | Tribool untracked_cache; 48 | }; 49 | 50 | struct IndexDir { 51 | explicit IndexDir(Arena* arena) : files(arena), subdirs(arena) {} 52 | 53 | StringView path; 54 | StringView basename; 55 | size_t depth = 0; 56 | struct stat st = {}; 57 | WithArena> files; 58 | WithArena> subdirs; 59 | 60 | Arena arena; 61 | std::vector unmatched; 62 | }; 63 | 64 | class Index { 65 | public: 66 | Index(git_repository* repo, git_index* index); 67 | 68 | std::vector GetDirtyCandidates(const ScanOpts& opts); 69 | 70 | private: 71 | size_t InitDirs(git_index* index); 72 | void InitSplits(size_t total_weight); 73 | 74 | Arena arena_; 75 | WithArena> dirs_; 76 | WithArena> splits_; 77 | git_index* git_index_; 78 | const char* root_dir_; 79 | RepoCaps caps_; 80 | }; 81 | 82 | } // namespace gitstatus 83 | 84 | #endif // ROMKATV_GITSTATUS_GIT_H_ 85 | -------------------------------------------------------------------------------- /gitstatus/src/thread_pool.cc: -------------------------------------------------------------------------------- 1 | #include "thread_pool.h" 2 | 3 | #include 4 | #include 5 | 6 | #include "check.h" 7 | #include "logging.h" 8 | 9 | namespace gitstatus { 10 | 11 | ThreadPool::ThreadPool(size_t num_threads) : num_inflight_(num_threads) { 12 | for (size_t i = 0; i != num_threads; ++i) { 13 | threads_.emplace_back([=]() { Loop(i + 1); }); 14 | } 15 | } 16 | 17 | ThreadPool::~ThreadPool() { 18 | { 19 | std::lock_guard lock(mutex_); 20 | exit_ = true; 21 | } 22 | cv_.notify_all(); 23 | sleeper_cv_.notify_one(); 24 | for (std::thread& t : threads_) t.join(); 25 | } 26 | 27 | void ThreadPool::Schedule(Time t, std::function f) { 28 | std::condition_variable* wake = nullptr; 29 | { 30 | std::unique_lock lock(mutex_); 31 | work_.push(Work{std::move(t), ++last_idx_, std::move(f)}); 32 | if (work_.top().idx == last_idx_) wake = have_sleeper_ ? &sleeper_cv_ : &cv_; 33 | } 34 | if (wake) wake->notify_one(); 35 | } 36 | 37 | void ThreadPool::Loop(size_t tid) { 38 | auto Next = [&]() -> std::function { 39 | std::unique_lock lock(mutex_); 40 | --num_inflight_; 41 | if (work_.empty() && num_inflight_ == 0) idle_cv_.notify_all(); 42 | while (true) { 43 | if (exit_) return nullptr; 44 | if (work_.empty()) { 45 | cv_.wait(lock); 46 | continue; 47 | } 48 | Time now = Clock::now(); 49 | const Work& top = work_.top(); 50 | if (top.t <= now) { 51 | std::function res = std::move(top.f); 52 | work_.pop(); 53 | ++num_inflight_; 54 | bool notify = !work_.empty() && !have_sleeper_; 55 | lock.unlock(); 56 | if (notify) cv_.notify_one(); 57 | return res; 58 | } 59 | if (have_sleeper_) { 60 | cv_.wait(lock); 61 | continue; 62 | } 63 | have_sleeper_ = true; 64 | sleeper_cv_.wait_until(lock, top.t); 65 | assert(have_sleeper_); 66 | have_sleeper_ = false; 67 | } 68 | }; 69 | while (std::function f = Next()) f(); 70 | } 71 | 72 | void ThreadPool::Wait() { 73 | std::unique_lock lock(mutex_); 74 | idle_cv_.wait(lock, [&] { return work_.empty() && num_inflight_ == 0; }); 75 | } 76 | 77 | static ThreadPool* g_thread_pool = nullptr; 78 | 79 | void InitGlobalThreadPool(size_t num_threads) { 80 | CHECK(!g_thread_pool); 81 | LOG(INFO) << "Spawning " << num_threads << " thread(s)"; 82 | g_thread_pool = new ThreadPool(num_threads); 83 | } 84 | 85 | ThreadPool* GlobalThreadPool() { return g_thread_pool; } 86 | 87 | } // namespace gitstatus 88 | -------------------------------------------------------------------------------- /gitstatus/src/string_view.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_STRING_VIEW_H_ 19 | #define ROMKATV_GITSTATUS_STRING_VIEW_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | namespace gitstatus { 28 | 29 | // WARNING: StringView must not have embedded null characters. Violations cause UB. 30 | struct StringView { 31 | StringView() : StringView("") {} 32 | 33 | // Requires: !memchr(s.data(), 0, s.size()). 34 | // 35 | // WARNING: The existence of this requirement and the fact that this constructor is implicit 36 | // means it's dangerous to have std::string instances with embedded null characters anywhere 37 | // in the program. If you have an std::string `s` with embedded nulls, an innocent-looking 38 | // `F(s)` might perform an implicit conversion to StringView and land you squarely in the 39 | // Undefined Behavior land. 40 | StringView(const std::string& s) : StringView(s.c_str(), s.size()) {} 41 | 42 | // Requires: !memchr(ptr, 0, len). 43 | StringView(const char* ptr, size_t len) : ptr(ptr), len(len) {} 44 | 45 | // Requires: end >= begin && !memchr(begin, 0, end - begin). 46 | StringView(const char* begin, const char* end) : StringView(begin, end - begin) {} 47 | 48 | // Requires: strchr(s, 0) == s + N. 49 | template 50 | StringView(const char (&s)[N]) : StringView(s, N - 1) { 51 | static_assert(N, ""); 52 | } 53 | 54 | // Explicit because it's the only constructor that isn't O(1). 55 | // Are you sure you don't already known the strings's length? 56 | explicit StringView(const char* ptr) : StringView(ptr, ptr ? std::strlen(ptr) : 0) {} 57 | 58 | bool StartsWith(StringView prefix) const { 59 | return len >= prefix.len && !std::memcmp(ptr, prefix.ptr, prefix.len); 60 | } 61 | 62 | bool EndsWith(StringView suffix) const { 63 | return len >= suffix.len && !std::memcmp(ptr + (len - suffix.len), suffix.ptr, suffix.len); 64 | } 65 | 66 | const char* ptr; 67 | size_t len; 68 | }; 69 | 70 | inline std::ostream& operator<<(std::ostream& strm, StringView s) { 71 | if (s.ptr) strm.write(s.ptr, s.len); 72 | return strm; 73 | } 74 | 75 | } // namespace gitstatus 76 | 77 | #endif // ROMKATV_GITSTATUS_STRING_VIEW_H_ 78 | -------------------------------------------------------------------------------- /gitstatus/src/print.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_PRINT_H_ 19 | #define ROMKATV_GITSTATUS_PRINT_H_ 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include "string_view.h" 33 | #include "strings.h" 34 | 35 | namespace gitstatus { 36 | 37 | template 38 | struct Printable { 39 | const T& value; 40 | }; 41 | 42 | template 43 | Printable Print(const T& val) { 44 | return {val}; 45 | } 46 | 47 | template 48 | std::ostream& operator<<(std::ostream& strm, const Printable& p) { 49 | static_assert(!std::is_pointer>(), ""); 50 | return strm << p.value; 51 | } 52 | 53 | inline std::ostream& operator<<(std::ostream& strm, const Printable& p) { 54 | Quote(strm, p.value.ptr, p.value.ptr + p.value.len); 55 | return strm; 56 | } 57 | 58 | inline std::ostream& operator<<(std::ostream& strm, const Printable& p) { 59 | Quote(strm, p.value.data(), p.value.data() + p.value.size()); 60 | return strm; 61 | } 62 | 63 | inline std::ostream& operator<<(std::ostream& strm, const Printable& p) { 64 | Quote(strm, p.value, p.value ? p.value + std::strlen(p.value) : nullptr); 65 | return strm; 66 | } 67 | 68 | inline std::ostream& operator<<(std::ostream& strm, const Printable& p) { 69 | Quote(strm, p.value, p.value ? p.value + std::strlen(p.value) : nullptr); 70 | return strm; 71 | } 72 | 73 | template 74 | std::ostream& operator<<(std::ostream& strm, const Printable>& p) { 75 | return strm << '{' << Print(p.value.first) << ", " << Print(p.value.second) << '}'; 76 | } 77 | 78 | template 79 | std::ostream& operator<<(std::ostream& strm, const Printable>& p) { 80 | strm << '['; 81 | for (size_t i = 0; i != p.value.size(); ++i) { 82 | if (i) strm << ", "; 83 | strm << Print(p.value[i]); 84 | } 85 | strm << ']'; 86 | return strm; 87 | } 88 | 89 | inline std::ostream& operator<<(std::ostream& strm, const Printable& p) { 90 | strm << p.value.tv_sec << '.' << std::setw(9) << std::setfill('0') << p.value.tv_nsec; 91 | return strm; 92 | } 93 | 94 | inline std::ostream& operator<<(std::ostream& strm, const Printable& p) { 95 | strm << p.value.seconds << '.' << std::setw(9) << std::setfill('0') << p.value.nanoseconds; 96 | return strm; 97 | } 98 | 99 | } // namespace gitstatus 100 | 101 | #endif // ROMKATV_GITSTATUS_PRINT_H_ 102 | -------------------------------------------------------------------------------- /gitstatus/src/options.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_OPTIONS_H_ 19 | #define ROMKATV_GITSTATUS_OPTIONS_H_ 20 | 21 | #include 22 | #include 23 | 24 | #include "logging.h" 25 | #include "time.h" 26 | 27 | namespace gitstatus { 28 | 29 | struct Limits { 30 | // Truncate commit summary if it's longer than this many bytes. 31 | size_t max_commit_summary_length = 256; 32 | // Report at most this many staged changes. 33 | size_t max_num_staged = 1; 34 | // Report at most this many unstaged changes. 35 | size_t max_num_unstaged = 1; 36 | // Report at most this many conflicted changes. 37 | size_t max_num_conflicted = 1; 38 | // Report at most this many untracked files. 39 | size_t max_num_untracked = 1; 40 | // If a repo has more files in its index than this, override max_num_unstaged and 41 | // max_num_untracked (but not max_num_staged) with zeros. 42 | size_t dirty_max_index_size = -1; 43 | // If true, report untracked files like `git status --untracked-files`. 44 | bool recurse_untracked_dirs = false; 45 | // Unless true, report zero untracked files for repositories with 46 | // status.showUntrackedFiles = false. 47 | bool ignore_status_show_untracked_files = false; 48 | // Unless true, report zero untracked files for repositories with 49 | // bash.showUntrackedFiles = false. 50 | bool ignore_bash_show_untracked_files = false; 51 | // Unless true, report zero staged, unstaged and conflicted changes for repositories with 52 | // bash.showDirtyState = false. 53 | bool ignore_bash_show_dirty_state = false; 54 | }; 55 | 56 | struct Options : Limits { 57 | // Use this many threads to scan git workdir for unstaged and untracked files. Must be positive. 58 | size_t num_threads = 1; 59 | // If non-negative, check whether the specified file descriptor is locked when not receiving any 60 | // requests for one second; exit if it isn't locked. 61 | int lock_fd = -1; 62 | // If non-negative, send signal 0 to the specified PID when not receiving any requests for one 63 | // second; exit if signal sending fails. 64 | int parent_pid = -1; 65 | // Don't write entries to log whose log level is below this. Log levels in increasing order: 66 | // DEBUG, INFO, WARN, ERROR, FATAL. 67 | LogLevel log_level = INFO; 68 | // Close git repositories that haven't been used for this long. This is meant to release resources 69 | // such as memory and file descriptors. The next request for a repo that's been closed is much 70 | // slower than for a repo that hasn't been. Negative value means infinity. 71 | Duration repo_ttl = std::chrono::seconds(3600); 72 | }; 73 | 74 | Options ParseOptions(int argc, char** argv); 75 | 76 | } // namespace gitstatus 77 | 78 | #endif // ROMKATV_GITSTATUS_OPTIONS_H_ 79 | -------------------------------------------------------------------------------- /internal/configure.zsh: -------------------------------------------------------------------------------- 1 | # Fewer than 47 columns will probably work. Haven't tried it. 2 | typeset -gr __p9k_wizard_columns=47 3 | # The bottleneck is ask_tails with nerd fonts. Everything else works fine with 12 lines. 4 | typeset -gr __p9k_wizard_lines=14 5 | typeset -gr __p9k_zd=${ZDOTDIR:-$HOME} 6 | typeset -gr __p9k_zd_u=${${${(q)__p9k_zd}/#(#b)${(q)HOME}(|\/*)/'~'$match[1]}//\%/%%} 7 | typeset -gr __p9k_zshrc=${${:-$__p9k_zd/.zshrc}:A} 8 | typeset -gr __p9k_zshrc_u=$__p9k_zd_u/.zshrc 9 | typeset -gr __p9k_root_dir_u=${${${(q)__p9k_root_dir}/#(#b)${(q)HOME}(|\/*)/'~'$match[1]}//\%/%%} 10 | 11 | function _p9k_can_configure() { 12 | [[ $1 == '-q' ]] && local -i q=1 || local -i q=0 13 | function $0_error() { 14 | (( q )) || print -rP "%1F[ERROR]%f %Bp10k configure%b: $1" >&2 15 | } 16 | typeset -g __p9k_cfg_path_o=${POWERLEVEL9K_CONFIG_FILE:=${ZDOTDIR:-~}/.p10k.zsh} 17 | typeset -g __p9k_cfg_basename=${__p9k_cfg_path_o:t} 18 | typeset -g __p9k_cfg_path=${__p9k_cfg_path_o:A} 19 | typeset -g __p9k_cfg_path_u=${${${(q)__p9k_cfg_path_o}/#(#b)${(q)HOME}(|\/*)/'~'$match[1]}//\%/%%} 20 | { 21 | [[ -e $__p9k_zd ]] || { $0_error "$__p9k_zd_u does not exist"; return 1 } 22 | [[ -d $__p9k_zd ]] || { $0_error "$__p9k_zd_u is not a directory"; return 1 } 23 | [[ ! -d $__p9k_cfg_path ]] || { $0_error "$__p9k_cfg_path_u is a directory"; return 1 } 24 | [[ ! -d $__p9k_zshrc ]] || { $0_error "$__p9k_zshrc_u is a directory"; return 1 } 25 | 26 | local dir=${__p9k_cfg_path:h} 27 | while [[ ! -e $dir && $dir != ${dir:h} ]]; do dir=${dir:h}; done 28 | if [[ ! -d $dir ]]; then 29 | $0_error "cannot create $__p9k_cfg_path_u because ${dir//\%/%%} is not a directory" 30 | return 1 31 | fi 32 | if [[ ! -w $dir ]]; then 33 | $0_error "cannot create $__p9k_cfg_path_u because ${dir//\%/%%} is readonly" 34 | return 1 35 | fi 36 | 37 | [[ ! -e $__p9k_cfg_path || -f $__p9k_cfg_path || -h $__p9k_cfg_path ]] || { 38 | $0_error "$__p9k_cfg_path_u is a special file" 39 | return 1 40 | } 41 | [[ ! -e $__p9k_zshrc || -f $__p9k_zshrc || -h $__p9k_zshrc ]] || { 42 | $0_error "$__p9k_zshrc_u a special file" 43 | return 1 44 | } 45 | [[ ! -e $__p9k_zshrc || -r $__p9k_zshrc ]] || { 46 | $0_error "$__p9k_zshrc_u is not readable" 47 | return 1 48 | } 49 | local style 50 | for style in lean lean-8colors classic rainbow pure; do 51 | [[ -r $__p9k_root_dir/config/p10k-$style.zsh ]] || { 52 | $0_error "$__p9k_root_dir_u/config/p10k-$style.zsh is not readable" 53 | return 1 54 | } 55 | done 56 | 57 | (( LINES >= __p9k_wizard_lines && COLUMNS >= __p9k_wizard_columns )) || { 58 | $0_error "terminal size too small; must be at least $__p9k_wizard_columns columns by $__p9k_wizard_lines lines" 59 | return 1 60 | } 61 | [[ -t 0 && -t 1 ]] || { 62 | $0_error "no TTY" 63 | return 2 64 | } 65 | return 0 66 | } always { 67 | unfunction $0_error 68 | } 69 | } 70 | 71 | function p9k_configure() { 72 | eval "$__p9k_intro" 73 | _p9k_can_configure || return 74 | ( 75 | set -- -f 76 | builtin source $__p9k_root_dir/internal/wizard.zsh 77 | ) 78 | local ret=$? 79 | case $ret in 80 | 0) builtin source $__p9k_cfg_path; _p9k__force_must_init=1;; 81 | 69) return 0;; 82 | *) return $ret;; 83 | esac 84 | } 85 | -------------------------------------------------------------------------------- /gitstatus/src/git.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_GIT_H_ 19 | #define ROMKATV_GITSTATUS_GIT_H_ 20 | 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | namespace gitstatus { 28 | 29 | // Not null. 30 | const char* GitError(); 31 | 32 | // Not null. 33 | std::string RepoState(git_repository* repo); 34 | 35 | // Returns the number of commits in the range. 36 | size_t CountRange(git_repository* repo, const std::string& range); 37 | 38 | // How many stashes are there? 39 | size_t NumStashes(git_repository* repo); 40 | 41 | // Returns the origin URL or an empty string. Not null. 42 | std::string RemoteUrl(git_repository* repo, const git_reference* ref); 43 | 44 | // Returns reference to HEAD or null if not found. The reference is symbolic if the repo is empty 45 | // and direct otherwise. 46 | git_reference* Head(git_repository* repo); 47 | 48 | // Returns the name of the local branch, or an empty string. 49 | const char* LocalBranchName(const git_reference* ref); 50 | 51 | struct CommitMessage { 52 | // Can be empty, meaning "UTF-8". 53 | std::string encoding; 54 | // The first paragraph of the commit's message as a one-liner. 55 | std::string summary; 56 | }; 57 | 58 | CommitMessage GetCommitMessage(git_repository* repo, const git_oid& id); 59 | 60 | struct Remote { 61 | // Tip of the remote branch. 62 | git_reference* ref; 63 | 64 | // Name of the tracking remote. For example, "origin". 65 | std::string name; 66 | 67 | // Name of the tracking remote branch. For example, "master". 68 | std::string branch; 69 | 70 | // URL of the tracking remote. For example, "https://foo.com/repo.git". 71 | std::string url; 72 | 73 | // Note: pushurl is not exposed (but could be). 74 | 75 | struct Free { 76 | void operator()(const Remote* p) const { 77 | if (p) { 78 | if (p->ref) git_reference_free(p->ref); 79 | delete p; 80 | } 81 | } 82 | }; 83 | }; 84 | 85 | struct PushRemote { 86 | // Tip of the remote branch. 87 | git_reference* ref; 88 | 89 | // Name of the tracking remote. For example, "origin". 90 | std::string name; 91 | 92 | // URL of the tracking remote. For example, "https://foo.com/repo.git". 93 | std::string url; 94 | 95 | // Note: pushurl is not exposed (but could be). 96 | 97 | struct Free { 98 | void operator()(const PushRemote* p) const { 99 | if (p) { 100 | if (p->ref) git_reference_free(p->ref); 101 | delete p; 102 | } 103 | } 104 | }; 105 | }; 106 | 107 | using RemotePtr = std::unique_ptr; 108 | using PushRemotePtr = std::unique_ptr; 109 | 110 | RemotePtr GetRemote(git_repository* repo, const git_reference* local); 111 | PushRemotePtr GetPushRemote(git_repository* repo, const git_reference* local); 112 | 113 | } // namespace gitstatus 114 | 115 | #endif // ROMKATV_GITSTATUS_GIT_H_ 116 | -------------------------------------------------------------------------------- /gitstatus/src/logging.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_LOGGING_H_ 19 | #define ROMKATV_GITSTATUS_LOGGING_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #define LOG(severity) LOG_I(severity) 27 | 28 | #define LOG_I(severity) \ 29 | (::gitstatus::severity < ::gitstatus::g_min_log_level) \ 30 | ? static_cast(0) \ 31 | : ::gitstatus::internal_logging::Assignable() = \ 32 | ::gitstatus::internal_logging::LogStream<::gitstatus::severity>(__FILE__, __LINE__, \ 33 | ::gitstatus::severity) \ 34 | .ref() 35 | 36 | namespace gitstatus { 37 | 38 | enum LogLevel { 39 | DEBUG, 40 | INFO, 41 | WARN, 42 | ERROR, 43 | FATAL, 44 | }; 45 | 46 | const char* LogLevelStr(LogLevel lvl); 47 | bool ParseLogLevel(const char* s, LogLevel& lvl); 48 | 49 | extern LogLevel g_min_log_level; 50 | 51 | namespace internal_logging { 52 | 53 | struct Assignable { 54 | template 55 | void operator=(const T&) const {} 56 | }; 57 | 58 | class LogStreamBase { 59 | public: 60 | LogStreamBase(const char* file, int line, LogLevel lvl); 61 | 62 | LogStreamBase& ref() { return *this; } 63 | std::ostream& strm() { return *strm_; } 64 | int stashed_errno() const { return errno_; } 65 | 66 | protected: 67 | void Flush(); 68 | 69 | private: 70 | int errno_; 71 | const char* file_; 72 | int line_; 73 | const char* lvl_; 74 | std::unique_ptr strm_; 75 | }; 76 | 77 | template 78 | class LogStream : public LogStreamBase { 79 | public: 80 | using LogStreamBase::LogStreamBase; 81 | ~LogStream() { this->Flush(); } 82 | }; 83 | 84 | template <> 85 | class LogStream : public LogStreamBase { 86 | public: 87 | using LogStreamBase::LogStreamBase; 88 | ~LogStream() __attribute__((noreturn)) { 89 | this->Flush(); 90 | std::abort(); 91 | } 92 | }; 93 | 94 | template 95 | LogStreamBase& operator<<(LogStreamBase& strm, const T& val) { 96 | strm.strm() << val; 97 | return strm; 98 | } 99 | 100 | inline LogStreamBase& operator<<(LogStreamBase& strm, std::ostream& (*manip)(std::ostream&)) { 101 | strm.strm() << manip; 102 | return strm; 103 | } 104 | 105 | struct Errno { 106 | int err; 107 | }; 108 | 109 | std::ostream& operator<<(std::ostream& strm, Errno e); 110 | 111 | struct StashedErrno {}; 112 | 113 | inline LogStreamBase& operator<<(LogStreamBase& strm, StashedErrno) { 114 | return strm << Errno{strm.stashed_errno()}; 115 | } 116 | 117 | } // namespace internal_logging 118 | 119 | inline internal_logging::Errno Errno(int err) { return {err}; } 120 | inline internal_logging::StashedErrno Errno() { return {}; } 121 | 122 | } // namespace gitstatus 123 | 124 | #endif // ROMKATV_GITSTATUS_LOGGING_H_ 125 | -------------------------------------------------------------------------------- /gitstatus/src/repo.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_REPO_H_ 19 | #define ROMKATV_GITSTATUS_REPO_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | 41 | #include "check.h" 42 | #include "index.h" 43 | #include "options.h" 44 | #include "string_cmp.h" 45 | #include "tag_db.h" 46 | #include "time.h" 47 | 48 | namespace gitstatus { 49 | 50 | struct IndexStats { 51 | size_t index_size = 0; 52 | size_t num_staged = 0; 53 | size_t num_unstaged = 0; 54 | size_t num_conflicted = 0; 55 | size_t num_untracked = 0; 56 | size_t num_staged_new = 0; 57 | size_t num_staged_deleted = 0; 58 | size_t num_unstaged_deleted = 0; 59 | size_t num_skip_worktree = 0; 60 | size_t num_assume_unchanged = 0; 61 | }; 62 | 63 | class Repo { 64 | public: 65 | explicit Repo(git_repository* repo, Limits lim); 66 | Repo(Repo&& other) = delete; 67 | ~Repo(); 68 | 69 | git_repository* repo() const { return repo_; } 70 | 71 | // Head can be null, in which case has_staged will be false. 72 | IndexStats GetIndexStats(const git_oid* head, git_config* cfg); 73 | 74 | // Returns the last tag in lexicographical order whose target is equal to the given, or an 75 | // empty string. Target can be null, in which case the tag is empty. 76 | std::future GetTagName(const git_oid* target); 77 | 78 | private: 79 | struct Shard { 80 | bool Contains(Str<> str, StringView path) const; 81 | std::string start_s; 82 | std::string end_s; 83 | size_t start_i; 84 | size_t end_i; 85 | }; 86 | 87 | void UpdateShards(); 88 | 89 | int OnDelta(const char* type, const git_diff_delta& d, std::atomic& c1, size_t m1, 90 | const std::atomic& c2, size_t m2); 91 | 92 | void StartStagedScan(const git_oid* head); 93 | void StartDirtyScan(const std::vector& paths); 94 | 95 | void DecInflight(); 96 | void RunAsync(std::function f); 97 | void Wait(); 98 | 99 | Limits lim_; 100 | git_repository* const repo_; 101 | git_index* git_index_ = nullptr; 102 | std::vector shards_; 103 | git_oid head_ = {}; 104 | TagDb tag_db_; 105 | 106 | std::unique_ptr index_; 107 | 108 | std::mutex mutex_; 109 | std::condition_variable cv_; 110 | std::atomic inflight_{0}; 111 | std::atomic error_{false}; 112 | std::atomic staged_{0}; 113 | std::atomic unstaged_{0}; 114 | std::atomic conflicted_{0}; 115 | std::atomic untracked_{0}; 116 | std::atomic staged_new_{0}; 117 | std::atomic staged_deleted_{0}; 118 | std::atomic unstaged_deleted_{0}; 119 | std::atomic skip_worktree_{0}; 120 | std::atomic assume_unchanged_{0}; 121 | std::atomic untracked_cache_{Tribool::kUnknown}; 122 | }; 123 | 124 | } // namespace gitstatus 125 | 126 | #endif // ROMKATV_GITSTATUS_REPO_H_ 127 | -------------------------------------------------------------------------------- /powerlevel10k.zsh-theme: -------------------------------------------------------------------------------- 1 | # vim:ft=zsh ts=2 sw=2 sts=2 et fenc=utf-8 2 | ################################################################ 3 | # Powerlevel10k Theme 4 | # https://github.com/romkatv/powerlevel10k 5 | # 6 | # Forked from Powerlevel9k Theme 7 | # https://github.com/bhilburn/powerlevel9k 8 | # 9 | # Which in turn was forked from Agnoster Theme 10 | # https://github.com/robbyrussell/oh-my-zsh/blob/74177c5320b2a1b2f8c4c695c05984b57fd7c6ea/themes/agnoster.zsh-theme 11 | ################################################################ 12 | 13 | # Temporarily change options. 14 | 'builtin' 'local' '-a' '__p9k_src_opts' 15 | [[ ! -o 'aliases' ]] || __p9k_src_opts+=('aliases') 16 | [[ ! -o 'sh_glob' ]] || __p9k_src_opts+=('sh_glob') 17 | [[ ! -o 'no_brace_expand' ]] || __p9k_src_opts+=('no_brace_expand') 18 | 'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand' 19 | 20 | (( $+__p9k_root_dir )) || typeset -gr __p9k_root_dir=${POWERLEVEL9K_INSTALLATION_DIR:-${${(%):-%x}:A:h}} 21 | (( $+__p9k_intro )) || { 22 | # Leading spaces before `local` are important. Otherwise Antigen will remove `local` (!!!). 23 | # __p9k_trapint is to work around bugs in zsh: https://www.zsh.org/mla/workers/2020/msg00612.html. 24 | # Likewise for `trap ":"` instead of the plain `trap ""`. 25 | typeset -gr __p9k_intro_base='emulate -L zsh -o no_hist_expand -o extended_glob -o no_prompt_bang -o prompt_percent -o no_prompt_subst -o no_aliases -o no_bg_nice -o typeset_silent -o no_rematch_pcre 26 | (( $+__p9k_trapped )) || { local -i __p9k_trapped; trap : INT; trap "trap ${(q)__p9k_trapint:--} INT" EXIT } 27 | local -a match mbegin mend 28 | local -i MBEGIN MEND OPTIND 29 | local MATCH OPTARG IFS=$'\'' \t\n\0'\' 30 | typeset -gr __p9k_intro_locale='[[ $langinfo[CODESET] != (utf|UTF)(-|)8 ]] && _p9k_init_locale && { [[ -n $LC_ALL ]] && local LC_ALL=$__p9k_locale || local LC_CTYPE=$__p9k_locale }' 31 | typeset -gr __p9k_intro_no_locale="${${__p9k_intro_base/ match / match reply }/ MATCH / MATCH REPLY }" 32 | typeset -gr __p9k_intro_no_reply="$__p9k_intro_base; $__p9k_intro_locale" 33 | typeset -gr __p9k_intro="$__p9k_intro_no_locale; $__p9k_intro_locale" 34 | } 35 | 36 | zmodload zsh/langinfo 37 | 38 | function _p9k_init_locale() { 39 | if (( ! $+__p9k_locale )); then 40 | typeset -g __p9k_locale= 41 | (( $+commands[locale] )) || return 42 | local -a loc 43 | loc=(${(@M)$(locale -a 2>/dev/null):#*.(utf|UTF)(-|)8}) || return 44 | (( $#loc )) || return 45 | typeset -g __p9k_locale=${loc[(r)(#i)C.UTF(-|)8]:-${loc[(r)(#i)en_US.UTF(-|)8]:-$loc[1]}} 46 | fi 47 | [[ -n $__p9k_locale ]] 48 | } 49 | 50 | () { 51 | eval "$__p9k_intro" 52 | if (( $+__p9k_sourced )); then 53 | (( $+functions[_p9k_setup] )) && _p9k_setup 54 | return 0 55 | fi 56 | typeset -gr __p9k_dump_file=${XDG_CACHE_HOME:-~/.cache}/p10k-dump-${(%):-%n}.zsh 57 | if [[ $__p9k_dump_file != $__p9k_instant_prompt_dump_file ]] && (( ! $+functions[_p9k_preinit] )) && source $__p9k_dump_file 2>/dev/null && (( $+functions[_p9k_preinit] )); then 58 | _p9k_preinit 59 | fi 60 | typeset -gr __p9k_sourced=13 61 | if [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]]; then 62 | if [[ -w $__p9k_root_dir && -w $__p9k_root_dir/internal && -w $__p9k_root_dir/gitstatus ]]; then 63 | local f 64 | for f in $__p9k_root_dir/{powerlevel9k.zsh-theme,powerlevel10k.zsh-theme,internal/p10k.zsh,internal/icons.zsh,internal/configure.zsh,internal/worker.zsh,internal/parser.zsh,gitstatus/gitstatus.plugin.zsh,gitstatus/install}; do 65 | [[ $f.zwc -nt $f ]] && continue 66 | zmodload -F zsh/files b:zf_mv b:zf_rm 67 | local tmp=$f.tmp.$$.zwc 68 | { 69 | # `zf_mv -f src dst` fails on NTFS if `dst` is not writable, hence `zf_rm`. 70 | zf_rm -f -- $f.zwc && zcompile -R -- $tmp $f && zf_mv -f -- $tmp $f.zwc 71 | } always { 72 | (( $? )) && zf_rm -f -- $tmp 73 | } 74 | done 75 | fi 76 | fi 77 | builtin source $__p9k_root_dir/internal/p10k.zsh || true 78 | } 79 | 80 | (( $+__p9k_instant_prompt_active )) && unsetopt prompt_cr prompt_sp || setopt prompt_cr prompt_sp 81 | 82 | (( ${#__p9k_src_opts} )) && setopt ${__p9k_src_opts[@]} 83 | 'builtin' 'unset' '__p9k_src_opts' 84 | -------------------------------------------------------------------------------- /gitstatus/src/arena.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include "arena.h" 19 | 20 | #include 21 | #include 22 | 23 | #include "bits.h" 24 | #include "check.h" 25 | 26 | namespace gitstatus { 27 | 28 | namespace { 29 | 30 | size_t Clamp(size_t min, size_t val, size_t max) { return std::min(max, std::max(min, val)); } 31 | 32 | static const uintptr_t kSingularity = reinterpret_cast(&kSingularity); 33 | 34 | } // namespace 35 | 36 | // Triple singularity. We are all fucked. 37 | Arena::Block Arena::g_empty_block = {kSingularity, kSingularity, kSingularity}; 38 | 39 | Arena::Arena(Arena::Options opt) : opt_(std::move(opt)), top_(&g_empty_block) { 40 | CHECK(opt_.min_block_size <= opt_.max_block_size); 41 | } 42 | 43 | Arena::Arena(Arena&& other) : Arena() { *this = std::move(other); } 44 | 45 | Arena::~Arena() { 46 | // See comments in Makefile for the reason sized deallocation is not used. 47 | for (const Block& b : blocks_) ::operator delete(reinterpret_cast(b.start)); 48 | } 49 | 50 | Arena& Arena::operator=(Arena&& other) { 51 | if (this != &other) { 52 | // In case std::vector ever gets small object optimization. 53 | size_t idx = other.reusable_ ? other.top_ - other.blocks_.data() : 0; 54 | opt_ = other.opt_; 55 | blocks_ = std::move(other.blocks_); 56 | reusable_ = other.reusable_; 57 | top_ = reusable_ ? blocks_.data() + idx : &g_empty_block; 58 | other.blocks_.clear(); 59 | other.reusable_ = 0; 60 | other.top_ = &g_empty_block; 61 | } 62 | return *this; 63 | } 64 | 65 | void Arena::Reuse(size_t num_blocks) { 66 | reusable_ = std::min(reusable_, num_blocks); 67 | for (size_t i = reusable_; i != blocks_.size(); ++i) { 68 | const Block& b = blocks_[i]; 69 | // See comments in Makefile for the reason sized deallocation is not used. 70 | ::operator delete(reinterpret_cast(b.start)); 71 | } 72 | blocks_.resize(reusable_); 73 | if (reusable_) { 74 | top_ = blocks_.data(); 75 | top_->tip = top_->start; 76 | } else { 77 | top_ = &g_empty_block; 78 | } 79 | } 80 | 81 | void Arena::AddBlock(size_t size, size_t alignment) { 82 | if (alignment > alignof(std::max_align_t)) { 83 | size += alignment - 1; 84 | } else { 85 | size = std::max(size, alignment); 86 | } 87 | if (size <= top_->size() && top_ < blocks_.data() + reusable_ - 1) { 88 | assert(blocks_.front().size() == top_->size()); 89 | ++top_; 90 | top_->tip = top_->start; 91 | return; 92 | } 93 | if (size <= opt_.max_alloc_threshold) { 94 | size = 95 | std::max(size, Clamp(opt_.min_block_size, NextPow2(top_->size() + 1), opt_.max_block_size)); 96 | } 97 | 98 | auto p = reinterpret_cast(::operator new(size)); 99 | blocks_.push_back(Block{p, p, p + size}); 100 | if (reusable_) { 101 | if (size < blocks_.front().size()) { 102 | top_ = &blocks_.back(); 103 | return; 104 | } 105 | if (size > blocks_.front().size()) reusable_ = 0; 106 | } 107 | std::swap(blocks_.back(), blocks_[reusable_]); 108 | top_ = &blocks_[reusable_++]; 109 | } 110 | 111 | void* Arena::AllocateSlow(size_t size, size_t alignment) { 112 | assert(alignment && !(alignment & (alignment - 1))); 113 | AddBlock(size, alignment); 114 | assert(Align(top_->tip, alignment) + size <= top_->end); 115 | return Allocate(size, alignment); 116 | } 117 | 118 | } // namespace gitstatus 119 | -------------------------------------------------------------------------------- /gitstatus/src/request.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include "request.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include "check.h" 31 | #include "logging.h" 32 | #include "print.h" 33 | #include "serialization.h" 34 | 35 | namespace gitstatus { 36 | 37 | namespace { 38 | 39 | Request ParseRequest(const std::string& s) { 40 | Request res; 41 | auto begin = s.begin(), end = s.end(), sep = std::find(begin, end, kFieldSep); 42 | VERIFY(sep != end) << "Malformed request: " << s; 43 | res.id.assign(begin, sep); 44 | 45 | begin = sep + 1; 46 | if (*begin == ':') { 47 | res.from_dotgit = true; 48 | ++begin; 49 | } 50 | sep = std::find(begin, end, kFieldSep); 51 | res.dir.assign(begin, sep); 52 | if (sep == end) return res; 53 | 54 | begin = sep + 1; 55 | VERIFY(begin + 1 == end && (*begin == '0' || *begin == '1')) << "Malformed request: " << s; 56 | res.diff = *begin == '0'; 57 | return res; 58 | } 59 | 60 | bool IsLockedFd(int fd) { 61 | CHECK(fd >= 0); 62 | struct flock flock = {}; 63 | flock.l_type = F_RDLCK; 64 | flock.l_whence = SEEK_SET; 65 | CHECK(fcntl(fd, F_GETLK, &flock) != -1) << Errno(); 66 | return flock.l_type != F_UNLCK; 67 | } 68 | 69 | } // namespace 70 | 71 | std::ostream& operator<<(std::ostream& strm, const Request& req) { 72 | strm << Print(req.id) << " for " << Print(req.dir); 73 | if (req.from_dotgit) strm << " [from-dotgit]"; 74 | if (!req.diff) strm << " [no-diff]"; 75 | return strm; 76 | } 77 | 78 | RequestReader::RequestReader(int fd, int lock_fd, int parent_pid) 79 | : fd_(fd), lock_fd_(lock_fd), parent_pid_(parent_pid) { 80 | CHECK(fd != lock_fd); 81 | } 82 | 83 | bool RequestReader::ReadRequest(Request& req) { 84 | auto eol = std::find(read_.begin(), read_.end(), kMsgSep); 85 | if (eol != read_.end()) { 86 | std::string msg(read_.begin(), eol); 87 | read_.erase(read_.begin(), eol + 1); 88 | req = ParseRequest(msg); 89 | return true; 90 | } 91 | 92 | char buf[256]; 93 | while (true) { 94 | fd_set fds; 95 | FD_ZERO(&fds); 96 | FD_SET(fd_, &fds); 97 | struct timeval timeout = {.tv_sec = 1}; 98 | 99 | int n; 100 | CHECK((n = select(fd_ + 1, &fds, NULL, NULL, &timeout)) >= 0) << Errno(); 101 | if (n == 0) { 102 | if (lock_fd_ >= 0 && !IsLockedFd(lock_fd_)) { 103 | LOG(INFO) << "Lock on fd " << lock_fd_ << " is gone. Exiting."; 104 | std::exit(0); 105 | } 106 | if (parent_pid_ >= 0 && kill(parent_pid_, 0)) { 107 | LOG(INFO) << "Unable to send signal 0 to " << parent_pid_ << ". Exiting."; 108 | std::exit(0); 109 | } 110 | req = {}; 111 | return false; 112 | } 113 | 114 | CHECK((n = read(fd_, buf, sizeof(buf))) >= 0) << Errno(); 115 | if (n == 0) { 116 | LOG(INFO) << "EOF. Exiting."; 117 | std::exit(0); 118 | } 119 | read_.insert(read_.end(), buf, buf + n); 120 | int eol = std::find(buf, buf + n, kMsgSep) - buf; 121 | if (eol != n) { 122 | std::string msg(read_.begin(), read_.end() - (n - eol)); 123 | read_.erase(read_.begin(), read_.begin() + msg.size() + 1); 124 | req = ParseRequest(msg); 125 | return true; 126 | } 127 | } 128 | } 129 | 130 | } // namespace gitstatus 131 | -------------------------------------------------------------------------------- /gitstatus/src/logging.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include "logging.h" 19 | 20 | #include 21 | #include 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | namespace gitstatus { 31 | 32 | namespace internal_logging { 33 | 34 | namespace { 35 | 36 | std::mutex g_log_mutex; 37 | 38 | constexpr char kHexLower[] = {'0', '1', '2', '3', '4', '5', '6', '7', 39 | '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; 40 | 41 | void FormatThreadId(char (&out)[2 * sizeof(std::uintptr_t) + 1]) { 42 | std::uintptr_t tid = (std::uintptr_t)pthread_self(); 43 | char* p = out + sizeof(out) - 1; 44 | *p = 0; 45 | do { 46 | --p; 47 | *p = kHexLower[tid & 0xF]; 48 | tid >>= 4; 49 | } while (p != out); 50 | } 51 | 52 | void FormatCurrentTime(char (&out)[64]) { 53 | std::time_t time = std::time(nullptr); 54 | struct tm tm; 55 | if (localtime_r(&time, &tm) != &tm || std::strftime(out, sizeof(out), "%F %T", &tm) == 0) { 56 | std::strcpy(out, "undef"); 57 | } 58 | } 59 | 60 | } // namespace 61 | 62 | LogStreamBase::LogStreamBase(const char* file, int line, LogLevel lvl) 63 | : errno_(errno), file_(file), line_(line), lvl_(LogLevelStr(lvl)) { 64 | strm_ = std::make_unique(); 65 | } 66 | 67 | void LogStreamBase::Flush() { 68 | { 69 | std::string msg = strm_->str(); 70 | char tid[2 * sizeof(std::uintptr_t) + 1]; 71 | FormatThreadId(tid); 72 | char time[64]; 73 | FormatCurrentTime(time); 74 | 75 | std::unique_lock lock(g_log_mutex); 76 | std::fprintf(stderr, "[%s %s %s %s:%d] %s\n", time, tid, lvl_, file_, line_, msg.c_str()); 77 | } 78 | strm_.reset(); 79 | errno = errno_; 80 | } 81 | 82 | std::ostream& operator<<(std::ostream& strm, Errno e) { 83 | // GNU C Library uses a buffer of 1024 characters for strerror(). Mimic to avoid truncations. 84 | char buf[1024]; 85 | auto x = strerror_r(e.err, buf, sizeof(buf)); 86 | // There are two versions of strerror_r with different semantics. We can figure out which 87 | // one we've got by looking at the result type. 88 | if (std::is_same::value) { 89 | // XSI-compliant version. 90 | strm << (x ? "unknown error" : buf); 91 | } else if (std::is_same::value) { 92 | // GNU-specific version. 93 | strm << x; 94 | } else { 95 | // Something else entirely. 96 | strm << "unknown error"; 97 | } 98 | return strm; 99 | } 100 | 101 | } // namespace internal_logging 102 | 103 | LogLevel g_min_log_level = INFO; 104 | 105 | const char* LogLevelStr(LogLevel lvl) { 106 | switch (lvl) { 107 | case DEBUG: 108 | return "DEBUG"; 109 | case INFO: 110 | return "INFO"; 111 | case WARN: 112 | return "WARN"; 113 | case ERROR: 114 | return "ERROR"; 115 | case FATAL: 116 | return "FATAL"; 117 | } 118 | return "UNKNOWN"; 119 | } 120 | 121 | bool ParseLogLevel(const char* s, LogLevel& lvl) { 122 | if (!s) 123 | return false; 124 | else if (!std::strcmp(s, "DEBUG")) 125 | lvl = DEBUG; 126 | else if (!std::strcmp(s, "INFO")) 127 | lvl = INFO; 128 | else if (!std::strcmp(s, "WARN")) 129 | lvl = WARN; 130 | else if (!std::strcmp(s, "ERROR")) 131 | lvl = ERROR; 132 | else if (!std::strcmp(s, "FATAL")) 133 | lvl = FATAL; 134 | else 135 | return false; 136 | return true; 137 | } 138 | 139 | } // namespace gitstatus 140 | -------------------------------------------------------------------------------- /gitstatus/install.info: -------------------------------------------------------------------------------- 1 | # 3 2 | # 3 | # This file is used by ./install and indirectly by shell bindings. 4 | # 5 | # The first line is read by powerlevel10k instant prompt. It must 6 | # be updated whenever the content of this file changes. The actual 7 | # value doesn't matter as long as it's unique. Consecutive integers 8 | # work fine. 9 | 10 | # Official gitstatusd binaries. 11 | uname_s_glob="cygwin_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="5a8a809dcebdb6aa9b47d37e086c0485424a9d9c136770eec3c26cedf5bb75e3"; 12 | uname_s_glob="cygwin_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="c84cade0d6b86e04c27a6055f45851f6b46d6b88ba58772f7ca8ef4d295c800f"; 13 | uname_s_glob="darwin"; uname_m_glob="arm64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="eae979e990ca37c56ee39fadd0c3f392cbbd0c6bdfb9a603010be60d9e48910a"; 14 | uname_s_glob="darwin"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="9fd3913ec1b6b856ab6e08a99a2343f0e8e809eb6b62ca4b0963163656c668e6"; 15 | uname_s_glob="freebsd"; uname_m_glob="amd64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="8e57ad642251e5acfa430aed82cd4ffe103db0bfadae4a15ccaf462c455d0442"; 16 | uname_s_glob="linux"; uname_m_glob="aarch64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="32b57eb28bf6d80b280e4020a0045184f8ca897b20b570c12948aa6838673225"; 17 | uname_s_glob="linux"; uname_m_glob="armv6l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="4bf5a0d0a082f544a48536ad3675930d5d2cc6a8cf906710045e0788f51192b3"; 18 | uname_s_glob="linux"; uname_m_glob="armv7l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="2b9deb29f86c8209114b71b94fc2e1ed936a1658808a1bee46f4a82fd6a1f8cc"; 19 | uname_s_glob="linux"; uname_m_glob="armv8l"; file="gitstatusd-${uname_s}-aarch64"; version="v1.5.4"; sha256="32b57eb28bf6d80b280e4020a0045184f8ca897b20b570c12948aa6838673225"; 20 | uname_s_glob="linux"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="56d55e2e9a202d3072fa612d8fa1faa61243ffc86418a7fa64c2c9d9a82e0f64"; 21 | uname_s_glob="linux"; uname_m_glob="ppc64le"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="1afd072c8c26ef6ec2d9ac11cef96c84cd6f10e859665a6ffcfb6112c758547e"; 22 | uname_s_glob="linux"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.4"; sha256="9633816e7832109e530c9e2532b11a1edae08136d63aa7e40246c0339b7db304"; 23 | uname_s_glob="msys_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20"; 24 | uname_s_glob="msys_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732"; 25 | 26 | # Fallbacks to official gitstatusd binaries. 27 | uname_s_glob="cygwin_nt-*"; uname_m_glob="i686"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.5.2"; sha256="5a8a809dcebdb6aa9b47d37e086c0485424a9d9c136770eec3c26cedf5bb75e3"; 28 | uname_s_glob="cygwin_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="c84cade0d6b86e04c27a6055f45851f6b46d6b88ba58772f7ca8ef4d295c800f"; 29 | uname_s_glob="mingw32_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20"; 30 | uname_s_glob="mingw32_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732"; 31 | uname_s_glob="mingw64_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20"; 32 | uname_s_glob="mingw64_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732"; 33 | uname_s_glob="msys_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20"; 34 | uname_s_glob="msys_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732"; 35 | -------------------------------------------------------------------------------- /gitstatus/src/string_cmp.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_STRING_CMP_H_ 19 | #define ROMKATV_GITSTATUS_STRING_CMP_H_ 20 | 21 | #include // because there is no std::strcasecmp in C++ 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "string_view.h" 29 | 30 | namespace gitstatus { 31 | 32 | // WARNING: These routines assume no embedded null characters in StringView. Violations cause UB. 33 | 34 | template 35 | struct StrCmp; 36 | 37 | template <> 38 | struct StrCmp<0> { 39 | int operator()(StringView x, StringView y) const { 40 | size_t n = std::min(x.len, y.len); 41 | int cmp = strncasecmp(x.ptr, y.ptr, n); 42 | if (cmp) return cmp; 43 | return static_cast(x.len) - static_cast(y.len); 44 | } 45 | 46 | int operator()(StringView x, const char* y) const { 47 | for (const char *p = x.ptr, *e = p + x.len; p != e; ++p, ++y) { 48 | if (int cmp = std::tolower(*p) - std::tolower(*y)) return cmp; 49 | } 50 | return 0 - *y; 51 | } 52 | 53 | int operator()(char x, char y) const { return std::tolower(x) - std::tolower(y); } 54 | int operator()(const char* x, const char* y) const { return strcasecmp(x, y); } 55 | int operator()(const char* x, StringView y) const { return -operator()(y, x); } 56 | }; 57 | 58 | template <> 59 | struct StrCmp<1> { 60 | int operator()(StringView x, StringView y) const { 61 | size_t n = std::min(x.len, y.len); 62 | int cmp = std::memcmp(x.ptr, y.ptr, n); 63 | if (cmp) return cmp; 64 | return static_cast(x.len) - static_cast(y.len); 65 | } 66 | 67 | int operator()(StringView x, const char* y) const { 68 | for (const char *p = x.ptr, *e = p + x.len; p != e; ++p, ++y) { 69 | if (int cmp = *p - *y) return cmp; 70 | } 71 | return 0 - *y; 72 | } 73 | 74 | int operator()(char x, char y) const { return x - y; } 75 | int operator()(const char* x, const char* y) const { return std::strcmp(x, y); } 76 | int operator()(const char* x, StringView y) const { return -operator()(y, x); } 77 | }; 78 | 79 | template <> 80 | struct StrCmp<-1> { 81 | explicit StrCmp(bool case_sensitive) : case_sensitive(case_sensitive) {} 82 | 83 | template 84 | int operator()(const X& x, const Y& y) const { 85 | return case_sensitive ? StrCmp<1>()(x, y) : StrCmp<0>()(x, y); 86 | } 87 | 88 | bool case_sensitive; 89 | }; 90 | 91 | template 92 | struct StrLt : private StrCmp { 93 | using StrCmp::StrCmp; 94 | 95 | template 96 | bool operator()(const X& x, const Y& y) const { 97 | return StrCmp::operator()(x, y) < 0; 98 | } 99 | }; 100 | 101 | template 102 | struct StrEq : private StrCmp { 103 | using StrCmp::StrCmp; 104 | 105 | template 106 | bool operator()(const X& x, const Y& y) const { 107 | return StrCmp::operator()(x, y) == 0; 108 | } 109 | 110 | bool operator()(const StringView& x, const StringView& y) const { 111 | return x.len == y.len && StrCmp::operator()(x, y) == 0; 112 | } 113 | }; 114 | 115 | template 116 | struct Str { 117 | static_assert(kCaseSensitive == 0 || kCaseSensitive == 1, ""); 118 | 119 | static const bool case_sensitive = kCaseSensitive; 120 | 121 | StrCmp Cmp; 122 | StrLt Lt; 123 | StrEq Eq; 124 | }; 125 | 126 | template 127 | const bool Str::case_sensitive; 128 | 129 | template <> 130 | struct Str<-1> { 131 | explicit Str(bool case_sensitive) 132 | : case_sensitive(case_sensitive), 133 | Cmp(case_sensitive), 134 | Lt(case_sensitive), 135 | Eq(case_sensitive) {} 136 | 137 | bool case_sensitive; 138 | 139 | StrCmp<-1> Cmp; 140 | StrLt<-1> Lt; 141 | StrEq<-1> Eq; 142 | }; 143 | 144 | template 145 | void StrSort(Iter begin, Iter end, bool case_sensitive) { 146 | case_sensitive ? std::sort(begin, end, StrLt()) : std::sort(begin, end, StrLt()); 147 | } 148 | 149 | } // namespace gitstatus 150 | 151 | #endif // ROMKATV_GITSTATUS_STRING_CMP_H_ 152 | -------------------------------------------------------------------------------- /gitstatus/src/check_dir_mtime.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include "check_dir_mtime.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "check.h" 34 | #include "dir.h" 35 | #include "logging.h" 36 | #include "print.h" 37 | #include "scope_guard.h" 38 | #include "stat.h" 39 | 40 | namespace gitstatus { 41 | 42 | namespace { 43 | 44 | constexpr char kDirPrefix[] = ".gitstatus."; 45 | 46 | void Touch(const char* path) { 47 | int fd = creat(path, 0444); 48 | VERIFY(fd >= 0) << Errno(); 49 | CHECK(!close(fd)) << Errno(); 50 | } 51 | 52 | bool StatChanged(const char* path, const struct stat& prev) { 53 | struct stat cur; 54 | VERIFY(!lstat(path, &cur)) << Errno(); 55 | return !StatEq(prev, cur); 56 | } 57 | 58 | void RemoveStaleDirs(const char* root_dir) { 59 | int dir_fd = open(root_dir, O_DIRECTORY | O_CLOEXEC); 60 | if (dir_fd < 0) return; 61 | ON_SCOPE_EXIT(&) { CHECK(!close(dir_fd)) << Errno(); }; 62 | 63 | Arena arena; 64 | std::vector entries; 65 | const std::time_t now = std::time(nullptr); 66 | if (!ListDir(dir_fd, arena, entries, 67 | /* precompose_unicode = */ false, 68 | /* case_sensitive = */ true)) { 69 | return; 70 | } 71 | 72 | std::string path = root_dir; 73 | const size_t root_dir_len = path.size(); 74 | 75 | for (const char* entry : entries) { 76 | if (std::strlen(entry) < std::strlen(kDirPrefix)) continue; 77 | if (std::memcmp(entry, kDirPrefix, std::strlen(kDirPrefix))) continue; 78 | 79 | struct stat st; 80 | if (fstatat(dir_fd, entry, &st, AT_SYMLINK_NOFOLLOW)) { 81 | LOG(WARN) << "Cannot stat " << Print(entry) << " in " << Print(root_dir) << ": " << Errno(); 82 | continue; 83 | } 84 | if (MTim(st).tv_sec + 10 > now) continue; 85 | 86 | path.resize(root_dir_len); 87 | path += entry; 88 | size_t dir_len = path.size(); 89 | 90 | path += "/b/1"; 91 | if (unlink(path.c_str()) && errno != ENOENT) { 92 | LOG(WARN) << "Cannot unlink " << Print(path) << ": " << Errno(); 93 | continue; 94 | } 95 | 96 | for (const char* d : {"/a/1", "/a", "/b", ""}) { 97 | path.resize(dir_len); 98 | path += d; 99 | if (rmdir(path.c_str()) && errno != ENOENT) { 100 | LOG(WARN) << "Cannot remove " << Print(path) << ": " << Errno(); 101 | break; 102 | } 103 | } 104 | } 105 | } 106 | 107 | } // namespace 108 | 109 | bool CheckDirMtime(const char* root_dir) { 110 | try { 111 | RemoveStaleDirs(root_dir); 112 | 113 | std::string tmp = std::string() + root_dir + kDirPrefix + "XXXXXX"; 114 | VERIFY(mkdtemp(&tmp[0])) << Errno(); 115 | ON_SCOPE_EXIT(&) { rmdir(tmp.c_str()); }; 116 | 117 | std::string a_dir = tmp + "/a"; 118 | VERIFY(!mkdir(a_dir.c_str(), 0755)) << Errno(); 119 | ON_SCOPE_EXIT(&) { rmdir(a_dir.c_str()); }; 120 | struct stat a_st; 121 | VERIFY(!lstat(a_dir.c_str(), &a_st)) << Errno(); 122 | 123 | std::string b_dir = tmp + "/b"; 124 | VERIFY(!mkdir(b_dir.c_str(), 0755)) << Errno(); 125 | ON_SCOPE_EXIT(&) { rmdir(b_dir.c_str()); }; 126 | struct stat b_st; 127 | VERIFY(!lstat(b_dir.c_str(), &b_st)) << Errno(); 128 | 129 | while (sleep(1)) { 130 | // zzzz 131 | } 132 | 133 | std::string a1 = a_dir + "/1"; 134 | VERIFY(!mkdir(a1.c_str(), 0755)) << Errno(); 135 | ON_SCOPE_EXIT(&) { rmdir(a1.c_str()); }; 136 | if (!StatChanged(a_dir.c_str(), a_st)) { 137 | LOG(WARN) << "Creating a directory doesn't change mtime of the parent: " << Print(root_dir); 138 | return false; 139 | } 140 | 141 | std::string b1 = b_dir + "/1"; 142 | Touch(b1.c_str()); 143 | ON_SCOPE_EXIT(&) { unlink(b1.c_str()); }; 144 | if (!StatChanged(b_dir.c_str(), b_st)) { 145 | LOG(WARN) << "Creating a file doesn't change mtime of the parent: " << Print(root_dir); 146 | return false; 147 | } 148 | 149 | LOG(INFO) << "All mtime checks have passes. Enabling untracked cache: " << Print(root_dir); 150 | return true; 151 | } catch (const Exception&) { 152 | LOG(WARN) << "Error while testing for mtime capability: " << Print(root_dir); 153 | return false; 154 | } 155 | } 156 | 157 | } // namespace gitstatus 158 | -------------------------------------------------------------------------------- /gitstatus/gitstatus.prompt.sh: -------------------------------------------------------------------------------- 1 | # Simple Bash prompt with Git status. 2 | 3 | # Source gitstatus.plugin.sh from $GITSTATUS_DIR or from the same directory 4 | # in which the current script resides if the variable isn't set. 5 | if [[ -n "${GITSTATUS_DIR-}" ]]; then 6 | source "$GITSTATUS_DIR" || return 7 | elif [[ "${BASH_SOURCE[0]}" == */* ]]; then 8 | source "${BASH_SOURCE[0]%/*}/gitstatus.plugin.sh" || return 9 | else 10 | source gitstatus.plugin.sh || return 11 | fi 12 | 13 | # Sets GITSTATUS_PROMPT to reflect the state of the current git repository. 14 | # The value is empty if not in a git repository. Forwards all arguments to 15 | # gitstatus_query. 16 | # 17 | # Example value of GITSTATUS_PROMPT: master ⇣42⇡42 ⇠42⇢42 *42 merge ~42 +42 !42 ?42 18 | # 19 | # master current branch 20 | # ⇣42 local branch is 42 commits behind the remote 21 | # ⇡42 local branch is 42 commits ahead of the remote 22 | # ⇠42 local branch is 42 commits behind the push remote 23 | # ⇢42 local branch is 42 commits ahead of the push remote 24 | # *42 42 stashes 25 | # merge merge in progress 26 | # ~42 42 merge conflicts 27 | # +42 42 staged changes 28 | # !42 42 unstaged changes 29 | # ?42 42 untracked files 30 | function gitstatus_prompt_update() { 31 | GITSTATUS_PROMPT="" 32 | 33 | gitstatus_query "$@" || return 1 # error 34 | [[ "$VCS_STATUS_RESULT" == ok-sync ]] || return 0 # not a git repo 35 | 36 | local reset=$'\001\e[0m\002' # no color 37 | local clean=$'\001\e[38;5;076m\002' # green foreground 38 | local untracked=$'\001\e[38;5;014m\002' # teal foreground 39 | local modified=$'\001\e[38;5;011m\002' # yellow foreground 40 | local conflicted=$'\001\e[38;5;196m\002' # red foreground 41 | 42 | local p 43 | 44 | local where # branch name, tag or commit 45 | if [[ -n "$VCS_STATUS_LOCAL_BRANCH" ]]; then 46 | where="$VCS_STATUS_LOCAL_BRANCH" 47 | elif [[ -n "$VCS_STATUS_TAG" ]]; then 48 | p+="${reset}#" 49 | where="$VCS_STATUS_TAG" 50 | else 51 | p+="${reset}@" 52 | where="${VCS_STATUS_COMMIT:0:8}" 53 | fi 54 | 55 | (( ${#where} > 32 )) && where="${where:0:12}…${where: -12}" # truncate long branch names and tags 56 | p+="${clean}${where}" 57 | 58 | # ⇣42 if behind the remote. 59 | (( VCS_STATUS_COMMITS_BEHIND )) && p+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}" 60 | # ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42. 61 | (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && p+=" " 62 | (( VCS_STATUS_COMMITS_AHEAD )) && p+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}" 63 | # ⇠42 if behind the push remote. 64 | (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}" 65 | (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" " 66 | # ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42. 67 | (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && p+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}" 68 | # *42 if have stashes. 69 | (( VCS_STATUS_STASHES )) && p+=" ${clean}*${VCS_STATUS_STASHES}" 70 | # 'merge' if the repo is in an unusual state. 71 | [[ -n "$VCS_STATUS_ACTION" ]] && p+=" ${conflicted}${VCS_STATUS_ACTION}" 72 | # ~42 if have merge conflicts. 73 | (( VCS_STATUS_NUM_CONFLICTED )) && p+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}" 74 | # +42 if have staged changes. 75 | (( VCS_STATUS_NUM_STAGED )) && p+=" ${modified}+${VCS_STATUS_NUM_STAGED}" 76 | # !42 if have unstaged changes. 77 | (( VCS_STATUS_NUM_UNSTAGED )) && p+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}" 78 | # ?42 if have untracked files. It's really a question mark, your font isn't broken. 79 | (( VCS_STATUS_NUM_UNTRACKED )) && p+=" ${untracked}?${VCS_STATUS_NUM_UNTRACKED}" 80 | 81 | GITSTATUS_PROMPT="${p}${reset}" 82 | } 83 | 84 | # Start gitstatusd in the background. 85 | gitstatus_stop && gitstatus_start -s -1 -u -1 -c -1 -d -1 86 | 87 | # On every prompt, fetch git status and set GITSTATUS_PROMPT. 88 | if [[ -z "${PROMPT_COMMAND[*]}" ]]; then 89 | PROMPT_COMMAND=gitstatus_prompt_update 90 | elif [[ ! "${PROMPT_COMMAND[*]}" =~ [[:space:]\;]?gitstatus_prompt_update[[:space:]\;]? ]]; then 91 | # Note: If PROMPT_COMMAND is an array, this will modify its first element. 92 | PROMPT_COMMAND=$'gitstatus_prompt_update\n'"$PROMPT_COMMAND" 93 | fi 94 | 95 | # Retain 3 trailing components of the current directory. 96 | PROMPT_DIRTRIM=3 97 | 98 | # Enable promptvars so that ${GITSTATUS_PROMPT} in PS1 is expanded. 99 | shopt -s promptvars 100 | 101 | # Customize prompt. Put $GITSTATUS_PROMPT in it reflect git status. 102 | # 103 | # Example: 104 | # 105 | # user@host ~/projects/skynet master ⇡42 106 | # $ █ 107 | PS1='\[\033[01;32m\]\u@\h\[\033[00m\] ' # green user@host 108 | PS1+='\[\033[01;34m\]\w\[\033[00m\]' # blue current working directory 109 | PS1+='${GITSTATUS_PROMPT:+ $GITSTATUS_PROMPT}' # git status (requires promptvars option) 110 | PS1+='\n\[\033[01;$((31+!$?))m\]\$\[\033[00m\] ' # green/red (success/error) $/# (normal/root) 111 | PS1+='\[\e]0;\u@\h: \w\a\]' # terminal title: user@host: dir 112 | -------------------------------------------------------------------------------- /gitstatus/gitstatus.prompt.zsh: -------------------------------------------------------------------------------- 1 | # Simple Zsh prompt with Git status. 2 | 3 | # Source gitstatus.plugin.zsh from $GITSTATUS_DIR or from the same directory 4 | # in which the current script resides if the variable isn't set. 5 | source "${GITSTATUS_DIR:-${${(%):-%x}:h}}/gitstatus.plugin.zsh" || return 6 | 7 | # Sets GITSTATUS_PROMPT to reflect the state of the current git repository. Empty if not 8 | # in a git repository. In addition, sets GITSTATUS_PROMPT_LEN to the number of columns 9 | # $GITSTATUS_PROMPT will occupy when printed. 10 | # 11 | # Example: 12 | # 13 | # GITSTATUS_PROMPT='master ⇣42⇡42 ⇠42⇢42 *42 merge ~42 +42 !42 ?42' 14 | # GITSTATUS_PROMPT_LEN=39 15 | # 16 | # master current branch 17 | # ⇣42 local branch is 42 commits behind the remote 18 | # ⇡42 local branch is 42 commits ahead of the remote 19 | # ⇠42 local branch is 42 commits behind the push remote 20 | # ⇢42 local branch is 42 commits ahead of the push remote 21 | # *42 42 stashes 22 | # merge merge in progress 23 | # ~42 42 merge conflicts 24 | # +42 42 staged changes 25 | # !42 42 unstaged changes 26 | # ?42 42 untracked files 27 | function gitstatus_prompt_update() { 28 | emulate -L zsh 29 | typeset -g GITSTATUS_PROMPT='' 30 | typeset -gi GITSTATUS_PROMPT_LEN=0 31 | 32 | # Call gitstatus_query synchronously. Note that gitstatus_query can also be called 33 | # asynchronously; see documentation in gitstatus.plugin.zsh. 34 | gitstatus_query 'MY' || return 1 # error 35 | [[ $VCS_STATUS_RESULT == 'ok-sync' ]] || return 0 # not a git repo 36 | 37 | local clean='%76F' # green foreground 38 | local modified='%178F' # yellow foreground 39 | local untracked='%39F' # blue foreground 40 | local conflicted='%196F' # red foreground 41 | 42 | local p 43 | 44 | local where # branch name, tag or commit 45 | if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then 46 | where=$VCS_STATUS_LOCAL_BRANCH 47 | elif [[ -n $VCS_STATUS_TAG ]]; then 48 | p+='%f#' 49 | where=$VCS_STATUS_TAG 50 | else 51 | p+='%f@' 52 | where=${VCS_STATUS_COMMIT[1,8]} 53 | fi 54 | 55 | (( $#where > 32 )) && where[13,-13]="…" # truncate long branch names and tags 56 | p+="${clean}${where//\%/%%}" # escape % 57 | 58 | # ⇣42 if behind the remote. 59 | (( VCS_STATUS_COMMITS_BEHIND )) && p+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}" 60 | # ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42. 61 | (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && p+=" " 62 | (( VCS_STATUS_COMMITS_AHEAD )) && p+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}" 63 | # ⇠42 if behind the push remote. 64 | (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}" 65 | (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" " 66 | # ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42. 67 | (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && p+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}" 68 | # *42 if have stashes. 69 | (( VCS_STATUS_STASHES )) && p+=" ${clean}*${VCS_STATUS_STASHES}" 70 | # 'merge' if the repo is in an unusual state. 71 | [[ -n $VCS_STATUS_ACTION ]] && p+=" ${conflicted}${VCS_STATUS_ACTION}" 72 | # ~42 if have merge conflicts. 73 | (( VCS_STATUS_NUM_CONFLICTED )) && p+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}" 74 | # +42 if have staged changes. 75 | (( VCS_STATUS_NUM_STAGED )) && p+=" ${modified}+${VCS_STATUS_NUM_STAGED}" 76 | # !42 if have unstaged changes. 77 | (( VCS_STATUS_NUM_UNSTAGED )) && p+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}" 78 | # ?42 if have untracked files. It's really a question mark, your font isn't broken. 79 | (( VCS_STATUS_NUM_UNTRACKED )) && p+=" ${untracked}?${VCS_STATUS_NUM_UNTRACKED}" 80 | 81 | GITSTATUS_PROMPT="${p}%f" 82 | 83 | # The length of GITSTATUS_PROMPT after removing %f and %F. 84 | GITSTATUS_PROMPT_LEN="${(m)#${${GITSTATUS_PROMPT//\%\%/x}//\%(f|<->F)}}" 85 | } 86 | 87 | # Start gitstatusd instance with name "MY". The same name is passed to 88 | # gitstatus_query in gitstatus_prompt_update. The flags with -1 as values 89 | # enable staged, unstaged, conflicted and untracked counters. 90 | gitstatus_stop 'MY' && gitstatus_start -s -1 -u -1 -c -1 -d -1 'MY' 91 | 92 | # On every prompt, fetch git status and set GITSTATUS_PROMPT. 93 | autoload -Uz add-zsh-hook 94 | add-zsh-hook precmd gitstatus_prompt_update 95 | 96 | # Enable/disable the right prompt options. 97 | setopt no_prompt_bang prompt_percent prompt_subst 98 | 99 | # Customize prompt. Put $GITSTATUS_PROMPT in it to reflect git status. 100 | # 101 | # Example: 102 | # 103 | # user@host ~/projects/skynet master ⇡42 104 | # % █ 105 | # 106 | # The current directory gets truncated from the left if the whole prompt doesn't fit on the line. 107 | PROMPT='%70F%n@%m%f ' # green user@host 108 | PROMPT+='%39F%$((-GITSTATUS_PROMPT_LEN-1))<…<%~%<<%f' # blue current working directory 109 | PROMPT+='${GITSTATUS_PROMPT:+ $GITSTATUS_PROMPT}' # git status 110 | PROMPT+=$'\n' # new line 111 | PROMPT+='%F{%(?.76.196)}%#%f ' # %/# (normal/root); green/red (ok/error) 112 | -------------------------------------------------------------------------------- /config/p10k-robbyrussell.zsh: -------------------------------------------------------------------------------- 1 | # Config file for Powerlevel10k with the style of robbyrussell theme from Oh My Zsh. 2 | # 3 | # Original: https://github.com/ohmyzsh/ohmyzsh/wiki/Themes#robbyrussell. 4 | # 5 | # Replication of robbyrussell theme is exact. The only observable difference is in 6 | # performance. Powerlevel10k prompt is very fast everywhere, even in large Git repositories. 7 | # 8 | # Usage: Source this file either before or after loading Powerlevel10k. 9 | # 10 | # source ~/powerlevel10k/config/p10k-robbyrussell.zsh 11 | # source ~/powerlevel10k/powerlevel10k.zsh-theme 12 | 13 | # Temporarily change options. 14 | 'builtin' 'local' '-a' 'p10k_config_opts' 15 | [[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases') 16 | [[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob') 17 | [[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand') 18 | 'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand' 19 | 20 | () { 21 | emulate -L zsh -o extended_glob 22 | 23 | # Unset all configuration options. 24 | unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR' 25 | 26 | # Zsh >= 5.1 is required. 27 | [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return 28 | 29 | # Left prompt segments. 30 | typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(prompt_char dir vcs) 31 | # Right prompt segments. 32 | typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=() 33 | 34 | # Basic style options that define the overall prompt look. 35 | typeset -g POWERLEVEL9K_BACKGROUND= # transparent background 36 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace 37 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space 38 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol 39 | typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION= # no segment icons 40 | 41 | # Green prompt symbol if the last command succeeded. 42 | typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS}_FOREGROUND=green 43 | # Red prompt symbol if the last command failed. 44 | typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS}_FOREGROUND=red 45 | # Prompt symbol: bold arrow. 46 | typeset -g POWERLEVEL9K_PROMPT_CHAR_CONTENT_EXPANSION='%B➜ ' 47 | 48 | # Cyan current directory. 49 | typeset -g POWERLEVEL9K_DIR_FOREGROUND=cyan 50 | # Show only the last segment of the current directory. 51 | typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_last 52 | # Bold directory. 53 | typeset -g POWERLEVEL9K_DIR_CONTENT_EXPANSION='%B$P9K_CONTENT' 54 | 55 | # Git status formatter. 56 | function my_git_formatter() { 57 | emulate -L zsh 58 | if [[ -n $P9K_CONTENT ]]; then 59 | # If P9K_CONTENT is not empty, it's either "loading" or from vcs_info (not from 60 | # gitstatus plugin). VCS_STATUS_* parameters are not available in this case. 61 | typeset -g my_git_format=$P9K_CONTENT 62 | else 63 | # Use VCS_STATUS_* parameters to assemble Git status. See reference: 64 | # https://github.com/romkatv/gitstatus/blob/master/gitstatus.plugin.zsh. 65 | typeset -g my_git_format="${1+%B%4F}git:(${1+%1F}" 66 | my_git_format+=${${VCS_STATUS_LOCAL_BRANCH:-${VCS_STATUS_COMMIT[1,8]}}//\%/%%} 67 | my_git_format+="${1+%4F})" 68 | if (( VCS_STATUS_NUM_CONFLICTED || VCS_STATUS_NUM_STAGED || 69 | VCS_STATUS_NUM_UNSTAGED || VCS_STATUS_NUM_UNTRACKED )); then 70 | my_git_format+=" ${1+%3F}✗" 71 | fi 72 | fi 73 | } 74 | functions -M my_git_formatter 2>/dev/null 75 | 76 | # Disable the default Git status formatting. 77 | typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true 78 | # Install our own Git status formatter. 79 | typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}' 80 | typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter()))+${my_git_format}}' 81 | # Grey Git status when loading. 82 | typeset -g POWERLEVEL9K_VCS_LOADING_FOREGROUND=246 83 | 84 | # Instant prompt mode. 85 | # 86 | # - off: Disable instant prompt. Choose this if you've tried instant prompt and found 87 | # it incompatible with your zsh configuration files. 88 | # - quiet: Enable instant prompt and don't print warnings when detecting console output 89 | # during zsh initialization. Choose this if you've read and understood 90 | # https://github.com/romkatv/powerlevel10k#instant-prompt. 91 | # - verbose: Enable instant prompt and print a warning when detecting console output during 92 | # zsh initialization. Choose this if you've never tried instant prompt, haven't 93 | # seen the warning, or if you are unsure what this all means. 94 | typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose 95 | 96 | # Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized. 97 | # For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload 98 | # can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you 99 | # really need it. 100 | typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true 101 | 102 | # If p10k is already loaded, reload configuration. 103 | # This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true. 104 | (( ! $+functions[p10k] )) || p10k reload 105 | } 106 | 107 | # Tell `p10k configure` which file it should overwrite. 108 | typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a} 109 | 110 | (( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]} 111 | 'builtin' 'unset' 'p10k_config_opts' 112 | -------------------------------------------------------------------------------- /gitstatus/src/repo_cache.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include "repo_cache.h" 19 | 20 | #include 21 | 22 | #include "check.h" 23 | #include "git.h" 24 | #include "print.h" 25 | #include "scope_guard.h" 26 | #include "string_view.h" 27 | 28 | namespace gitstatus { 29 | 30 | namespace { 31 | 32 | void GitDirs(const char* dir, bool from_dotgit, std::string& gitdir, std::string& workdir) { 33 | git_buf gitdir_buf = {}; 34 | git_buf workdir_buf = {}; 35 | ON_SCOPE_EXIT(&) { 36 | git_buf_free(&gitdir_buf); 37 | git_buf_free(&workdir_buf); 38 | }; 39 | int flags = from_dotgit ? GIT_REPOSITORY_OPEN_NO_SEARCH | GIT_REPOSITORY_OPEN_NO_DOTGIT : 0; 40 | switch (git_repository_discover_ex(&gitdir_buf, &workdir_buf, NULL, NULL, dir, flags, nullptr)) { 41 | case 0: 42 | gitdir.assign(gitdir_buf.ptr, gitdir_buf.size); 43 | workdir.assign(workdir_buf.ptr, workdir_buf.size); 44 | VERIFY(!gitdir.empty() && gitdir.front() == '/' && gitdir.back() == '/'); 45 | VERIFY(!workdir.empty() && workdir.front() == '/' && workdir.back() == '/'); 46 | break; 47 | case GIT_ENOTFOUND: 48 | gitdir.clear(); 49 | workdir.clear(); 50 | break; 51 | default: 52 | LOG(ERROR) << "git_repository_open_ext: " << Print(dir) << ": " << GitError(); 53 | throw Exception(); 54 | } 55 | } 56 | 57 | git_repository* OpenRepo(const std::string& dir, bool from_dotgit) { 58 | git_repository* repo = nullptr; 59 | int flags = from_dotgit ? GIT_REPOSITORY_OPEN_NO_SEARCH | GIT_REPOSITORY_OPEN_NO_DOTGIT : 0; 60 | switch (git_repository_open_ext(&repo, dir.c_str(), flags, nullptr)) { 61 | case 0: 62 | return repo; 63 | case GIT_ENOTFOUND: 64 | return nullptr; 65 | default: 66 | LOG(ERROR) << "git_repository_open_ext: " << Print(dir) << ": " << GitError(); 67 | throw Exception(); 68 | } 69 | } 70 | 71 | std::string DirName(std::string path) { 72 | if (path.empty()) return ""; 73 | while (path.back() == '/') { 74 | path.pop_back(); 75 | if (path.empty()) return ""; 76 | } 77 | do { 78 | path.pop_back(); 79 | if (path.empty()) return ""; 80 | } while (path.back() != '/'); 81 | return path; 82 | } 83 | 84 | } // namespace 85 | 86 | Repo* RepoCache::Open(const std::string& dir, bool from_dotgit) { 87 | if (dir.empty() || dir.front() != '/') return nullptr; 88 | 89 | std::string gitdir, workdir; 90 | GitDirs(dir.c_str(), from_dotgit, gitdir, workdir); 91 | if (gitdir.empty()) { 92 | // This isn't quite correct because of differences in canonicalization, .git files and GIT_DIR. 93 | // A proper solution would require tracking the "discovery dir" for every repository and 94 | // performing path canonicalization. 95 | if (from_dotgit) { 96 | Erase(cache_.find(dir.back() == '/' ? dir : dir + '/')); 97 | } else { 98 | std::string path = dir; 99 | if (path.back() != '/') path += '/'; 100 | do { 101 | Erase(cache_.find(path + ".git/")); 102 | path = DirName(path); 103 | } while (!path.empty()); 104 | } 105 | return nullptr; 106 | } 107 | 108 | auto it = cache_.find(gitdir); 109 | if (it != cache_.end()) { 110 | lru_.erase(it->second->lru); 111 | it->second->lru = lru_.insert({Clock::now(), it}); 112 | return it->second.get(); 113 | } 114 | 115 | // Opening from gitdir is faster but we cannot use it when gitdir came from a .git file. 116 | git_repository* repo = 117 | DirName(gitdir) == workdir ? OpenRepo(gitdir, true) : OpenRepo(dir, from_dotgit); 118 | if (!repo) return nullptr; 119 | ON_SCOPE_EXIT(&) { 120 | if (repo) git_repository_free(repo); 121 | }; 122 | if (git_repository_is_bare(repo)) return nullptr; 123 | workdir = git_repository_workdir(repo) ?: ""; 124 | if (workdir.empty()) return nullptr; 125 | VERIFY(workdir.front() == '/' && workdir.back() == '/') << Print(workdir); 126 | 127 | auto x = cache_.emplace(gitdir, nullptr); 128 | std::unique_ptr& elem = x.first->second; 129 | if (elem) { 130 | lru_.erase(elem->lru); 131 | } else { 132 | LOG(INFO) << "Initializing new repository: " << Print(gitdir); 133 | 134 | // Libgit2 initializes odb and refdb lazily with double-locking. To avoid useless work 135 | // when multiple threads attempt to initialize the same db at the same time, we trigger 136 | // initialization manually before threads are in play. 137 | git_odb* odb; 138 | VERIFY(!git_repository_odb(&odb, repo)) << GitError(); 139 | git_odb_free(odb); 140 | 141 | git_refdb* refdb; 142 | VERIFY(!git_repository_refdb(&refdb, repo)) << GitError(); 143 | git_refdb_free(refdb); 144 | 145 | elem = std::make_unique(std::exchange(repo, nullptr), lim_); 146 | } 147 | elem->lru = lru_.insert({Clock::now(), x.first}); 148 | return elem.get(); 149 | } 150 | 151 | void RepoCache::Free(Time cutoff) { 152 | while (true) { 153 | if (lru_.empty()) break; 154 | auto it = lru_.begin(); 155 | if (it->first > cutoff) break; 156 | Erase(it->second); 157 | } 158 | } 159 | 160 | void RepoCache::Erase(Cache::iterator it) { 161 | if (it == cache_.end()) return; 162 | LOG(INFO) << "Closing repository: " << Print(it->first); 163 | lru_.erase(it->second->lru); 164 | cache_.erase(it); 165 | } 166 | 167 | } // namespace gitstatus 168 | -------------------------------------------------------------------------------- /internal/notes.md: -------------------------------------------------------------------------------- 1 | battery: use the same technique as in vpn_ip to avoid reset=2. 2 | 3 | --- 4 | 5 | implement fake gitstatus api on top of vcs_info (or plain git?) + worker and use it if there is no 6 | gitstatus. 7 | 8 | --- 9 | 10 | - call vcs_info on worker. the tricky question is what to display while "loading". 11 | 12 | --- 13 | 14 | - add _SHOW_SYSTEM to all *env segments. 15 | 16 | --- 17 | 18 | - support states in SHOW_ON_COMMAND: POWERLEVEL9K_SEGMENT_STATE_SHOW_ON_COMMAND='...' 19 | 20 | --- 21 | 22 | add POWERLEVEL9K_${SEGMENT}_${STATE}_SHOW_IN_DIR='pwd_pattern'; implement the same way as 23 | SHOW_ON_UPGLOB. how should it interact with POWERLEVEL9K_${SEGMENT}_DISABLED_DIR_PATTERN? 24 | 25 | --- 26 | 27 | add `p10k upglob`; returns 0 on match and sets REPLY to the directory where match was found. 28 | 29 | --- 30 | 31 | when directory cannot be shortened any further, start chopping off segments from the left and 32 | replacing the chopped off part with `…`. e.g., `…/x/anchor/y/anchor`. the shortest dir 33 | representation is thus `…/last` or `…/last` depending on whether the last segment is an anchor. 34 | the replacement parameter's value is `…/` (with a slash) to allow for `x/anchor/y/anchor`. 35 | 36 | --- 37 | 38 | - add to faq: how do i display an environment variable in prompt? link it from "extensible" 39 | 40 | --- 41 | 42 | - add to faq: how do i display an icon in prompt? link it from "extensible" 43 | 44 | --- 45 | 46 | - add root_indicator to config templates 47 | 48 | --- 49 | 50 | - test chruby and add it to config templates 51 | 52 | --- 53 | 54 | - add ssh to config templates 55 | 56 | --- 57 | 58 | - add swift version to config templates; see if there is a good pattern for PROJECT_ONLY 59 | 60 | --- 61 | 62 | - add swiftenv 63 | 64 | --- 65 | 66 | - add faq: how to customize directory shortening? mention POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER, 67 | POWERLEVEL9K_DIR_MAX_LENGTH and co., and truncate_to_last. 68 | 69 | --- 70 | 71 | fix a bug in zsh: https://github.com/romkatv/powerlevel10k/issues/502. to reproduce: 72 | 73 | ```zsh 74 | emulate zsh -o prompt_percent -c 'print -P "%F{#ff0000}red%F{green}%B bold green"' 75 | ``` 76 | 77 | --- 78 | 79 | add `p10k explain` that prints something like this: 80 | 81 | ```text 82 | segment icons meaning 83 | 84 | --- 85 | 86 | --- 87 | 88 | --- 89 | 90 | --- 91 | 92 | --- 93 | 94 | --- 95 | 96 | --- 97 | 98 | --- 99 | -- 100 | status ✔ ✘ exit code of the last command 101 | ``` 102 | 103 | implement it the hard way: for every enabled segment go over all its {state,icon} pairs, resolve 104 | the icon (if not absolute), apply VISUAL_IDENTIFIER_EXPANSION, remove leading and trailing 105 | whitespace and print without formatting (sort of like `print -P | cat`); print segment names in 106 | green and icons in bold; battery can have an unlimited number of icons, so `...` would be needed 107 | (based on total length of concatenated icons rather than the number of icons); user-defined 108 | segments would have "unknown" icons by default (yellow and not bold); can allow them to 109 | participate by defining `explainprompt_foo` that populates array `reply` with strings like this: 110 | '-s STATE -i LOCK_ICON +r'; the first element must be segment description. 111 | 112 | --- 113 | 114 | add `docker_context` prompt segment; similar to `kubecontext`; the data should come from 115 | `currentContext` field in `~/.docker/config.json` (according to 116 | https://github.com/starship/starship/issues/995); there is also `DOCKER_CONTEXT`; more info: 117 | https://docs.docker.com/engine/reference/commandline/context_use; also 118 | https://github.com/starship/starship/pull/996. 119 | 120 | --- 121 | 122 | support `env`, `ionice` and `strace` precommands in `parser.zsh`. 123 | 124 | --- 125 | 126 | Add ruler to configuration wizard. Options: `─`, `·`, `╌`, `┄`, `▁`, `═`. 127 | 128 | --- 129 | 130 | Add frame styles to the wizard. 131 | 132 | ```text 133 | ╭─ 134 | ╰─ 135 | 136 | ┌─ 137 | └─ 138 | 139 | ┏━ 140 | ┗━ 141 | 142 | ╔═ 143 | ╚═ 144 | 145 | ▛▀ 146 | ▙▄ 147 | ``` 148 | 149 | Prompt connection should have matching options. 150 | 151 | --- 152 | 153 | Add `POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_MIRROR_SEPARATOR`. If set, left segments get separated with 154 | `POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR` followed by `POWERLEVEL9K_LEFT_SEGMENT_MIRROR_SEPARATOR`. 155 | Each is drawn without background. The first with the foreground of left segment, the second with 156 | the background of right segment. To insert space in between, embed it in 157 | `POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_MIRROR_SEPARATOR`. 158 | `POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR` is unused. 159 | 160 | --- 161 | 162 | Add *Segment Connection* screen to configuration wizard with options *Fused*, *Touching* and 163 | *Disjoint*. The last two differ by the absence/presence of space between `SEGMENT_SEPARATOR` and 164 | `SEGMENT_MIRROR_SEPARATOR`. 165 | 166 | *Fused* requires line separator (there is already a screen for it) but the other two options require 167 | two filled separators similar to heads and tail. Figure out how to present this choice. 168 | 169 | --- 170 | 171 | Optimize auto-wizard check. 172 | 173 | ```text 174 | time ( repeat 1000 [[ -z "${parameters[(I)POWERLEVEL9K_*~(POWERLEVEL9K_MODE|POWERLEVEL9K_CONFIG_FILE)]}" ]] ) 175 | user=0.21s system=0.05s cpu=99% total=0.264 176 | 177 | time ( repeat 1000 [[ -z "${parameters[(I)POWERLEVEL9K_*]}" ]] ) 178 | user=0.17s system=0.00s cpu=99% total=0.175 179 | ``` 180 | 181 | --- 182 | 183 | Add the equivalent of `P9K_PYTHON_VERSION` to all `*env` segments where it makes sense. 184 | 185 | --- 186 | 187 | Define `P9K_ICON` on initialization. Fill it with `$icon`. Duplicate every key that ends in `_ICON`. 188 | Respect `POWERLEVEL9K_VCS_STASH_ICON` overrides but not anything with segment name or state. 189 | 190 | Define `POWERLEVEL9K_VCS_*` parameters in config templates for all symbols used in 191 | `my_git_formatter`. Add missing entries to `icons`. Use `$P9K_ICON[...]` within `my_git_formatter`. 192 | Add a screen to the wizard to choose between clear and circled icons. 193 | 194 | --- 195 | 196 | Add a screen to the wizard asking whether to set `POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'`. 197 | Show it only if there is `$HOME/.git`. By default this parameter should be commented out. 198 | -------------------------------------------------------------------------------- /gitstatus/src/dir.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include "dir.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifdef __linux__ 33 | #include 34 | #include 35 | #endif 36 | 37 | #ifdef __APPLE__ 38 | #include 39 | #endif 40 | 41 | #include "bits.h" 42 | #include "check.h" 43 | #include "scope_guard.h" 44 | #include "string_cmp.h" 45 | #include "tribool.h" 46 | 47 | namespace gitstatus { 48 | 49 | namespace { 50 | 51 | bool Dots(const char* name) { 52 | if (name[0] == '.') { 53 | if (name[1] == 0) return true; 54 | if (name[1] == '.' && name[2] == 0) return true; 55 | } 56 | return false; 57 | } 58 | 59 | } // namespace 60 | 61 | // The linux-specific implementation is about 20% faster than the generic (posix) implementation. 62 | #ifdef __linux__ 63 | 64 | uint64_t Read64(const void* p) { 65 | uint64_t res; 66 | std::memcpy(&res, p, 8); 67 | return res; 68 | } 69 | 70 | void Write64(uint64_t x, void* p) { std::memcpy(p, &x, 8); } 71 | 72 | void SwapBytes(char** begin, char** end) { 73 | #if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ 74 | for (; begin != end; ++begin) Write64(__builtin_bswap64(Read64(*begin)), *begin); 75 | #elif __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__ 76 | #error "sorry, not implemented" 77 | #endif 78 | } 79 | 80 | template 81 | void SortEntries(char** begin, char** end) { 82 | static_assert(kCaseSensitive, ""); 83 | SwapBytes(begin, end); 84 | std::sort(begin, end, [](const char* a, const char* b) { 85 | uint64_t x = Read64(a); 86 | uint64_t y = Read64(b); 87 | // Add 5 for good luck. 88 | return x < y || (x == y && std::memcmp(a + 5, b + 5, 256) < 0); 89 | }); 90 | SwapBytes(begin, end); 91 | } 92 | 93 | template <> 94 | void SortEntries(char** begin, char** end) { 95 | std::sort(begin, end, StrLt()); 96 | } 97 | 98 | bool ListDir(int dir_fd, Arena& arena, std::vector& entries, bool precompose_unicode, 99 | bool case_sensitive) { 100 | struct linux_dirent64 { 101 | ino64_t d_ino; 102 | off64_t d_off; 103 | unsigned short d_reclen; 104 | unsigned char d_type; 105 | char d_name[]; 106 | }; 107 | 108 | constexpr size_t kBufSize = 8 << 10; 109 | const size_t orig_size = entries.size(); 110 | 111 | while (true) { 112 | char* buf = static_cast(arena.Allocate(kBufSize, alignof(linux_dirent64))); 113 | // Save 256 bytes for the rainy day. 114 | int n = syscall(SYS_getdents64, dir_fd, buf, kBufSize - 256); 115 | if (n < 0) { 116 | entries.resize(orig_size); 117 | return false; 118 | } 119 | for (int pos = 0; pos < n;) { 120 | auto* ent = reinterpret_cast(buf + pos); 121 | if (!Dots(ent->d_name)) entries.push_back(ent->d_name); 122 | pos += ent->d_reclen; 123 | } 124 | if (n == 0) break; 125 | // The following optimization relies on SYS_getdents64 always returning as many 126 | // entries as would fit. This is not guaranteed by the specification and I don't 127 | // know if this is true in practice. The optimization has no measurable effect on 128 | // gitstatus performance, so it's turned off. 129 | // 130 | // if (n + sizeof(linux_dirent64) + 512 <= kBufSize) break; 131 | } 132 | 133 | if (case_sensitive) { 134 | SortEntries(entries.data() + orig_size, entries.data() + entries.size()); 135 | } else { 136 | SortEntries(entries.data() + orig_size, entries.data() + entries.size()); 137 | } 138 | 139 | return true; 140 | } 141 | 142 | #else // __linux__ 143 | 144 | namespace { 145 | 146 | char* DirentDup(Arena& arena, const struct dirent& ent, size_t len) { 147 | char* p = arena.Allocate(len + 2); 148 | *p++ = ent.d_type; 149 | std::memcpy(p, ent.d_name, len + 1); 150 | return p; 151 | } 152 | 153 | #ifdef __APPLE__ 154 | 155 | std::atomic g_iconv_error(true); 156 | 157 | Tribool IConvTry(char* inp, size_t ins, char* outp, size_t outs) { 158 | if (outs == 0) return Tribool::kUnknown; 159 | iconv_t ic = iconv_open("UTF-8", "UTF-8-MAC"); 160 | if (ic == (iconv_t)-1) { 161 | if (g_iconv_error.load(std::memory_order_relaxed) && 162 | g_iconv_error.exchange(false, std::memory_order_relaxed)) { 163 | LOG(ERROR) << "iconv_open(\"UTF-8\", \"UTF-8-MAC\") failed"; 164 | } 165 | return Tribool::kFalse; 166 | } 167 | ON_SCOPE_EXIT(&) { CHECK(iconv_close(ic) == 0) << Errno(); }; 168 | --outs; 169 | if (iconv(ic, &inp, &ins, &outp, &outs) >= 0) { 170 | *outp = 0; 171 | return Tribool::kTrue; 172 | } 173 | return errno == E2BIG ? Tribool::kUnknown : Tribool::kFalse; 174 | } 175 | 176 | char* DirenvConvert(Arena& arena, struct dirent& ent, bool do_convert) { 177 | if (!do_convert) return DirentDup(arena, ent, std::strlen(ent.d_name)); 178 | 179 | size_t len = 0; 180 | do_convert = false; 181 | for (unsigned char c; (c = ent.d_name[len]); ++len) { 182 | if (c & 0x80) do_convert = true; 183 | } 184 | if (!do_convert) return DirentDup(arena, ent, len); 185 | 186 | size_t n = NextPow2(len + 2); 187 | while (true) { 188 | char* p = arena.Allocate(n); 189 | switch (IConvTry(ent.d_name, len, p + 1, n - 1)) { 190 | case Tribool::kFalse: 191 | return DirentDup(arena, ent, len); 192 | case Tribool::kTrue: 193 | *p = ent.d_type; 194 | return p + 1; 195 | case Tribool::kUnknown: 196 | break; 197 | } 198 | n *= 2; 199 | } 200 | } 201 | 202 | #else // __APPLE__ 203 | 204 | char* DirenvConvert(Arena& arena, struct dirent& ent, bool do_convert) { 205 | return DirentDup(arena, ent, std::strlen(ent.d_name)); 206 | } 207 | 208 | #endif // __APPLE__ 209 | 210 | } // namespace 211 | 212 | bool ListDir(int dir_fd, Arena& arena, std::vector& entries, bool precompose_unicode, 213 | bool case_sensitive) { 214 | const size_t orig_size = entries.size(); 215 | dir_fd = dup(dir_fd); 216 | if (dir_fd < 0) return false; 217 | DIR* dir = fdopendir(dir_fd); 218 | if (!dir) { 219 | CHECK(!close(dir_fd)) << Errno(); 220 | return false; 221 | } 222 | ON_SCOPE_EXIT(&) { CHECK(!closedir(dir)) << Errno(); }; 223 | while (struct dirent* ent = (errno = 0, readdir(dir))) { 224 | if (Dots(ent->d_name)) continue; 225 | entries.push_back(DirenvConvert(arena, *ent, precompose_unicode)); 226 | } 227 | if (errno) { 228 | entries.resize(orig_size); 229 | return false; 230 | } 231 | StrSort(entries.data() + orig_size, entries.data() + entries.size(), case_sensitive); 232 | return true; 233 | } 234 | 235 | #endif // __linux__ 236 | 237 | } // namespace gitstatus 238 | -------------------------------------------------------------------------------- /internal/worker.zsh: -------------------------------------------------------------------------------- 1 | # invoked in worker: _p9k_worker_main 2 | function _p9k_worker_main() { 3 | mkfifo -- $_p9k__worker_file_prefix.fifo || return 4 | echo -nE - s$_p9k_worker_pgid$'\x1e' || return 5 | exec <$_p9k__worker_file_prefix.fifo || return 6 | zf_rm -- $_p9k__worker_file_prefix.fifo || return 7 | 8 | local -i reset 9 | local req fd 10 | local -a ready 11 | local _p9k_worker_request_id 12 | local -A _p9k_worker_fds # fd => id$'\x1f'callback 13 | local -A _p9k_worker_inflight # id => inflight count 14 | 15 | function _p9k_worker_reply() { 16 | print -nr -- e${(pj:\n:)@}$'\x1e' || kill -- -$_p9k_worker_pgid 17 | } 18 | 19 | # usage: _p9k_worker_async 20 | function _p9k_worker_async() { 21 | local fd async=$1 22 | sysopen -r -o cloexec -u fd <(() { eval $async; } && print -n '\x1e') || return 23 | (( ++_p9k_worker_inflight[$_p9k_worker_request_id] )) 24 | _p9k_worker_fds[$fd]=$_p9k_worker_request_id$'\x1f'$2 25 | } 26 | 27 | trap '' PIPE 28 | 29 | { 30 | while zselect -a ready 0 ${(k)_p9k_worker_fds}; do 31 | [[ $ready[1] == -r ]] || return 32 | for fd in ${ready:1}; do 33 | if [[ $fd == 0 ]]; then 34 | local buf= 35 | [[ -t 0 ]] # https://www.zsh.org/mla/workers/2020/msg00207.html 36 | if sysread -t 0 'buf[$#buf+1]'; then 37 | while [[ $buf != *$'\x1e' ]]; do 38 | sysread 'buf[$#buf+1]' || return 39 | done 40 | else 41 | (( $? == 4 )) || return 42 | fi 43 | for req in ${(ps:\x1e:)buf}; do 44 | _p9k_worker_request_id=${req%%$'\x1f'*} 45 | () { eval $req[$#_p9k_worker_request_id+2,-1] } 46 | (( $+_p9k_worker_inflight[$_p9k_worker_request_id] )) && continue 47 | print -rn -- d$_p9k_worker_request_id$'\x1e' || return 48 | done 49 | else 50 | local REPLY= 51 | while true; do 52 | if sysread -i $fd 'REPLY[$#REPLY+1]'; then 53 | [[ $REPLY == *$'\x1e' ]] || continue 54 | else 55 | (( $? == 5 )) || return 56 | break 57 | fi 58 | done 59 | local cb=$_p9k_worker_fds[$fd] 60 | _p9k_worker_request_id=${cb%%$'\x1f'*} 61 | unset "_p9k_worker_fds[$fd]" 62 | exec {fd}>&- 63 | if [[ $REPLY == *$'\x1e' ]]; then 64 | REPLY[-1]="" 65 | () { eval $cb[$#_p9k_worker_request_id+2,-1] } 66 | fi 67 | if (( --_p9k_worker_inflight[$_p9k_worker_request_id] == 0 )); then 68 | unset "_p9k_worker_inflight[$_p9k_worker_request_id]" 69 | print -rn -- d$_p9k_worker_request_id$'\x1e' || return 70 | fi 71 | fi 72 | done 73 | done 74 | } always { 75 | kill -- -$_p9k_worker_pgid 76 | } 77 | } 78 | 79 | # invoked in master: _p9k_worker_invoke 80 | function _p9k_worker_invoke() { 81 | [[ -n $_p9k__worker_resp_fd ]] || return 82 | local req=$1$'\x1f'$2$'\x1e' 83 | if [[ -n $_p9k__worker_req_fd && $+_p9k__worker_request_map[$1] == 0 ]]; then 84 | _p9k__worker_request_map[$1]= 85 | print -rnu $_p9k__worker_req_fd -- $req 86 | else 87 | _p9k__worker_request_map[$1]=$req 88 | fi 89 | } 90 | 91 | function _p9k_worker_cleanup() { 92 | # __p9k_intro bugs out here in some cases for some reason. 93 | emulate -L zsh 94 | [[ $_p9k__worker_shell_pid == $sysparams[pid] ]] && _p9k_worker_stop 95 | return 0 96 | } 97 | 98 | function _p9k_worker_stop() { 99 | # See comments in _p9k_worker_cleanup. 100 | emulate -L zsh 101 | add-zsh-hook -D zshexit _p9k_worker_cleanup 102 | [[ -n $_p9k__worker_resp_fd ]] && zle -F $_p9k__worker_resp_fd 103 | [[ -n $_p9k__worker_resp_fd ]] && exec {_p9k__worker_resp_fd}>&- 104 | [[ -n $_p9k__worker_req_fd ]] && exec {_p9k__worker_req_fd}>&- 105 | [[ -n $_p9k__worker_pid ]] && kill -- -$_p9k__worker_pid 2>/dev/null 106 | [[ -n $_p9k__worker_file_prefix ]] && zf_rm -f -- $_p9k__worker_file_prefix.fifo 107 | _p9k__worker_pid= 108 | _p9k__worker_req_fd= 109 | _p9k__worker_resp_fd= 110 | _p9k__worker_shell_pid= 111 | _p9k__worker_request_map=() 112 | return 0 113 | } 114 | 115 | function _p9k_worker_receive() { 116 | eval "$__p9k_intro" 117 | 118 | [[ -z $_p9k__worker_resp_fd ]] && return 119 | 120 | { 121 | (( $# <= 1 )) || return 122 | 123 | local buf resp 124 | 125 | [[ -t $_p9k__worker_resp_fd ]] # https://www.zsh.org/mla/workers/2020/msg00207.html 126 | if sysread -i $_p9k__worker_resp_fd -t 0 'buf[$#buf+1]'; then 127 | while [[ $buf == *[^$'\x05\x1e']$'\x05'# ]]; do 128 | sysread -i $_p9k__worker_resp_fd 'buf[$#buf+1]' || return 129 | done 130 | else 131 | (( $? == 4 )) || return 132 | fi 133 | 134 | local -i reset max_reset 135 | for resp in ${(ps:\x1e:)${buf//$'\x05'}}; do 136 | local arg=$resp[2,-1] 137 | case $resp[1] in 138 | d) 139 | local req=$_p9k__worker_request_map[$arg] 140 | if [[ -n $req ]]; then 141 | _p9k__worker_request_map[$arg]= 142 | print -rnu $_p9k__worker_req_fd -- $req || return 143 | else 144 | unset "_p9k__worker_request_map[$arg]" 145 | fi 146 | ;; 147 | e) 148 | () { eval $arg } 149 | (( reset > max_reset )) && max_reset=reset 150 | ;; 151 | s) 152 | [[ -z $_p9k__worker_req_fd ]] || return 153 | [[ $arg == <1-> ]] || return 154 | _p9k__worker_pid=$arg 155 | sysopen -w -o cloexec -u _p9k__worker_req_fd $_p9k__worker_file_prefix.fifo || return 156 | local req= 157 | for req in $_p9k__worker_request_map; do 158 | print -rnu $_p9k__worker_req_fd -- $req || return 159 | done 160 | _p9k__worker_request_map=({${(k)^_p9k__worker_request_map},''}) 161 | ;; 162 | *) 163 | return 1 164 | ;; 165 | esac 166 | done 167 | 168 | if (( max_reset == 2 )); then 169 | _p9k__refresh_reason=worker 170 | _p9k_set_prompt 171 | _p9k__refresh_reason='' 172 | fi 173 | (( max_reset )) && _p9k_reset_prompt 174 | return 0 175 | } always { 176 | (( $? )) && _p9k_worker_stop 177 | } 178 | } 179 | 180 | function _p9k_worker_start() { 181 | setopt monitor || return 182 | { 183 | [[ -n $_p9k__worker_resp_fd ]] && return 184 | 185 | if [[ -n "$TMPDIR" && ( ( -d "$TMPDIR" && -w "$TMPDIR" ) || ! ( -d /tmp && -w /tmp ) ) ]]; then 186 | local tmpdir=$TMPDIR 187 | else 188 | local tmpdir=/tmp 189 | fi 190 | _p9k__worker_file_prefix=$tmpdir/p10k.worker.$EUID.$sysparams[pid].$EPOCHSECONDS 191 | 192 | sysopen -r -o cloexec -u _p9k__worker_resp_fd <( 193 | exec 0$_p9k__worker_file_prefix.log 196 | setopt xtrace 197 | else 198 | exec 2>/dev/null 199 | fi 200 | builtin cd -q / || return 201 | zmodload zsh/zselect || return 202 | ! { zselect -t0 || (( $? != 1 )) } || return 203 | local _p9k_worker_pgid=$sysparams[pid] 204 | _p9k_worker_main & 205 | { 206 | trap '' PIPE 207 | while syswrite $'\x05'; do zselect -t 1000; done 208 | zf_rm -f $_p9k__worker_file_prefix.fifo 209 | kill -- -$_p9k_worker_pgid 210 | } & 211 | exec =true) || return 212 | _p9k__worker_pid=$sysparams[procsubstpid] 213 | zle -F $_p9k__worker_resp_fd _p9k_worker_receive 214 | _p9k__worker_shell_pid=$sysparams[pid] 215 | add-zsh-hook zshexit _p9k_worker_cleanup 216 | } always { 217 | (( $? )) && _p9k_worker_stop 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /gitstatus/src/gitstatus.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include "check.h" 27 | #include "git.h" 28 | #include "logging.h" 29 | #include "options.h" 30 | #include "print.h" 31 | #include "repo.h" 32 | #include "repo_cache.h" 33 | #include "request.h" 34 | #include "response.h" 35 | #include "scope_guard.h" 36 | #include "thread_pool.h" 37 | #include "timer.h" 38 | 39 | namespace gitstatus { 40 | namespace { 41 | 42 | using namespace std::string_literals; 43 | 44 | void Truncate(std::string& s, size_t max_len) { 45 | if (s.size() > max_len) s.resize(max_len); 46 | } 47 | 48 | void ProcessRequest(const Options& opts, RepoCache& cache, Request req) { 49 | Timer timer; 50 | ON_SCOPE_EXIT(&) { timer.Report("request"); }; 51 | 52 | ResponseWriter resp(req.id); 53 | Repo* repo = cache.Open(req.dir, req.from_dotgit); 54 | if (!repo) return; 55 | 56 | git_config* cfg; 57 | VERIFY(!git_repository_config(&cfg, repo->repo())) << GitError(); 58 | ON_SCOPE_EXIT(=) { git_config_free(cfg); }; 59 | VERIFY(!git_config_refresh(cfg)) << GitError(); 60 | 61 | // Symbolic reference if and only if the repo is empty. 62 | git_reference* head = Head(repo->repo()); 63 | if (!head) return; 64 | ON_SCOPE_EXIT(=) { git_reference_free(head); }; 65 | 66 | // Null if and only if the repo is empty. 67 | const git_oid* head_target = git_reference_target(head); 68 | 69 | // Looking up tags may take some time. Do it in the background while we check for stuff. 70 | // Note that GetTagName() doesn't access index, so it'll overlap with index reading and 71 | // parsing. 72 | std::future tag = repo->GetTagName(head_target); 73 | ON_SCOPE_EXIT(&) { 74 | if (tag.valid()) { 75 | try { 76 | tag.wait(); 77 | } catch (const Exception&) { 78 | } 79 | } 80 | }; 81 | 82 | // Repository working directory. Absolute; no trailing slash. E.g., "/home/romka/gitstatus". 83 | StringView workdir(git_repository_workdir(repo->repo())); 84 | if (workdir.len == 0) return; 85 | if (workdir.len > 1 && workdir.ptr[workdir.len - 1] == '/') --workdir.len; 86 | resp.Print(workdir); 87 | 88 | // Revision. Either 40 hex digits or an empty string for empty repo. 89 | resp.Print(head_target ? git_oid_tostr_s(head_target) : ""); 90 | 91 | // Local branch name (e.g., "master") or empty string if not on a branch. 92 | resp.Print(LocalBranchName(head)); 93 | 94 | // Remote tracking branch or null. 95 | RemotePtr remote = GetRemote(repo->repo(), head); 96 | 97 | // Tracking remote branch name (e.g., "master") or empty string if there is no tracking remote. 98 | resp.Print(remote ? remote->branch : ""); 99 | 100 | // Tracking remote name (e.g., "origin") or empty string if there is no tracking remote. 101 | resp.Print(remote ? remote->name : ""); 102 | 103 | // Tracking remote URL or empty string if there is no tracking remote. 104 | resp.Print(remote ? remote->url : ""); 105 | 106 | // Repository state, A.K.A. action. For example, "merge". 107 | resp.Print(RepoState(repo->repo())); 108 | 109 | IndexStats stats; 110 | // Look for staged, unstaged and untracked. This is where most of the time is spent. 111 | if (req.diff) stats = repo->GetIndexStats(head_target, cfg); 112 | 113 | // The number of files in the index. 114 | resp.Print(stats.index_size); 115 | // The number of staged changes. At most opts.max_num_staged. 116 | resp.Print(stats.num_staged); 117 | // The number of unstaged changes. At most opts.max_num_unstaged. 0 if index is too large. 118 | resp.Print(stats.num_unstaged); 119 | // The number of conflicted changes. At most opts.max_num_conflicted. 0 if index is too large. 120 | resp.Print(stats.num_conflicted); 121 | // The number of untracked changes. At most opts.max_num_untracked. 0 if index is too large. 122 | resp.Print(stats.num_untracked); 123 | 124 | if (remote && remote->ref) { 125 | const char* ref = git_reference_name(remote->ref); 126 | // Number of commits we are ahead of upstream. Non-negative integer. 127 | resp.Print(CountRange(repo->repo(), ref + "..HEAD"s)); 128 | // Number of commits we are behind upstream. Non-negative integer. 129 | resp.Print(CountRange(repo->repo(), "HEAD.."s + ref)); 130 | } else { 131 | resp.Print("0"); 132 | resp.Print("0"); 133 | } 134 | 135 | // Number of stashes. Non-negative integer. 136 | resp.Print(NumStashes(repo->repo())); 137 | 138 | // Tag that points to HEAD (e.g., "v4.2") or empty string if there aren't any. The same as 139 | // `git describe --tags --exact-match`. 140 | resp.Print(tag.get()); 141 | 142 | // The number of unstaged deleted files. At most stats.num_unstaged. 143 | resp.Print(stats.num_unstaged_deleted); 144 | // The number of staged new files. At most stats.num_staged. 145 | resp.Print(stats.num_staged_new); 146 | // The number of staged deleted files. At most stats.num_staged. 147 | resp.Print(stats.num_staged_deleted); 148 | 149 | // Push remote or null. 150 | PushRemotePtr push_remote = GetPushRemote(repo->repo(), head); 151 | 152 | // Push remote name (e.g., "origin") or empty string if there is no push remote. 153 | resp.Print(push_remote ? push_remote->name : ""); 154 | 155 | // Push remote URL or empty string if there is no push remote. 156 | resp.Print(push_remote ? push_remote->url : ""); 157 | 158 | if (push_remote && push_remote->ref) { 159 | const char* ref = git_reference_name(push_remote->ref); 160 | // Number of commits we are ahead of push remote. Non-negative integer. 161 | resp.Print(CountRange(repo->repo(), ref + "..HEAD"s)); 162 | // Number of commits we are behind upstream. Non-negative integer. 163 | resp.Print(CountRange(repo->repo(), "HEAD.."s + ref)); 164 | } else { 165 | resp.Print("0"); 166 | resp.Print("0"); 167 | } 168 | 169 | // The number of files in the index with skip-worktree bit set. 170 | resp.Print(stats.num_skip_worktree); 171 | // The number of files in the index with assume-unchanged bit set. 172 | resp.Print(stats.num_assume_unchanged); 173 | 174 | CommitMessage msg = head_target ? GetCommitMessage(repo->repo(), *head_target) : CommitMessage(); 175 | Truncate(msg.summary, opts.max_commit_summary_length); 176 | resp.Print(msg.encoding); 177 | resp.Print(msg.summary); 178 | 179 | resp.Dump("with git status"); 180 | } 181 | 182 | int GitStatus(int argc, char** argv) { 183 | tzset(); 184 | Options opts = ParseOptions(argc, argv); 185 | g_min_log_level = opts.log_level; 186 | for (int i = 0; i != argc; ++i) LOG(INFO) << "argv[" << i << "]: " << Print(argv[i]); 187 | RequestReader reader(fileno(stdin), opts.lock_fd, opts.parent_pid); 188 | RepoCache cache(opts); 189 | 190 | InitGlobalThreadPool(opts.num_threads); 191 | git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0); 192 | git_libgit2_opts(GIT_OPT_DISABLE_INDEX_CHECKSUM_VERIFICATION, 1); 193 | git_libgit2_opts(GIT_OPT_DISABLE_INDEX_FILEPATH_VALIDATION, 1); 194 | git_libgit2_opts(GIT_OPT_DISABLE_READNG_PACKED_TAGS, 1); 195 | git_libgit2_init(); 196 | 197 | while (true) { 198 | try { 199 | Request req; 200 | if (reader.ReadRequest(req)) { 201 | LOG(INFO) << "Processing request: " << req; 202 | try { 203 | ProcessRequest(opts, cache, req); 204 | LOG(INFO) << "Successfully processed request: " << req; 205 | } catch (const Exception&) { 206 | LOG(ERROR) << "Error processing request: " << req; 207 | } 208 | } else if (opts.repo_ttl >= Duration()) { 209 | cache.Free(Clock::now() - opts.repo_ttl); 210 | } 211 | } catch (const Exception&) { 212 | } 213 | } 214 | } 215 | 216 | } // namespace 217 | } // namespace gitstatus 218 | 219 | int main(int argc, char** argv) { gitstatus::GitStatus(argc, argv); } 220 | -------------------------------------------------------------------------------- /gitstatus/src/git.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #include "git.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "arena.h" 32 | #include "check.h" 33 | #include "print.h" 34 | #include "scope_guard.h" 35 | 36 | namespace gitstatus { 37 | 38 | const char* GitError() { 39 | const git_error* err = git_error_last(); 40 | return err && err->message ? err->message : "unknown error"; 41 | } 42 | 43 | std::string RepoState(git_repository* repo) { 44 | Arena arena; 45 | StringView gitdir(git_repository_path(repo)); 46 | 47 | // These names mostly match gitaction in vcs_info: 48 | // https://github.com/zsh-users/zsh/blob/master/Functions/VCS_Info/Backends/VCS_INFO_get_data_git. 49 | auto State = [&]() { 50 | switch (git_repository_state(repo)) { 51 | case GIT_REPOSITORY_STATE_NONE: 52 | return ""; 53 | case GIT_REPOSITORY_STATE_MERGE: 54 | return "merge"; 55 | case GIT_REPOSITORY_STATE_REVERT: 56 | return "revert"; 57 | case GIT_REPOSITORY_STATE_REVERT_SEQUENCE: 58 | return "revert-seq"; 59 | case GIT_REPOSITORY_STATE_CHERRYPICK: 60 | return "cherry"; 61 | case GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE: 62 | return "cherry-seq"; 63 | case GIT_REPOSITORY_STATE_BISECT: 64 | return "bisect"; 65 | case GIT_REPOSITORY_STATE_REBASE: 66 | return "rebase"; 67 | case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE: 68 | return "rebase-i"; 69 | case GIT_REPOSITORY_STATE_REBASE_MERGE: 70 | return "rebase-m"; 71 | case GIT_REPOSITORY_STATE_APPLY_MAILBOX: 72 | return "am"; 73 | case GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE: 74 | return "am/rebase"; 75 | } 76 | return "action"; 77 | }; 78 | 79 | auto DirExists = [&](StringView name) { 80 | int fd = open(arena.StrCat(gitdir, "/", name), O_DIRECTORY | O_CLOEXEC); 81 | if (fd < 0) return false; 82 | CHECK(!close(fd)) << Errno(); 83 | return true; 84 | }; 85 | 86 | auto ReadFile = [&](StringView name) { 87 | std::ifstream strm(arena.StrCat(gitdir, "/", name)); 88 | std::string res; 89 | strm >> res; 90 | return res; 91 | }; 92 | 93 | std::string next; 94 | std::string last; 95 | 96 | if (DirExists("rebase-merge")) { 97 | next = ReadFile("rebase-merge/msgnum"); 98 | last = ReadFile("rebase-merge/end"); 99 | } else if (DirExists("rebase-apply")) { 100 | next = ReadFile("rebase-apply/next"); 101 | last = ReadFile("rebase-apply/last"); 102 | } 103 | 104 | std::ostringstream res; 105 | res << State(); 106 | if (!next.empty() && !last.empty()) res << ' ' << next << '/' << last; 107 | return res.str(); 108 | } 109 | 110 | size_t CountRange(git_repository* repo, const std::string& range) { 111 | git_revwalk* walk = nullptr; 112 | VERIFY(!git_revwalk_new(&walk, repo)) << GitError(); 113 | ON_SCOPE_EXIT(=) { git_revwalk_free(walk); }; 114 | VERIFY(!git_revwalk_push_range(walk, range.c_str())) << GitError(); 115 | size_t res = 0; 116 | while (true) { 117 | git_oid oid; 118 | switch (git_revwalk_next(&oid, walk)) { 119 | case 0: 120 | ++res; 121 | break; 122 | case GIT_ITEROVER: 123 | return res; 124 | default: 125 | LOG(ERROR) << "git_revwalk_next: " << range << ": " << GitError(); 126 | throw Exception(); 127 | } 128 | } 129 | } 130 | 131 | size_t NumStashes(git_repository* repo) { 132 | size_t res = 0; 133 | auto* cb = +[](size_t index, const char* message, const git_oid* stash_id, void* payload) { 134 | ++*static_cast(payload); 135 | return 0; 136 | }; 137 | if (!git_stash_foreach(repo, cb, &res)) return res; 138 | // Example error: failed to parse signature - malformed e-mail. 139 | // See https://github.com/romkatv/powerlevel10k/issues/216. 140 | LOG(WARN) << "git_stash_foreach: " << GitError(); 141 | return 0; 142 | } 143 | 144 | git_reference* Head(git_repository* repo) { 145 | git_reference* symbolic = nullptr; 146 | switch (git_reference_lookup(&symbolic, repo, "HEAD")) { 147 | case 0: 148 | break; 149 | case GIT_ENOTFOUND: 150 | return nullptr; 151 | default: 152 | LOG(ERROR) << "git_reference_lookup: " << GitError(); 153 | throw Exception(); 154 | } 155 | 156 | git_reference* direct = nullptr; 157 | if (git_reference_resolve(&direct, symbolic)) { 158 | LOG(INFO) << "Empty git repo (no HEAD)"; 159 | return symbolic; 160 | } 161 | git_reference_free(symbolic); 162 | return direct; 163 | } 164 | 165 | const char* LocalBranchName(const git_reference* ref) { 166 | CHECK(ref); 167 | git_reference_t type = git_reference_type(ref); 168 | switch (type) { 169 | case GIT_REFERENCE_DIRECT: { 170 | return git_reference_is_branch(ref) ? git_reference_shorthand(ref) : ""; 171 | } 172 | case GIT_REFERENCE_SYMBOLIC: { 173 | static constexpr char kHeadPrefix[] = "refs/heads/"; 174 | const char* target = git_reference_symbolic_target(ref); 175 | if (!target) return ""; 176 | size_t len = std::strlen(target); 177 | if (len < sizeof(kHeadPrefix)) return ""; 178 | if (std::memcmp(target, kHeadPrefix, sizeof(kHeadPrefix) - 1)) return ""; 179 | return target + (sizeof(kHeadPrefix) - 1); 180 | } 181 | case GIT_REFERENCE_INVALID: 182 | case GIT_REFERENCE_ALL: 183 | break; 184 | } 185 | LOG(ERROR) << "Invalid reference type: " << type; 186 | throw Exception(); 187 | } 188 | 189 | RemotePtr GetRemote(git_repository* repo, const git_reference* local) { 190 | git_remote* remote; 191 | git_buf symref = {}; 192 | if (git_branch_remote(&remote, &symref, repo, git_reference_name(local))) return nullptr; 193 | ON_SCOPE_EXIT(&) { 194 | git_remote_free(remote); 195 | git_buf_free(&symref); 196 | }; 197 | 198 | git_reference* ref; 199 | if (git_reference_lookup(&ref, repo, symref.ptr)) return nullptr; 200 | ON_SCOPE_EXIT(&) { if (ref) git_reference_free(ref); }; 201 | 202 | const char* branch = nullptr; 203 | std::string name = remote ? git_remote_name(remote) : "."; 204 | if (git_branch_name(&branch, ref)) { 205 | branch = ""; 206 | } else if (remote) { 207 | VERIFY(std::strstr(branch, name.c_str()) == branch); 208 | VERIFY(branch[name.size()] == '/'); 209 | branch += name.size() + 1; 210 | } 211 | 212 | auto res = std::make_unique(); 213 | res->name = std::move(name); 214 | res->branch = branch; 215 | res->url = remote ? (git_remote_url(remote) ?: "") : ""; 216 | res->ref = std::exchange(ref, nullptr); 217 | return RemotePtr(res.release()); 218 | } 219 | 220 | PushRemotePtr GetPushRemote(git_repository* repo, const git_reference* local) { 221 | git_remote* remote; 222 | git_buf symref = {}; 223 | if (git_branch_push_remote(&remote, &symref, repo, git_reference_name(local))) return nullptr; 224 | ON_SCOPE_EXIT(&) { 225 | git_remote_free(remote); 226 | git_buf_free(&symref); 227 | }; 228 | 229 | git_reference* ref; 230 | if (git_reference_lookup(&ref, repo, symref.ptr)) return nullptr; 231 | ON_SCOPE_EXIT(&) { if (ref) git_reference_free(ref); }; 232 | 233 | std::string name = remote ? git_remote_name(remote) : "."; 234 | 235 | auto res = std::make_unique(); 236 | res->name = std::move(name); 237 | res->url = remote ? (git_remote_url(remote) ?: "") : ""; 238 | res->ref = std::exchange(ref, nullptr); 239 | return PushRemotePtr(res.release()); 240 | } 241 | 242 | CommitMessage GetCommitMessage(git_repository* repo, const git_oid& id) { 243 | git_commit* commit; 244 | VERIFY(!git_commit_lookup(&commit, repo, &id)) << GitError(); 245 | ON_SCOPE_EXIT(=) { git_commit_free(commit); }; 246 | return {.encoding = git_commit_message_encoding(commit) ?: "", 247 | .summary = git_commit_summary(commit) ?: ""}; 248 | } 249 | 250 | } // namespace gitstatus 251 | -------------------------------------------------------------------------------- /config/p10k-pure.zsh: -------------------------------------------------------------------------------- 1 | # Config file for Powerlevel10k with the style of Pure (https://github.com/sindresorhus/pure). 2 | # 3 | # Differences from Pure: 4 | # 5 | # - Git: 6 | # - `@c4d3ec2c` instead of something like `v1.4.0~11` when in detached HEAD state. 7 | # - No automatic `git fetch` (the same as in Pure with `PURE_GIT_PULL=0`). 8 | # 9 | # Apart from the differences listed above, the replication of Pure prompt is exact. This includes 10 | # even the questionable parts. For example, just like in Pure, there is no indication of Git status 11 | # being stale; prompt symbol is the same in command, visual and overwrite vi modes; when prompt 12 | # doesn't fit on one line, it wraps around with no attempt to shorten it. 13 | # 14 | # If you like the general style of Pure but not particularly attached to all its quirks, type 15 | # `p10k configure` and pick "Lean" style. This will give you slick minimalist prompt while taking 16 | # advantage of Powerlevel10k features that aren't present in Pure. 17 | 18 | # Temporarily change options. 19 | 'builtin' 'local' '-a' 'p10k_config_opts' 20 | [[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases') 21 | [[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob') 22 | [[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand') 23 | 'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand' 24 | 25 | () { 26 | emulate -L zsh -o extended_glob 27 | 28 | # Unset all configuration options. 29 | unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR' 30 | 31 | # Zsh >= 5.1 is required. 32 | [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return 33 | 34 | # Prompt colors. 35 | local grey=242 36 | local red=1 37 | local yellow=3 38 | local blue=4 39 | local magenta=5 40 | local cyan=6 41 | local white=7 42 | 43 | # Left prompt segments. 44 | typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=( 45 | # =========================[ Line #1 ]========================= 46 | context # user@host 47 | dir # current directory 48 | vcs # git status 49 | command_execution_time # previous command duration 50 | # =========================[ Line #2 ]========================= 51 | newline # \n 52 | virtualenv # python virtual environment 53 | prompt_char # prompt symbol 54 | ) 55 | 56 | # Right prompt segments. 57 | typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=( 58 | # =========================[ Line #1 ]========================= 59 | # command_execution_time # previous command duration 60 | # virtualenv # python virtual environment 61 | # context # user@host 62 | # time # current time 63 | # =========================[ Line #2 ]========================= 64 | newline # \n 65 | ) 66 | 67 | # Basic style options that define the overall prompt look. 68 | typeset -g POWERLEVEL9K_BACKGROUND= # transparent background 69 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace 70 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space 71 | typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol 72 | typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION= # no segment icons 73 | 74 | # Add an empty line before each prompt except the first. This doesn't emulate the bug 75 | # in Pure that makes prompt drift down whenever you use the Alt-C binding from fzf or similar. 76 | typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=true 77 | 78 | # Magenta prompt symbol if the last command succeeded. 79 | typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS}_FOREGROUND=$magenta 80 | # Red prompt symbol if the last command failed. 81 | typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS}_FOREGROUND=$red 82 | # Default prompt symbol. 83 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯' 84 | # Prompt symbol in command vi mode. 85 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮' 86 | # Prompt symbol in visual vi mode is the same as in command mode. 87 | typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='❮' 88 | # Prompt symbol in overwrite vi mode is the same as in command mode. 89 | typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=false 90 | 91 | # Grey Python Virtual Environment. 92 | typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=$grey 93 | # Don't show Python version. 94 | typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false 95 | typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER= 96 | 97 | # Blue current directory. 98 | typeset -g POWERLEVEL9K_DIR_FOREGROUND=$blue 99 | 100 | # Context format when root: user@host. The first part white, the rest grey. 101 | typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE="%F{$white}%n%f%F{$grey}@%m%f" 102 | # Context format when not root: user@host. The whole thing grey. 103 | typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE="%F{$grey}%n@%m%f" 104 | # Don't show context unless root or in SSH. 105 | typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION= 106 | 107 | # Show previous command duration only if it's >= 5s. 108 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=5 109 | # Don't show fractional seconds. Thus, 7s rather than 7.3s. 110 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0 111 | # Duration format: 1d 2h 3m 4s. 112 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s' 113 | # Yellow previous command duration. 114 | typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=$yellow 115 | 116 | # Grey Git prompt. This makes stale prompts indistinguishable from up-to-date ones. 117 | typeset -g POWERLEVEL9K_VCS_FOREGROUND=$grey 118 | 119 | # Disable async loading indicator to make directories that aren't Git repositories 120 | # indistinguishable from large Git repositories without known state. 121 | typeset -g POWERLEVEL9K_VCS_LOADING_TEXT= 122 | 123 | # Don't wait for Git status even for a millisecond, so that prompt always updates 124 | # asynchronously when Git state changes. 125 | typeset -g POWERLEVEL9K_VCS_MAX_SYNC_LATENCY_SECONDS=0 126 | 127 | # Cyan ahead/behind arrows. 128 | typeset -g POWERLEVEL9K_VCS_{INCOMING,OUTGOING}_CHANGESFORMAT_FOREGROUND=$cyan 129 | # Don't show remote branch, current tag or stashes. 130 | typeset -g POWERLEVEL9K_VCS_GIT_HOOKS=(vcs-detect-changes git-untracked git-aheadbehind) 131 | # Don't show the branch icon. 132 | typeset -g POWERLEVEL9K_VCS_BRANCH_ICON= 133 | # When in detached HEAD state, show @commit where branch normally goes. 134 | typeset -g POWERLEVEL9K_VCS_COMMIT_ICON='@' 135 | # Don't show staged, unstaged, untracked indicators. 136 | typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED}_ICON= 137 | # Show '*' when there are staged, unstaged or untracked files. 138 | typeset -g POWERLEVEL9K_VCS_DIRTY_ICON='*' 139 | # Show '⇣' if local branch is behind remote. 140 | typeset -g POWERLEVEL9K_VCS_INCOMING_CHANGES_ICON=':⇣' 141 | # Show '⇡' if local branch is ahead of remote. 142 | typeset -g POWERLEVEL9K_VCS_OUTGOING_CHANGES_ICON=':⇡' 143 | # Don't show the number of commits next to the ahead/behind arrows. 144 | typeset -g POWERLEVEL9K_VCS_{COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=1 145 | # Remove space between '⇣' and '⇡' and all trailing spaces. 146 | typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${${${P9K_CONTENT/⇣* :⇡/⇣⇡}// }//:/ }' 147 | 148 | # Grey current time. 149 | typeset -g POWERLEVEL9K_TIME_FOREGROUND=$grey 150 | # Format for the current time: 09:51:02. See `man 3 strftime`. 151 | typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}' 152 | # If set to true, time will update when you hit enter. This way prompts for the past 153 | # commands will contain the start times of their commands rather than the end times of 154 | # their preceding commands. 155 | typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false 156 | 157 | # Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt 158 | # when accepting a command line. Supported values: 159 | # 160 | # - off: Don't change prompt when accepting a command line. 161 | # - always: Trim down prompt when accepting a command line. 162 | # - same-dir: Trim down prompt when accepting a command line unless this is the first command 163 | # typed after changing current working directory. 164 | typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off 165 | 166 | # Instant prompt mode. 167 | # 168 | # - off: Disable instant prompt. Choose this if you've tried instant prompt and found 169 | # it incompatible with your zsh configuration files. 170 | # - quiet: Enable instant prompt and don't print warnings when detecting console output 171 | # during zsh initialization. Choose this if you've read and understood 172 | # https://github.com/romkatv/powerlevel10k#instant-prompt. 173 | # - verbose: Enable instant prompt and print a warning when detecting console output during 174 | # zsh initialization. Choose this if you've never tried instant prompt, haven't 175 | # seen the warning, or if you are unsure what this all means. 176 | typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose 177 | 178 | # Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized. 179 | # For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload 180 | # can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you 181 | # really need it. 182 | typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true 183 | 184 | # If p10k is already loaded, reload configuration. 185 | # This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true. 186 | (( ! $+functions[p10k] )) || p10k reload 187 | } 188 | 189 | # Tell `p10k configure` which file it should overwrite. 190 | typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a} 191 | 192 | (( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]} 193 | 'builtin' 'unset' 'p10k_config_opts' 194 | -------------------------------------------------------------------------------- /gitstatus/src/arena.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Roman Perepelitsa. 2 | // 3 | // This file is part of GitStatus. 4 | // 5 | // GitStatus is free software: you can redistribute it and/or modify 6 | // it under the terms of the GNU General Public License as published by 7 | // the Free Software Foundation, either version 3 of the License, or 8 | // (at your option) any later version. 9 | // 10 | // GitStatus is distributed in the hope that it will be useful, 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | // GNU General Public License for more details. 14 | // 15 | // You should have received a copy of the GNU General Public License 16 | // along with GitStatus. If not, see . 17 | 18 | #ifndef ROMKATV_GITSTATUS_ARENA_H_ 19 | #define ROMKATV_GITSTATUS_ARENA_H_ 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "string_view.h" 31 | 32 | namespace gitstatus { 33 | 34 | // Thread-compatible. Very fast and very flexible w.r.t. allocation size and alignment. 35 | // 36 | // Natural API extensions: 37 | // 38 | // // Donates a block to the arena. When the time comes, it'll be freed with 39 | // // free(p, size, userdata). 40 | // void Donate(void* p, size_t size, void* userdata, void(*free)(void*, size_t, void*)); 41 | class Arena { 42 | public: 43 | struct Options { 44 | // The first call to Allocate() will allocate a block of this size. There is one exception when 45 | // the first requested allocation size is larger than this limit. Subsequent blocks will be 46 | // twice as large as the last until they saturate at max_block_size. 47 | size_t min_block_size = 64; 48 | 49 | // Allocate blocks at most this large. There is one exception when the requested allocation 50 | // size is larger than this limit. 51 | size_t max_block_size = 8 << 10; 52 | 53 | // When the size of the first allocation in a block is larger than this threshold, the block 54 | // size will be equal to the allocation size. This is meant to reduce memory waste when making 55 | // many allocations with sizes slightly over max_block_size / 2. With max_alloc_threshold equal 56 | // to max_block_size / N, the upper bound on wasted memory when making many equally-sized 57 | // allocations is 100.0 / (N + 1) percent. When making allocations of different sizes, the upper 58 | // bound on wasted memory is 50%. 59 | size_t max_alloc_threshold = 1 << 10; 60 | 61 | // Natural extensions: 62 | // 63 | // void* userdata; 64 | // void (*alloc)(size_t size, size_t alignment, void* userdata); 65 | // void (*free)(void* p, size_t size, void* userdata); 66 | }; 67 | 68 | // Requires: opt.min_block_size <= opt.max_block_size. 69 | // 70 | // Doesn't allocate any memory. 71 | Arena(Options opt); 72 | Arena() : Arena(Options()) {} 73 | Arena(Arena&&); 74 | ~Arena(); 75 | 76 | Arena& operator=(Arena&& other); 77 | 78 | // Requires: alignment is a power of 2. 79 | // 80 | // Result is never null and always aligned. If size is zero, the result may be equal to the last. 81 | // Alignment above alignof(std::max_align_t) is supported. There is no requirement for alignment 82 | // to be less than size or to divide it. 83 | inline void* Allocate(size_t size, size_t alignment) { 84 | assert(alignment && !(alignment & (alignment - 1))); 85 | uintptr_t p = Align(top_->tip, alignment); 86 | uintptr_t e = p + size; 87 | if (e <= top_->end) { 88 | top_->tip = e; 89 | return reinterpret_cast(p); 90 | } 91 | return AllocateSlow(size, alignment); 92 | } 93 | 94 | template 95 | inline T* Allocate(size_t n) { 96 | static_assert(!std::is_reference(), ""); 97 | return static_cast(Allocate(n * sizeof(T), alignof(T))); 98 | } 99 | 100 | template 101 | inline T* Allocate() { 102 | return Allocate(1); 103 | } 104 | 105 | inline char* MemDup(const char* p, size_t len) { 106 | char* res = Allocate(len); 107 | std::memcpy(res, p, len); 108 | return res; 109 | } 110 | 111 | // Copies the null-terminated string (including the trailing null character) to the arena and 112 | // returns a pointer to the copy. 113 | inline char* StrDup(const char* s) { 114 | size_t len = std::strlen(s); 115 | return MemDup(s, len + 1); 116 | } 117 | 118 | // Guarantees: !StrDup(p, len)[len]. 119 | inline char* StrDup(const char* p, size_t len) { 120 | char* res = Allocate(len + 1); 121 | std::memcpy(res, p, len); 122 | res[len] = 0; 123 | return res; 124 | } 125 | 126 | // Guarantees: !StrDup(s)[s.len]. 127 | inline char* StrDup(StringView s) { 128 | return StrDup(s.ptr, s.len); 129 | } 130 | 131 | template 132 | inline char* StrCat(const Ts&... ts) { 133 | return [&](std::initializer_list ss) { 134 | size_t len = 0; 135 | for (StringView s : ss) len += s.len; 136 | char* p = Allocate(len + 1); 137 | for (StringView s : ss) { 138 | std::memcpy(p, s.ptr, s.len); 139 | p += s.len; 140 | } 141 | *p = 0; 142 | return p - len; 143 | }({ts...}); 144 | } 145 | 146 | // Copies/moves `val` to the arena and returns a pointer to it. 147 | template 148 | inline std::remove_const_t>* Dup(T&& val) { 149 | return DirectInit>>(std::forward(val)); 150 | } 151 | 152 | // The same as `new T{args...}` but on the arena. 153 | template 154 | inline T* DirectInit(Args&&... args) { 155 | T* res = Allocate(); 156 | ::new (const_cast(static_cast(res))) T(std::forward(args)...); 157 | return res; 158 | } 159 | 160 | // The same as `new T(args...)` but on the arena. 161 | template 162 | inline T* BraceInit(Args&&... args) { 163 | T* res = Allocate(); 164 | ::new (const_cast(static_cast(res))) T{std::forward(args)...}; 165 | return res; 166 | } 167 | 168 | // Tip() and TipSize() allow you to allocate the remainder of the current block. They can be 169 | // useful if you are flexible w.r.t. the allocation size. 170 | // 171 | // Invariant: 172 | // 173 | // const void* tip = Tip(); 174 | // void* p = Allocate(TipSize(), 1); // grab the remainder of the current block 175 | // assert(p == tip); 176 | const void* Tip() const { return reinterpret_cast(top_->tip); } 177 | size_t TipSize() const { return top_->end - top_->tip; } 178 | 179 | // Invalidates all allocations (without running destructors of allocated objects) and frees all 180 | // blocks except at most the specified number of blocks. The retained blocks will be used to 181 | // fulfil future allocation requests. 182 | void Reuse(size_t num_blocks = std::numeric_limits::max()); 183 | 184 | private: 185 | struct Block { 186 | size_t size() const { return end - start; } 187 | uintptr_t start; 188 | uintptr_t tip; 189 | uintptr_t end; 190 | }; 191 | 192 | inline static size_t Align(size_t n, size_t m) { return (n + m - 1) & ~(m - 1); }; 193 | 194 | void AddBlock(size_t size, size_t alignment); 195 | bool ReuseBlock(size_t size, size_t alignment); 196 | 197 | __attribute__((noinline)) void* AllocateSlow(size_t size, size_t alignment); 198 | 199 | Options opt_; 200 | std::vector blocks_; 201 | // Invariant: !blocks_.empty() <= reusable_ && reusable_ <= blocks_.size(). 202 | size_t reusable_ = 0; 203 | // Invariant: (top_ == &g_empty_block) == blocks_.empty(). 204 | // Invariant: blocks_.empty() || top_ == &blocks_.back() || top_ < blocks_.data() + reusable_. 205 | Block* top_; 206 | 207 | static Block g_empty_block; 208 | }; 209 | 210 | // Copies of ArenaAllocator use the same thread-compatible Arena without synchronization. 211 | template 212 | class ArenaAllocator { 213 | public: 214 | using value_type = T; 215 | using pointer = T*; 216 | using const_pointer = const T*; 217 | using reference = T&; 218 | using const_reference = const T&; 219 | using size_type = size_t; 220 | using difference_type = ptrdiff_t; 221 | using propagate_on_container_move_assignment = std::true_type; 222 | template 223 | struct rebind { 224 | using other = ArenaAllocator; 225 | }; 226 | using is_always_equal = std::false_type; 227 | 228 | ArenaAllocator(Arena* arena = nullptr) : arena_(*arena) {} 229 | 230 | Arena& arena() const { return arena_; } 231 | 232 | pointer address(reference x) const { return &x; } 233 | const_pointer address(const_reference x) const { return &x; } 234 | pointer allocate(size_type n, const void* hint = nullptr) { return arena_.Allocate(n); } 235 | void deallocate(T* p, std::size_t n) {} 236 | size_type max_size() const { return std::numeric_limits::max() / sizeof(value_type); } 237 | 238 | template 239 | void construct(U* p, Args&&... args) { 240 | ::new (const_cast(static_cast(p))) U(std::forward(args)...); 241 | } 242 | 243 | template 244 | void destroy(U* p) { 245 | p->~U(); 246 | } 247 | 248 | bool operator==(const ArenaAllocator& other) const { return &arena_ == &other.arena_; } 249 | bool operator!=(const ArenaAllocator& other) const { return &arena_ != &other.arena_; } 250 | 251 | private: 252 | Arena& arena_; 253 | }; 254 | 255 | template 256 | struct LazyWithArena; 257 | 258 | template