diff --git a/README.md b/README.md index cbf29c3a..0fcf098b 100644 --- a/README.md +++ b/README.md @@ -43,15 +43,15 @@ git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus echo 'source ~/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc ``` -Alternatively, on macOS you can install with Homebrew: +Alternatively, if you have Homebrew installed: ```zsh brew install romkatv/gitstatus/gitstatus -echo 'source /usr/local/opt/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc +echo "source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.zsh" >>! ~/.zshrc ``` -(If you choose this option, replace `~/gitstatus` with `/usr/local/opt/gitstatus` in all code -snippets below.) +(If you choose this option, replace `~/gitstatus` with `$(brew --prefix)/opt/gitstatus/gitstatus` +in all code snippets below.) _Make sure to disable your current theme if you have one._ @@ -144,15 +144,15 @@ git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus echo 'source ~/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc ``` -Alternatively, on macOS you can install with Homebrew: +Alternatively, if you have Homebrew installed: ```zsh brew install romkatv/gitstatus/gitstatus -echo 'source /usr/local/opt/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc +echo "source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.sh" >> ~/.bashrc ``` -(If you choose this option, replace `~/gitstatus` with `/usr/local/opt/gitstatus` in all code -snippets below.) +(If you choose this option, replace `~/gitstatus` with `$(brew --prefix)/opt/gitstatus/gitstatus` +in all code snippets below.) This will give you a basic yet functional prompt with git status in it. It's [over 10x faster](#benchmarks) than any alternative that can give you comparable prompt. diff --git a/build b/build index ad1d1484..667def9f 100755 --- a/build +++ b/build @@ -90,6 +90,9 @@ if [ -n "$gitstatus_install_tools" ]; then freebsd) command pkg install -y cmake gmake binutils gcc git perl5 ;; + openbsd) + command pkg_add install cmake gmake gcc git wget + ;; netbsd) command pkgin -y install cmake gmake binutils git ;; @@ -102,7 +105,7 @@ if [ -n "$gitstatus_install_tools" ]; then sudo port -N install libiconv cmake wget elif command -v brew >/dev/null 2>&1; then for formula in libiconv cmake git wget; do - if command brew list "$formula" &>/dev/null; then + if command brew ls --version "$formula" &>/dev/null; then command brew upgrade "$formula" else command brew install "$formula" @@ -160,6 +163,13 @@ case "$gitstatus_kernel" in gitstatus_ldflags="$gitstatus_ldflags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now" libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON" ;; + openbsd) + gitstatus_cxx=eg++ + gitstatus_make=gmake + gitstatus_ldflags="$gitstatus_ldflags -static" + gitstatus_ldflags="$gitstatus_ldflags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now" + libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON" + ;; netbsd) gitstatus_make=gmake gitstatus_ldflags="$gitstatus_ldflags -static" @@ -473,7 +483,7 @@ case "$gitstatus_kernel" in fi fi ;; - freebsd|netbsd|darwin) + freebsd|openbsd|netbsd|darwin) if [ -n "$docker_cmd" ]; then >&2 echo "[error] docker (-d) is not supported on $gitstatus_kernel" exit 1 diff --git a/build.info b/build.info index aa52263b..ec19ff74 100644 --- a/build.info +++ b/build.info @@ -3,7 +3,7 @@ # # This value is also read by shell bindings (indirectly, through # ./install) when using GITSTATUS_DAEMON or usrbin/gitstatusd. -gitstatus_version="v1.3.1" +gitstatus_version="v1.5.0" # libgit2 is a build time dependency of gitstatusd. The values of # libgit2_version and libgit2_sha256 are read by ./build. diff --git a/gitstatus.plugin.sh b/gitstatus.plugin.sh index 61b81e6a..95c5402e 100644 --- a/gitstatus.plugin.sh +++ b/gitstatus.plugin.sh @@ -264,50 +264,18 @@ function gitstatus_start() { return 1 fi + export _GITSTATUS_CLIENT_PID _GITSTATUS_REQ_FD _GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID unset -f gitstatus_start_impl - - if [[ "${GITSTATUS_STOP_ON_EXEC:-1}" == 1 ]]; then - type -t _gitstatus_exec &>/dev/null || function _gitstatus_exec() { exec "$@"; } - type -t _gitstatus_builtin &>/dev/null || function _gitstatus_builtin() { builtin "$@"; } - - function _gitstatus_exec_wrapper() { - (( ! $# )) || gitstatus_stop - local ret=0 - _gitstatus_exec "$@" || ret=$? - [[ -n "${GITSTATUS_DAEMON_PID:-}" ]] || gitstatus_start || true - return $ret - } - - function _gitstatus_builtin_wrapper() { - while [[ "${1:-}" == builtin ]]; do shift; done - if [[ "${1:-}" == exec ]]; then - _gitstatus_exec_wrapper "${@:2}" - else - _gitstatus_builtin "$@" - fi - } - - alias exec=_gitstatus_exec_wrapper - alias builtin=_gitstatus_builtin_wrapper - - _GITSTATUS_EXEC_HOOK=1 - else - unset _GITSTATUS_EXEC_HOOK - fi } # Stops gitstatusd if it's running. function gitstatus_stop() { - [[ "${_GITSTATUS_CLIENT_PID:-$BASHPID}" == "$BASHPID" ]] || return 0 - [[ -z "${_GITSTATUS_REQ_FD:-}" ]] || exec {_GITSTATUS_REQ_FD}>&- || true - [[ -z "${_GITSTATUS_RESP_FD:-}" ]] || exec {_GITSTATUS_RESP_FD}>&- || true - [[ -z "${GITSTATUS_DAEMON_PID:-}" ]] || kill "$GITSTATUS_DAEMON_PID" &>/dev/null || true - if [[ -n "${_GITSTATUS_EXEC_HOOK:-}" ]]; then - unalias exec builtin &>/dev/null || true - function _gitstatus_exec_wrapper() { _gitstatus_exec "$@"; } - function _gitstatus_builtin_wrapper() { _gitstatus_builtin "$@"; } + if [[ "${_GITSTATUS_CLIENT_PID:-$BASHPID}" == "$BASHPID" ]]; then + [[ -z "${_GITSTATUS_REQ_FD:-}" ]] || exec {_GITSTATUS_REQ_FD}>&- || true + [[ -z "${_GITSTATUS_RESP_FD:-}" ]] || exec {_GITSTATUS_RESP_FD}>&- || true + [[ -z "${GITSTATUS_DAEMON_PID:-}" ]] || kill "$GITSTATUS_DAEMON_PID" &>/dev/null || true fi - unset _GITSTATUS_REQ_FD _GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID _GITSTATUS_EXEC_HOOK + unset _GITSTATUS_REQ_FD _GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID unset _GITSTATUS_DIRTY_MAX_INDEX_SIZE _GITSTATUS_CLIENT_PID } @@ -332,6 +300,8 @@ function gitstatus_stop() { # VCS_STATUS_WORKDIR Git repo working directory. Not empty. # VCS_STATUS_COMMIT Commit hash that HEAD is pointing to. Either 40 hex digits or # empty if there is no HEAD (empty repo). +# VCS_STATUS_COMMIT_ENCODING Encoding of the HEAD's commit message. Empty value means UTF-8. +# VCS_STATUS_COMMIT_SUMMARY The first paragraph of the HEAD's commit message as one line. # VCS_STATUS_LOCAL_BRANCH Local branch name or empty if not on a branch. # VCS_STATUS_REMOTE_NAME The remote name, e.g. "upstream" or "origin". # VCS_STATUS_REMOTE_BRANCH Upstream branch name. Can be empty. @@ -435,6 +405,8 @@ function gitstatus_query() { VCS_STATUS_PUSH_COMMITS_BEHIND="${resp[24]:-0}" VCS_STATUS_NUM_SKIP_WORKTREE="${resp[25]:-0}" VCS_STATUS_NUM_ASSUME_UNCHANGED="${resp[26]:-0}" + VCS_STATUS_COMMIT_ENCODING="${resp[27]-}" + VCS_STATUS_COMMIT_SUMMARY="${resp[28]-}" VCS_STATUS_HAS_STAGED=$((VCS_STATUS_NUM_STAGED > 0)) if (( _GITSTATUS_DIRTY_MAX_INDEX_SIZE >= 0 && VCS_STATUS_INDEX_SIZE > _GITSTATUS_DIRTY_MAX_INDEX_SIZE_ )); then @@ -477,6 +449,8 @@ function gitstatus_query() { unset VCS_STATUS_PUSH_COMMITS_BEHIND unset VCS_STATUS_NUM_SKIP_WORKTREE unset VCS_STATUS_NUM_ASSUME_UNCHANGED + unset VCS_STATUS_COMMIT_ENCODING + unset VCS_STATUS_COMMIT_SUMMARY fi } diff --git a/gitstatus.plugin.zsh b/gitstatus.plugin.zsh index ca0fb311..c37af1d7 100644 --- a/gitstatus.plugin.zsh +++ b/gitstatus.plugin.zsh @@ -15,6 +15,8 @@ # VCS_STATUS_COMMIT=c000eddcff0fb38df2d0137efe24d9d2d900f209 # VCS_STATUS_COMMITS_AHEAD=0 # VCS_STATUS_COMMITS_BEHIND=0 +# VCS_STATUS_COMMIT_ENCODING='' +# VCS_STATUS_COMMIT_SUMMARY='pull upstream changes from gitstatus' # VCS_STATUS_HAS_CONFLICTED=0 # VCS_STATUS_HAS_STAGED=0 # VCS_STATUS_HAS_UNSTAGED=1 @@ -88,6 +90,8 @@ typeset -g _gitstatus_plugin_dir"${1:-}"="${${(%):-%x}:A:h}" # VCS_STATUS_WORKDIR Git repo working directory. Not empty. # VCS_STATUS_COMMIT Commit hash that HEAD is pointing to. Either 40 hex digits or # empty if there is no HEAD (empty repo). +# VCS_STATUS_COMMIT_ENCODING Encoding of the HEAD's commit message. Empty value means UTF-8. +# VCS_STATUS_COMMIT_SUMMARY The first paragraph of the HEAD's commit message as one line. # VCS_STATUS_LOCAL_BRANCH Local branch name or empty if not on a branch. # VCS_STATUS_REMOTE_NAME The remote name, e.g. "upstream" or "origin". # VCS_STATUS_REMOTE_BRANCH Upstream branch name. Can be empty. @@ -329,7 +333,9 @@ function _gitstatus_process_response"${1:-}"() { VCS_STATUS_PUSH_COMMITS_AHEAD \ VCS_STATUS_PUSH_COMMITS_BEHIND \ VCS_STATUS_NUM_SKIP_WORKTREE \ - VCS_STATUS_NUM_ASSUME_UNCHANGED in "${(@)resp[3,27]}"; do + VCS_STATUS_NUM_ASSUME_UNCHANGED \ + VCS_STATUS_COMMIT_ENCODING \ + VCS_STATUS_COMMIT_SUMMARY in "${(@)resp[3,29]}"; do done typeset -gi VCS_STATUS_{INDEX_SIZE,NUM_STAGED,NUM_UNSTAGED,NUM_CONFLICTED,NUM_UNTRACKED,COMMITS_AHEAD,COMMITS_BEHIND,STASHES,NUM_UNSTAGED_DELETED,NUM_STAGED_NEW,NUM_STAGED_DELETED,PUSH_COMMITS_AHEAD,PUSH_COMMITS_BEHIND,NUM_SKIP_WORKTREE,NUM_ASSUME_UNCHANGED} typeset -gi VCS_STATUS_HAS_STAGED=$((VCS_STATUS_NUM_STAGED > 0)) diff --git a/install.info b/install.info index e71a03ef..8214e938 100644 --- a/install.info +++ b/install.info @@ -1,4 +1,4 @@ -# 0 +# 1 # # This file is used by ./install and indirectly by shell bindings. # @@ -19,7 +19,7 @@ uname_s_glob="linux"; uname_m_glob="armv7l"; file="gitstatusd-${uname_ uname_s_glob="linux"; uname_m_glob="armv8l"; file="gitstatusd-${uname_s}-aarch64"; version="v1.3.1"; sha256="4e0a506eafb14b009cf6670f0e11399ac7e765cad17b9fcf38ef65516d248bfa"; uname_s_glob="linux"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="ba506fbecf4a4430533e661bb63c7b77f6b4836ea013bdf8a6eabeace456f3b9"; uname_s_glob="linux"; uname_m_glob="ppc64le"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="1bf907db28ac7d6516add51be47b73b1854b84ecf46de56ccb1479e6a7e29ed2"; -uname_s_glob="linux"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="91bcc1efafff8c896e8f172ff624d9407494f7a26b4ad1bf573f52623be2ca91"; +uname_s_glob="linux"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.0"; sha256="d88e33e6174b205d76eaa6f6a88129d5854f9f52348983807dede2e81caae844"; uname_s_glob="msys_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="618d2425c6a22fa3762fe6fe252f9ddb4ed9138df1377e48b2f119cd4875f400"; uname_s_glob="msys_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="bdfae7a7c0fd83d0214a7eabde3b7d8709336bd08697a74d48bea4a04c352676"; diff --git a/src/git.cc b/src/git.cc index 029b02bf..552100cb 100644 --- a/src/git.cc +++ b/src/git.cc @@ -239,4 +239,12 @@ PushRemotePtr GetPushRemote(git_repository* repo, const git_reference* local) { return PushRemotePtr(res.release()); } +CommitMessage GetCommitMessage(git_repository* repo, const git_oid& id) { + git_commit* commit; + VERIFY(!git_commit_lookup(&commit, repo, &id)) << GitError(); + ON_SCOPE_EXIT(=) { git_commit_free(commit); }; + return {.encoding = git_commit_message_encoding(commit) ?: "", + .summary = git_commit_summary(commit) ?: ""}; +} + } // namespace gitstatus diff --git a/src/git.h b/src/git.h index 7e5a6f9d..b85f09f7 100644 --- a/src/git.h +++ b/src/git.h @@ -48,6 +48,15 @@ git_reference* Head(git_repository* repo); // Returns the name of the local branch, or an empty string. const char* LocalBranchName(const git_reference* ref); +struct CommitMessage { + // Can be empty, meaning "UTF-8". + std::string encoding; + // The first paragraph of the commit's message as a one-liner. + std::string summary; +}; + +CommitMessage GetCommitMessage(git_repository* repo, const git_oid& id); + struct Remote { // Tip of the remote branch. git_reference* ref; diff --git a/src/gitstatus.cc b/src/gitstatus.cc index 5560ca8d..81399ea7 100644 --- a/src/gitstatus.cc +++ b/src/gitstatus.cc @@ -41,6 +41,10 @@ namespace { using namespace std::string_literals; +void Truncate(std::string& s, size_t max_len) { + if (s.size() > max_len) s.resize(max_len); +} + void ProcessRequest(const Options& opts, RepoCache& cache, Request req) { Timer timer; ON_SCOPE_EXIT(&) { timer.Report("request"); }; @@ -167,6 +171,11 @@ void ProcessRequest(const Options& opts, RepoCache& cache, Request req) { // The number of files in the index with assume-unchanged bit set. resp.Print(stats.num_assume_unchanged); + CommitMessage msg = head_target ? GetCommitMessage(repo->repo(), *head_target) : CommitMessage(); + Truncate(msg.summary, opts.max_commit_summary_length); + resp.Print(msg.encoding); + resp.Print(msg.summary); + resp.Dump("with git status"); } diff --git a/src/options.cc b/src/options.cc index 421e5854..1879c424 100644 --- a/src/options.cc +++ b/src/options.cc @@ -53,6 +53,12 @@ long ParseInt(const char* s) { return res; } +size_t ParseSizeT(const char* s) { + static_assert(sizeof(long) <= sizeof(size_t)); + long res = ParseLong(s); + return res >= 0 ? res : -1; +} + void PrintUsage() { std::cout << "Usage: gitstatusd [OPTION]...\n" << "Print machine-readable status of the git repos for directores in stdin.\n" @@ -81,12 +87,18 @@ void PrintUsage() { << " repo that's been closed is much slower than for a repo that hasn't been.\n" << " Negative value means infinity.\n" << "\n" + << " -z, --max-commit-summary-length=NUM [default=256]\n" + << " Truncate commit summary if it's longer than this many bytes.\n" + << "\n" << " -s, --max-num-staged=NUM [default=1]\n" << " Report at most this many staged changes; negative value means infinity.\n" << "\n" << " -u, --max-num-unstaged=NUM [default=1]\n" << " Report at most this many unstaged changes; negative value means infinity.\n" << "\n" + << " -c, --max-num-conflicted=NUM [default=1]\n" + << " Report at most this many conflicted changes; negative value means infinity.\n" + << "\n" << " -d, --max-num-untracked=NUM [default=1]\n" << " Report at most this many untracked files; negative value means infinity.\n" << "\n" @@ -170,6 +182,8 @@ void PrintUsage() { << " 25. Number of commits the current branch is behind push remote.\n" << " 26. Number of files in the index with skip-worktree bit set.\n" << " 27. Number of files in the index with assume-unchanged bit set.\n" + << " 28. Encoding of the HEAD's commit message. Empty value means UTF-8.\n" + << " 29. The first paragraph of the HEAD's commit message as one line.\n" << "\n" << "Note: Renamed files are reported as deleted plus new.\n" << "\n" @@ -212,6 +226,8 @@ void PrintUsage() { << " '0'\n" << " '0'\n" << " '0'\n" + << " ''\n" + << " 'add a build server for darwin-arm64'\n" << "\n" << "EXIT STATUS\n" << "\n" @@ -239,12 +255,13 @@ const char* Version() { Options ParseOptions(int argc, char** argv) { const struct option opts[] = {{"help", no_argument, nullptr, 'h'}, {"version", no_argument, nullptr, 'V'}, - {"version-glob", no_argument, nullptr, 'G'}, + {"version-glob", required_argument, nullptr, 'G'}, {"lock-fd", required_argument, nullptr, 'l'}, {"parent-pid", required_argument, nullptr, 'p'}, {"num-threads", required_argument, nullptr, 't'}, {"log-level", required_argument, nullptr, 'v'}, {"repo-ttl-seconds", required_argument, nullptr, 'r'}, + {"max-commit-summary-length", required_argument, nullptr, 'z'}, {"max-num-staged", required_argument, nullptr, 's'}, {"max-num-unstaged", required_argument, nullptr, 'u'}, {"max-num-conflicted", required_argument, nullptr, 'c'}, @@ -257,7 +274,7 @@ Options ParseOptions(int argc, char** argv) { {}}; Options res; while (true) { - switch (getopt_long(argc, argv, "hVG:l:p:t:v:r:s:u:c:d:m:eUWD", opts, nullptr)) { + switch (getopt_long(argc, argv, "hVG:l:p:t:v:r:z:s:u:c:d:m:eUWD", opts, nullptr)) { case -1: if (optind != argc) { std::cerr << "unexpected positional argument: " << argv[optind] << std::endl; @@ -306,20 +323,23 @@ Options ParseOptions(int argc, char** argv) { res.num_threads = n; break; } + case 'z': + res.max_commit_summary_length = ParseSizeT(optarg); + break; case 's': - res.max_num_staged = ParseLong(optarg); + res.max_num_staged = ParseSizeT(optarg); break; case 'u': - res.max_num_unstaged = ParseLong(optarg); + res.max_num_unstaged = ParseSizeT(optarg); break; case 'c': - res.max_num_conflicted = ParseLong(optarg); + res.max_num_conflicted = ParseSizeT(optarg); break; case 'd': - res.max_num_untracked = ParseLong(optarg); + res.max_num_untracked = ParseSizeT(optarg); break; case 'm': - res.dirty_max_index_size = ParseLong(optarg); + res.dirty_max_index_size = ParseSizeT(optarg); break; case 'e': res.recurse_untracked_dirs = true; diff --git a/src/options.h b/src/options.h index 7cbfeed8..fd561e11 100644 --- a/src/options.h +++ b/src/options.h @@ -27,6 +27,8 @@ namespace gitstatus { struct Limits { + // Truncate commit summary if it's longer than this many bytes. + size_t max_commit_summary_length = 256; // Report at most this many staged changes. size_t max_num_staged = 1; // Report at most this many unstaged changes.