diff --git a/gitstatus.plugin.sh b/gitstatus.plugin.sh index 8badeb9..95c5402 100644 --- a/gitstatus.plugin.sh +++ b/gitstatus.plugin.sh @@ -300,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. @@ -403,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 @@ -445,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 ca0fb31..c37af1d 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/src/git.cc b/src/git.cc index 029b02b..552100c 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 7e5a6f9..b85f09f 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 5560ca8..81399ea 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 d88354d..1879c42 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" @@ -245,6 +261,7 @@ Options ParseOptions(int argc, char** argv) { {"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 7cbfeed..fd561e1 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.