{"id":13399540,"url":"https://github.com/romkatv/gitstatus","last_synced_at":"2025-04-11T11:46:06.446Z","repository":{"id":38864649,"uuid":"172673408","full_name":"romkatv/gitstatus","owner":"romkatv","description":"Git status for Bash and Zsh prompt","archived":false,"fork":false,"pushed_at":"2024-10-03T09:16:16.000Z","size":76165,"stargazers_count":1725,"open_issues_count":24,"forks_count":105,"subscribers_count":25,"default_branch":"master","last_synced_at":"2025-04-03T14:42:03.102Z","etag":null,"topics":["bash","git","zsh"],"latest_commit_sha":null,"homepage":"","language":"C++","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/romkatv.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-02-26T08:50:40.000Z","updated_at":"2025-04-03T13:02:59.000Z","dependencies_parsed_at":"2023-12-10T12:25:23.279Z","dependency_job_id":"aa0ea799-cb62-4a76-a213-6a94a9305ced","html_url":"https://github.com/romkatv/gitstatus","commit_stats":{"total_commits":1363,"total_committers":29,"mean_commits":47.0,"dds":0.03741746148202496,"last_synced_commit":"44504a24b1b999a4f56ff74c75b8215bdcadee1f"},"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romkatv%2Fgitstatus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romkatv%2Fgitstatus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romkatv%2Fgitstatus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/romkatv%2Fgitstatus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/romkatv","download_url":"https://codeload.github.com/romkatv/gitstatus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248388027,"owners_count":21095326,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","git","zsh"],"created_at":"2024-07-30T19:00:39.177Z","updated_at":"2025-04-11T11:46:06.407Z","avatar_url":"https://github.com/romkatv.png","language":"C++","readme":"# gitstatus\n\n- **THE PROJECT HAS VERY LIMITED SUPPORT**\n- **NO NEW FEATURES ARE IN THE WORKS**\n- **MOST BUGS WILL GO UNFIXED**\n\n**gitstatus** is a 10x faster alternative to `git status` and `git describe`. Its primary use\ncase is to enable fast git prompt in interactive shells.\n\nHeavy lifting is done by **gitstatusd** -- a custom binary written in C++. It comes with Zsh and\nBash bindings for integration with shell.\n\n## Table of Contents\n\n1. [Using from Zsh](#using-from-zsh)\n1. [Using from Bash](#using-from-bash)\n2. [Using from other shells](#using-from-other-shells)\n1. [How it works](#how-it-works)\n1. [Benchmarks](#benchmarks)\n1. [Why fast](#why-fast)\n1. [Requirements](#requirements)\n1. [Compiling](#compiling)\n1. [License](#license)\n\n## Using from Zsh\n\nThe easiest way to take advantage of gitstatus from Zsh is to use a theme that's already integrated\nwith it. For example, [Powerlevel10k](https://github.com/romkatv/powerlevel10k) is a flexible and\nfast theme with first-class gitstatus integration. If you install Powerlevel10k, you don't need to\ninstall gitstatus.\n\n![Powerlevel10k Zsh Theme](\n  https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/prompt-styles-high-contrast.png)\n\nFor those who wish to use gitstatus without a theme, there is\n[gitstatus.prompt.zsh](gitstatus.prompt.zsh). Install it as follows:\n\n```zsh\ngit clone --depth=1 https://github.com/romkatv/gitstatus.git ~/gitstatus\necho 'source ~/gitstatus/gitstatus.prompt.zsh' \u003e\u003e! ~/.zshrc\n```\n\nUsers in China can use the official mirror on gitee.com for faster download.\u003cbr\u003e\n中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.\n\n```zsh\ngit clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus\necho 'source ~/gitstatus/gitstatus.prompt.zsh' \u003e\u003e! ~/.zshrc\n```\n\nAlternatively, if you have Homebrew installed:\n\n```zsh\nbrew install romkatv/gitstatus/gitstatus\necho \"source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.zsh\" \u003e\u003e! ~/.zshrc\n```\n\n(If you choose this option, replace `~/gitstatus` with `$(brew --prefix)/opt/gitstatus/gitstatus`\nin all code snippets below.)\n\n_Make sure to disable your current theme if you have one._\n\nThis will give you a basic yet functional prompt with git status in it. It's\n[over 10x faster](#benchmarks) than any alternative that can give you comparable prompt. In order\nto customize it, set `PROMPT` and/or `RPROMPT` at the end of `~/.zshrc` after sourcing\n`gitstatus.prompt.zsh`. Insert `${GITSTATUS_PROMPT}` where you want git status to go. For example:\n\n```zsh\nsource ~/gitstatus/gitstatus.prompt.zsh\n\nPROMPT='%~%# '               # left prompt: directory followed by %/# (normal/root)\nRPROMPT='$GITSTATUS_PROMPT'  # right prompt: git status\n```\n\nThe expansion of `${GITSTATUS_PROMPT}` can contain the following bits:\n\n| segment     |  meaning                                              |\n|-------------|-------------------------------------------------------|\n| `master`    | current branch                                        |\n| `#v1`       | HEAD is tagged with `v1`; not shown when on a branch  |\n| `@5fc6fca4` | current commit; not shown when on a branch or tag     |\n| `⇣1`        | local branch is behind the remote by 1 commit         |\n| `⇡2`        | local branch is ahead of the remote by 2 commits      |\n| `⇠3`        | local branch is behind the push remote by 3 commits   |\n| `⇢4`        | local branch is ahead of the push remote by 4 commits |\n| `*5`        | there are 5 stashes                                   |\n| `merge`     | merge is in progress (could be some other action)     |\n| `~6`        | there are 6 merge conflicts                           |\n| `+7`        | there are 7 staged changes                            |\n| `!8`        | there are 8 unstaged changes                          |\n| `?9`        | there are 9 untracked files                           |\n\n`$GITSTATUS_PROMPT_LEN` tells you how long `$GITSTATUS_PROMPT` is when printed to the console.\n[gitstatus.prompt.zsh](gitstatus.prompt.zsh) has an example of using it to truncate the current\ndirectory.\n\nIf you'd like to change the format of git status, or want to have greater control over the\nprocess of assembling `PROMPT`, you can copy and modify parts of\n[gitstatus.prompt.zsh](gitstatus.prompt.zsh) instead of sourcing the script. Your `~/.zshrc`\nmight look something like this:\n\n```zsh\nsource ~/gitstatus/gitstatus.plugin.zsh\n\nfunction my_set_prompt() {\n  PROMPT='%~%# '\n  RPROMPT=''\n\n  if gitstatus_query MY \u0026\u0026 [[ $VCS_STATUS_RESULT == ok-sync ]]; then\n    RPROMPT=${${VCS_STATUS_LOCAL_BRANCH:-@${VCS_STATUS_COMMIT}}//\\%/%%}  # escape %\n    (( VCS_STATUS_NUM_STAGED    )) \u0026\u0026 RPROMPT+='+'\n    (( VCS_STATUS_NUM_UNSTAGED  )) \u0026\u0026 RPROMPT+='!'\n    (( VCS_STATUS_NUM_UNTRACKED )) \u0026\u0026 RPROMPT+='?'\n  fi\n\n  setopt no_prompt_{bang,subst} prompt_percent  # enable/disable correct prompt expansions\n}\n\ngitstatus_stop 'MY' \u0026\u0026 gitstatus_start -s -1 -u -1 -c -1 -d -1 'MY'\nautoload -Uz add-zsh-hook\nadd-zsh-hook precmd my_set_prompt\n```\n\nThis snippet is sourcing `gitstatus.plugin.zsh` rather than `gitstatus.prompt.zsh`. The former\ndefines low-level bindings that communicate with gitstatusd over pipes. The latter is a simple\nscript that uses these bindings to assemble git prompt.\n\nUnlike [Powerlevel10k](https://github.com/romkatv/powerlevel10k), code based on\n[gitstatus.prompt.zsh](gitstatus.prompt.zsh) is communicating with gitstatusd synchronously. This\ncan make your prompt slow when working in a large git repository or on a slow machine. To avoid\nthis problem, call `gitstatus_query` asynchronously as documented in\n[gitstatus.plugin.zsh](gitstatus.plugin.zsh). This can be quite challenging.\n\n## Using from Bash\n\nThe easiest way to take advantage of gitstatus from Bash is via\n[gitstatus.prompt.sh](gitstatus.prompt.sh). Install it as follows:\n\n```bash\ngit clone --depth=1 https://github.com/romkatv/gitstatus.git ~/gitstatus\necho 'source ~/gitstatus/gitstatus.prompt.sh' \u003e\u003e ~/.bashrc\n```\n\nUsers in China can use the official mirror on gitee.com for faster download.\u003cbr\u003e\n中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.\n\n```bash\ngit clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus\necho 'source ~/gitstatus/gitstatus.prompt.sh' \u003e\u003e ~/.bashrc\n```\n\nAlternatively, if you have Homebrew installed:\n\n```zsh\nbrew install romkatv/gitstatus/gitstatus\necho \"source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.sh\" \u003e\u003e ~/.bashrc\n```\n\n(If you choose this option, replace `~/gitstatus` with `$(brew --prefix)/opt/gitstatus/gitstatus`\nin all code snippets below.)\n\nThis will give you a basic yet functional prompt with git status in it. It's\n[over 10x faster](#benchmarks) than any alternative that can give you comparable prompt.\n\n![Bash Prompt with GitStatus](\n  https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/bash-prompt.png)\n\nIn order to customize your prompt, set `PS1` at the end of `~/.bashrc` after sourcing\n`gitstatus.prompt.sh`. Insert `${GITSTATUS_PROMPT}` where you want git status to go. For example:\n\n```bash\nsource ~/gitstatus/gitstatus.prompt.sh\n\nPS1='\\w ${GITSTATUS_PROMPT}\\n\\$ ' # directory followed by git status and $/# (normal/root)\n```\n\nThe expansion of `${GITSTATUS_PROMPT}` can contain the following bits:\n\n| segment     |  meaning                                              |\n|-------------|-------------------------------------------------------|\n| `master`    | current branch                                        |\n| `#v1`       | HEAD is tagged with `v1`; not shown when on a branch  |\n| `@5fc6fca4` | current commit; not shown when on a branch or tag     |\n| `⇣1`        | local branch is behind the remote by 1 commit         |\n| `⇡2`        | local branch is ahead of the remote by 2 commits      |\n| `⇠3`        | local branch is behind the push remote by 3 commits   |\n| `⇢4`        | local branch is ahead of the push remote by 4 commits |\n| `*5`        | there are 5 stashes                                   |\n| `merge`     | merge is in progress (could be some other action)     |\n| `~6`        | there are 6 merge conflicts                           |\n| `+7`        | there are 7 staged changes                            |\n| `!8`        | there are 8 unstaged changes                          |\n| `?9`        | there are 9 untracked files                           |\n\nIf you'd like to change the format of git status, or want to have greater control over the\nprocess of assembling `PS1`, you can copy and modify parts of\n[gitstatus.prompt.sh](gitstatus.prompt.sh) instead of sourcing the script. Your `~/.bashrc` might\nlook something like this:\n\n```bash\nsource ~/gitstatus/gitstatus.plugin.sh\n\nfunction my_set_prompt() {\n  PS1='\\w'\n\n  if gitstatus_query \u0026\u0026 [[ \"$VCS_STATUS_RESULT\" == ok-sync ]]; then\n    if [[ -n \"$VCS_STATUS_LOCAL_BRANCH\" ]]; then\n      PS1+=\" ${VCS_STATUS_LOCAL_BRANCH//\\\\/\\\\\\\\}\"  # escape backslash\n    else\n      PS1+=\" @${VCS_STATUS_COMMIT//\\\\/\\\\\\\\}\"       # escape backslash\n    fi\n    (( VCS_STATUS_HAS_STAGED\"    )) \u0026\u0026 PS1+='+'\n    (( VCS_STATUS_HAS_UNSTAGED\"  )) \u0026\u0026 PS1+='!'\n    (( VCS_STATUS_HAS_UNTRACKED\" )) \u0026\u0026 PS1+='?'\n  fi\n\n  PS1+='\\n\\$ '\n\n  shopt -u promptvars  # disable expansion of '$(...)' and the like\n}\n\ngitstatus_stop \u0026\u0026 gitstatus_start\nPROMPT_COMMAND=my_set_prompt\n```\n\nThis snippet is sourcing `gitstatus.plugin.sh` rather than `gitstatus.prompt.sh`. The former\ndefines low-level bindings that communicate with gitstatusd over pipes. The latter is a simple\nscript that uses these bindings to assemble git prompt.\n\nNote: Bash bindings, unlike Zsh bindings, don't support asynchronous calls.\n\n## Using from other shells\n\nIf there are no gitstatusd bindings for your shell, you'll need to get your hands dirty.\nUse the existing bindings for inspiration; run `gitstatusd --help` or read the same thing in\n[options.cc](src/options.cc).\n\n## How it works\n\ngitstatusd reads requests from stdin and prints responses to stdout. Requests contain an ID and\na directory. Responses contain the same ID and machine-readable git status for the directory.\ngitstatusd keeps some state in memory for the directories it has seen in order to serve future\nrequests faster.\n\n[Zsh bindings](gitstatus.plugin.zsh) and [Bash bindings](gitstatus.plugin.sh) start gitstatusd in\nthe background and communicate with it via pipes. Themes such as\n[Powerlevel10k](https://github.com/romkatv/powerlevel10k) use these bindings to put git status in\n`PROMPT`.\n\nNote that gitstatus cannot be used as a drop-in replacement for `git status` command as it doesn't\nproduce output in the same format. It does perform the same computation though.\n\n## Benchmarks\n\nThe following benchmark results were obtained on Intel i9-7900X running Ubuntu 18.04 in\na clean [chromium](https://github.com/chromium/chromium) repository synced to `9394e49a`. The\nrepository was checked out to an ext4 filesystem on M.2 SSD.\n\nThree functionally equivalent tools for computing git status were benchmarked:\n\n* `gitstatusd`\n* `git` with `core.untrackedcache` enabled and `core.fsmonitor` disabled\n* `lg2` -- a demo/example executable from [libgit2](https://github.com/romkatv/libgit2) that\n  implements a subset of `git` functionality on top of libgit2 API; for the purposes of this\n  benchmark the subset is sufficient to generate the same data as the other tools\n\nEvery tool was benchmark in cold and hot conditions. For `git` the first run in a repository was\nconsidered cold, with the following runs considered hot. `lg2` was patched to compute results twice\nin a single invocation without freeing the repository in between; the second run was considered hot.\nThe same patching was not done for `git` because `git` cannot be easily modified to refresh inmemory\nindex state between invocations; in fact, this limitation is one of the primary reasons developers\nuse libgit2. `gitstatusd` was benchmarked similarly to `lg2` with two result computations in the\nsame invocation.\n\nTwo commands were benchmarked: `status` and `describe`.\n\n### Status\n\nIn this benchmark all tools were computing the equivalent of `git status`. Lower numbers are better.\n\n| Tool          |      Cold  |         Hot |\n|---------------|-----------:|------------:|\n| **gitstatus** | **291 ms** | **30.9 ms** |\n| git           |     876 ms |      295 ms |\n| lg2           |    1730 ms |     1310 ms |\n\ngitstatusd is substantially faster than the alternatives, especially on hot runs. Note that hot runs\nare of primary importance to the main use case of gitstatus in interactive shells.\n\nThe performance of `git status` fluctuated wildly in this benchmarks for reasons unknown to the\nauthor. Moreover, performance is sticky -- once `git status` settles around a number, it stays\nthere for a long time. Numbers as diverse as 295, 352, 663 and 730 had been observed on hot runs on\nthe same repository. The number in the table is the lowest (fastest or best) that `git status` had\nshown.\n\n### Describe\n\nIn this benchmark all tools were computing the equivalent of `git describe --tags --exact-match`\nto find tags that resolve to the same commit as `HEAD`. Lower numbers are better.\n\n| Tool          |       Cold  |           Hot |\n|---------------|------------:|--------------:|\n| **gitstatus** | **4.04 ms** | **0.0345 ms** |\n| git           |     18.0 ms |       14.5 ms |\n| lg2           |      185 ms |       45.2 ms |\n\ngitstatusd is once again faster than the alternatives, more so on hot runs.\n\n## Why fast\n\nSince gitstatusd doesn't have to print all staged/unstaged/untracked files but only report\nwhether there are any, it can terminate repository scan early. It can also remember which files\nwere dirty on the previous run and check them first on the next run to avoid the scan entirely if\nthe files are still dirty. However, the benchmarks above were performed in a clean repository where\nthese shortcuts do not trigger. All benchmarked tools had to do the same work -- check the status\nof every file in the index to see if it has changed, check every directory for newly created files,\netc. And yet, gitstatusd came ahead by a large margin. This section describes what it does that\nmakes it so fast.\n\nMost of the following comparisons are done against libgit2 rather than git because of the author's\nfamiliarity with the former but not the with latter. libgit2 has clean, well-documented APIs and an\nelegant implementation, which makes it so much easier to work with and to analyze performance\nbottlenecks.\n\n### Summary for the impatient\n\nUnder the benchmark conditions described above, the equivalent of libgit2's\n`git_diff_index_to_workdir` (the most expensive part of `status` command) is 46.3 times faster in\ngitstatusd. The speedup comes from the following sources.\n\n* gitstatusd uses more efficient data structures and algorithms and employs performance-conscious\ncoding style throughout the codebase. This reduces CPU time in userspace by 32x compared to libgit2.\n* gitstatusd uses less expensive system calls and makes fewer of them. This reduces CPU time spent\nin kernel by 1.9x.\n* gitstatusd can utilize multiple cores to scan index and workdir in parallel with almost perfect\nscaling. This reduces total run time by 12.4x while having virtually no effect on total CPU time.\n\n### Problem statement\n\nThe most resource-intensive part of the `status` command is finding the difference between _index_\nand _workdir_ (`git_diff_index_to_workdir` in libgit2). Index is a list of all files in the git\nrepository with their last modification times. This is an obvious simplification but it suffices for\nthis exposition. On disk, index is stored sorted by file path. Here's an example of git index:\n\n| File        | Last modification time |\n|-------------|-----------------------:|\n| Makefile    |   2019-04-01T14:12:32Z |\n| src/hello.c |   2019-04-01T14:12:00Z |\n| src/hello.h |   2019-04-01T14:12:32Z |\n\nThis list needs to be compared to the list of files in the working directory. If any of the files\nlisted in the index are missing from the workdir or have different last modification time, they are\n\"unstaged\" in gitstatusd parlance. If you run `git status`, they'll be shown as \"changes not staged\nfor commit\". Thus, any implementation of `status` command has to call `stat()` or one of its\nvariants on every file in the index.\n\nIn addition, all files in the working directory for which there is no entry in the index at all are\n\"untracked\". `git status` will show them as \"untracked files\". Finding untracked files requires some\nform of work directory traversal.\n\n### Single-threaded scan\n\nLet's see how `git_diff_index_to_workdir` from libgit2 accomplishes these tasks. Here's its CPU\nprofile from 200 hot runs over chromium repository.\n\n![libgit2 CPU profile (hot)](\n  https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-libgit2.png)\n\n(The CPU profile was created with [gperftools](https://github.com/gperftools/gperftools) and\nrendered with [pprof](https://github.com/google/pprof)).\n\nWe can see `__GI__lxstat` taking a lot of time. This is the `stat()` call for every file in the\nindex. We can also identify `__opendir`, `__readdir` and `__GI___close_nocancel` -- glibc wrappers\nfor reading the contents of a directory. This is for finding untracked files. Out of the total 232\nseconds, 111 seconds -- or 47.7% -- was spent on these calls. The rest is computation -- comparing\nstrings, sorting arrays, etc.\n\nNow let's take a look at the CPU profile of gitstatusd on the same task.\n\n![gitstatusd CPU profile (hot)](\n  https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-gitstatusd-hot.png)\n\nThe first impression is that this profile looks pruned. This isn't an artifact. The profile was\ngenerated with the same tools and the same flags as the profile of libgit2.\n\nSince both profiles were generated from the same workload, absolute numbers can be compared. We can\nsee that gitstatusd took 62 seconds in total compared to libgit2's 232 seconds. System calls at the\ncore of the algorithm are clearly visible. `__GI___fxstatat` is a flavor of `stat()`, and the other\nthree calls -- `__libc_openat64`, `__libc_close` and `__GI___fxstat` are responsible for opening\ndirectories and finding untracked files. Notice that there is almost nothing else in the profile\napart from these calls. The rest of the code accounts for 3.77 seconds of CPU time -- 32 times less\nthan in libgit2.\n\nSo, one reason gitstatusd is fast is that it has efficient diffing code -- very little time is spent\noutside of kernel. However, if we look closely, we can notice that system calls in gitstatusd are\n_also_ faster than in libgit2. For example, libgit2 spent 72.07 seconds in `__GI__lxstat` while\ngitstatusd spent only 48.82 seconds in `__GI___fxstatat`. There are two reasons for this difference.\nFirst, libgit2 makes more `stat()` calls than is strictly required. It's not necessary to stat\ndirectories because index only has files. There are 25k directories in chromium repository (and 300k\nfiles) -- that's 25k `stat()` calls that could be avoided. The second reason is that libgit2 and\ngitstatusd use different flavors of `stat()`. libgit2 uses `lstat()`, which takes a path to the file\nas input. Its performance is linear in the number of subdirectories in the path because it needs to\nperform a lookup for every one of them and to check permissions. gitstatusd uses `fstatat()`, which\ntakes a file descriptor to the parent directory and a name of the file. Just a single lookup, less\nCPU time.\n\nSimilarly to `lstat()` vs `fstatat()`, it's faster to open files and directories with `openat()`\nfrom the parent directory file descriptor than with regular `open()` that accepts full file path.\ngitstatusd takes advantage of `openat()` to open directories as fast as possible. It opens about 90%\nof the directories (this depends on the actual directory structure of the repository) from the\nimmediate parent -- the most efficient way -- and the remaining 10% it opens from the repository's\nroot directory. The reason it's done this way is to keep the maximum number of simultaneously open\nfile descriptors bounded. libgit2 can have O(repository depth) simultaneously open file descriptors,\nwhich may be OK for a single-threaded application but can balloon to a large number when scans are\ndone by many threads simultaneously, like in gitstatusd.\n\nThere is no equivalent to `__opendir` or `__readdir` in the gitstatusd profile because it uses the\nequivalent of [untracked cache](https://git-scm.com/docs/git-update-index#_untracked_cache) from\ngit. On the first scan of the workdir gitstatusd lists all files just like libgit2. But, unlike\nlibgit2, it remembers the last modification time of every directory along with the list of\nuntracked files under it. On the next scan, gitstatusd can skip listing files in directories whose\nlast modification time hasn't changed.\n\nTo summarize, here's what gitstatusd was doing when the CPU profile was captured:\n\n1. `__libc_openat64`: Open every directory for which there are files in the index.\n2. `__GI___fxstat`: Check last modification time of the directory. Since it's the same as on the\n   last scan, this directory has the same list of untracked files as before, which is empty (the\n   repository is clean).\n3. `__GI___fxstatat`: Check last modification time for every file in the index that belongs to this\n   directory.\n4. `__libc_close`: Close the file descriptor to the directory.\n\nHere's how the very first scan of a repository looks like in gitstatusd:\n\n![gitstatusd CPU profile (cold)](\n  https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-gitstatusd-cold.png)\n\n(Some glibc functions are mislabel on this profile. `explicit_bzero` and `__nss_passwd_lookup` are\nin reality `strcmp` and `memcmp`.)\n\nThis is a superset of the previous -- hot -- profile, with an extra `syscall` and string sorting for\ndirectory listing. gitstatusd uses `getdents64` Linux system call directly, bypassing the glibc\nwrapper that libgit2 uses. This is 23% faster. The details of this optimization can be found in a\n[separate document](docs/listdir.md).\n\n### Multithreading\n\nThe diffing algorithm in gitstatusd was designed from the ground up with the intention of using it\nconcurrently from multiple threads. With a fast SSD, `status` is CPU bound, so taking advantage of\nall available CPU cores is an obvious way to yield results faster.\n\ngitstatusd exhibits almost perfect scaling from multithreading. Engaging all cores allows it to\nproduce results 12.4 times faster than in single-threaded execution. This is on Intel i9-7900X with\n10 cores (20 with hyperthreading) with single-core frequency of 4.3GHz and all-core frequency of\n4.0GHz.\n\nNote: `git status` also uses all available cores in some parts of its algorithm while `lg2` does\neverything in a single thread.\n\n### Postprocessing\n\nOnce the difference between the index and the workdir is found, we have a list of _candidates_ --\nfiles that may be unstaged or untracked. To make the final judgement, these files need to be checked\nagainst `.gitignore` rules and a few other things.\n\ngitstatusd uses [patched libgit2](https://github.com/romkatv/libgit2) for this step. This fork\nadds several optimizations that make libgit2 faster. The patched libgit2 performs more than twice\nas fast in the benchmark as the original even without changes in the user code (that is, in the\ncode that uses the libgit2 APIs). The fork also adds several API extensions, most notable of which\nis the support for multi-threaded scans. If `lg2 status` is modified to take advantage of these\nextensions, it outperforms the original libgit2 by a factor of 18. Lastly, the fork fixes a score of\nbugs, most of which become apparent only when using libgit2 from multiple threads.\n\n_WARNING: Changes to libgit2 are extensive but the testing they underwent isn't. It is\n**not recommended** to use the patched libgit2 in production._\n\n## Requirements\n\n* To compile: binutils, cmake, gcc, g++, git and GNU make.\n* To run: Linux, macOS, FreeBSD, Android, WSL, Cygwin or MSYS2.\n\n## Compiling\n\nThere are prebuilt `gitstatusd` binaries in [releases](\n  https://github.com/romkatv/gitstatus/releases). When using the official shell bindings\nprovided by gitstatus, the right binary for your architecture gets downloaded automatically.\n\nIf prebuilt binaries don't work for you, you'll need to get your hands dirty.\n\n### Compiling for personal use\n\n```zsh\ngit clone --depth=1 https://github.com/romkatv/gitstatus.git\ncd gitstatus\n./build -w -s -d docker\n```\n\nUsers in China can use the official mirror on gitee.com for faster download.\u003cbr\u003e\n中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.\n\n```zsh\ngit clone --depth=1 https://gitee.com/romkatv/gitstatus.git\ncd gitstatus\n./build -w -s -d docker\n```\n\n- If it says that `-d docker` is not supported on your OS, remove this flag.\n- If it says that `-s` is not supported on your OS, remove this flag.\n- If it tell you to install docker but you cannot or don't want to, remove `-d docker`.\n- If it says that some command is missing, install it.\n\nIf everything goes well, the newly built binary will appear in `./usrbin`. It'll be picked up\nby shell bindings automatically.\n\nWhen you update shell bindings, they may refuse to work with the binary you've built earlier. In\nthis case you'll need to rebuild.\n\nIf you are using gitstatus through [Powerlevel10k](https://github.com/romkatv/powerlevel10k), the\ninstructions are the same except that you don't need to clone gitstatus. Instead, change your\ncurrent directory to `/path/to/powerlevel10k/gitstatus` (`/path/to/powerlevel10k` is the directory\nwhere you've installed Powerlevel10k) and run `./build -w -s -d docker` from there as described\nabove.\n\n### Compiling for distribution\n\nIt's currently neither easy nor recommended to package and distribute gitstatus. There are no\ninstructions you can follow that would allow you to easily update your package when new versions of\ngitstatus are released. This may change in the future but not soon.\n\n## License\n\nGNU General Public License v3.0. See [LICENSE](LICENSE). Contributions are covered by the same\nlicense.\n","funding_links":[],"categories":["C++","\u003ca name=\"cpp\"\u003e\u003c/a\u003eC++","Shell"],"sub_categories":["Rust tools"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromkatv%2Fgitstatus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fromkatv%2Fgitstatus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fromkatv%2Fgitstatus/lists"}