gitstatus

Git status for Bash and Zsh prompt
授权协议 GPL-3.0 License
开发语言 SHELL
所属分类 应用工具、 终端/远程登录
软件类型 开源软件
地区 不详
投 递 者 单于山
操作系统 跨平台
开源组织
适用人群 未知
 软件概览

gitstatus

gitstatus is a 10x faster alternative to git status and git describe. Its primary usecase is to enable fast git prompt in interactive shells.

Heavy lifting is done by gitstatusd -- a custom binary written in C++. It comes with Zsh andBash bindings for integration with shell.

Table of Contents

  1. Using from Zsh
  2. Using from Bash
  3. Using from other shells
  4. How it works
  5. Benchmarks
  6. Why fast
  7. Requirements
  8. Compiling
  9. License

Using from Zsh

The easiest way to take advantage of gitstatus from Zsh is to use a theme that's already integratedwith it. For example, Powerlevel10k is a flexible andfast theme with first-class gitstatus integration.

Powerlevel10k Zsh Theme

For those who wish to use gitstatus without a theme, there isgitstatus.prompt.zsh. Install it as follows:

git clone --depth=1 https://github.com/romkatv/gitstatus.git ~/gitstatus
echo 'source ~/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc

Users in mainland China can use the official mirror on gitee.com for faster download.
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.

git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus
echo 'source ~/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc

Alternatively, if you have Homebrew installed:

brew install romkatv/gitstatus/gitstatus
echo "source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.zsh" >>! ~/.zshrc

(If you choose this option, replace ~/gitstatus with $(brew --prefix)/opt/gitstatus/gitstatusin all code snippets below.)

Make sure to disable your current theme if you have one.

This will give you a basic yet functional prompt with git status in it. It'sover 10x faster than any alternative that can give you comparable prompt. In orderto customize it, set PROMPT and/or RPROMPT at the end of ~/.zshrc after sourcinggitstatus.prompt.zsh. Insert ${GITSTATUS_PROMPT} where you want git status to go. For example:

source ~/gitstatus/gitstatus.prompt.zsh

PROMPT='%~%# '               # left prompt: directory followed by %/# (normal/root)
RPROMPT='$GITSTATUS_PROMPT'  # right prompt: git status

The expansion of ${GITSTATUS_PROMPT} can contain the following bits:

segment meaning
master current branch
#v1 HEAD is tagged with v1; not shown when on a branch
@5fc6fca4 current commit; not shown when on a branch or tag
⇣1 local branch is behind the remote by 1 commit
⇡2 local branch is ahead of the remote by 2 commits
⇠3 local branch is behind the push remote by 3 commits
⇢4 local branch is ahead of the push remote by 4 commits
*5 there are 5 stashes
merge merge is in progress (could be some other action)
~6 there are 6 merge conflicts
+7 there are 7 staged changes
!8 there are 8 unstaged changes
?9 there are 9 untracked files

$GITSTATUS_PROMPT_LEN tells you how long $GITSTATUS_PROMPT is when printed to the console.gitstatus.prompt.zsh has an example of using it to truncate the currentdirectory.

If you'd like to change the format of git status, or want to have greater control over theprocess of assembling PROMPT, you can copy and modify parts ofgitstatus.prompt.zsh instead of sourcing the script. Your ~/.zshrcmight look something like this:

source ~/gitstatus/gitstatus.plugin.zsh

function my_set_prompt() {
  PROMPT='%~%# '
  RPROMPT=''

  if gitstatus_query MY && [[ $VCS_STATUS_RESULT == ok-sync ]]; then
    RPROMPT=${${VCS_STATUS_LOCAL_BRANCH:-@${VCS_STATUS_COMMIT}}//\%/%%}  # escape %
    (( VCS_STATUS_NUM_STAGED    )) && RPROMPT+='+'
    (( VCS_STATUS_NUM_UNSTAGED  )) && RPROMPT+='!'
    (( VCS_STATUS_NUM_UNTRACKED )) && RPROMPT+='?'
  fi

  setopt no_prompt_{bang,subst} prompt_percent  # enable/disable correct prompt expansions
}

gitstatus_stop 'MY' && gitstatus_start -s -1 -u -1 -c -1 -d -1 'MY'
autoload -Uz add-zsh-hook
add-zsh-hook precmd my_set_prompt

This snippet is sourcing gitstatus.plugin.zsh rather than gitstatus.prompt.zsh. The formerdefines low-level bindings that communicate with gitstatusd over pipes. The latter is a simplescript that uses these bindings to assemble git prompt.

Unlike Powerlevel10k, code based ongitstatus.prompt.zsh is communicating with gitstatusd synchronously. Thiscan make your prompt slow when working in a large git repository or on a slow machine. To avoidthis problem, call gitstatus_query asynchronously as documented ingitstatus.plugin.zsh. This can be quite challenging.

Using from Bash

The easiest way to take advantage of gitstatus from Bash is viagitstatus.prompt.sh. Install it as follows:

git clone --depth=1 https://github.com/romkatv/gitstatus.git ~/gitstatus
echo 'source ~/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc

Users in mainland China can use the official mirror on gitee.com for faster download.
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.

git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus
echo 'source ~/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc

Alternatively, if you have Homebrew installed:

brew install romkatv/gitstatus/gitstatus
echo "source $(brew --prefix)/opt/gitstatus/gitstatus.prompt.sh" >> ~/.bashrc

(If you choose this option, replace ~/gitstatus with $(brew --prefix)/opt/gitstatus/gitstatusin all code snippets below.)

This will give you a basic yet functional prompt with git status in it. It'sover 10x faster than any alternative that can give you comparable prompt.

Bash Prompt with GitStatus

In order to customize your prompt, set PS1 at the end of ~/.bashrc after sourcinggitstatus.prompt.sh. Insert ${GITSTATUS_PROMPT} where you want git status to go. For example:

source ~/gitstatus/gitstatus.prompt.sh

PS1='\w ${GITSTATUS_PROMPT}\n\$ ' # directory followed by git status and $/# (normal/root)

The expansion of ${GITSTATUS_PROMPT} can contain the following bits:

segment meaning
master current branch
#v1 HEAD is tagged with v1; not shown when on a branch
@5fc6fca4 current commit; not shown when on a branch or tag
⇣1 local branch is behind the remote by 1 commit
⇡2 local branch is ahead of the remote by 2 commits
⇠3 local branch is behind the push remote by 3 commits
⇢4 local branch is ahead of the push remote by 4 commits
*5 there are 5 stashes
merge merge is in progress (could be some other action)
~6 there are 6 merge conflicts
+7 there are 7 staged changes
!8 there are 8 unstaged changes
?9 there are 9 untracked files

If you'd like to change the format of git status, or want to have greater control over theprocess of assembling PS1, you can copy and modify parts ofgitstatus.prompt.sh instead of sourcing the script. Your ~/.bashrc mightlook something like this:

source ~/gitstatus/gitstatus.plugin.sh

function my_set_prompt() {
  PS1='\w'

  if gitstatus_query && [[ "$VCS_STATUS_RESULT" == ok-sync ]]; then
    if [[ -n "$VCS_STATUS_LOCAL_BRANCH" ]]; then
      PS1+=" ${VCS_STATUS_LOCAL_BRANCH//\\/\\\\}"  # escape backslash
    else
      PS1+=" @${VCS_STATUS_COMMIT//\\/\\\\}"       # escape backslash
    fi
    (( VCS_STATUS_HAS_STAGED"    )) && PS1+='+'
    (( VCS_STATUS_HAS_UNSTAGED"  )) && PS1+='!'
    (( VCS_STATUS_HAS_UNTRACKED" )) && PS1+='?'
  fi

  PS1+='\n\$ '

  shopt -u promptvars  # disable expansion of '$(...)' and the like
}

gitstatus_stop && gitstatus_start
PROMPT_COMMAND=my_set_prompt

This snippet is sourcing gitstatus.plugin.sh rather than gitstatus.prompt.sh. The formerdefines low-level bindings that communicate with gitstatusd over pipes. The latter is a simplescript that uses these bindings to assemble git prompt.

Note: Bash bindings, unlike Zsh bindings, don't support asynchronous calls.

Using from other shells

If there are no gitstatusd bindings for your shell, you'll need to get your hands dirty.Use the existing bindings for inspiration; run gitstatusd --help or read the same thing inoptions.cc.

How it works

gitstatusd reads requests from stdin and prints responses to stdout. Requests contain an ID anda directory. Responses contain the same ID and machine-readable git status for the directory.gitstatusd keeps some state in memory for the directories it has seen in order to serve futurerequests faster.

Zsh bindings and Bash bindings start gitstatusd inthe background and communicate with it via pipes. Themes such asPowerlevel10k use these bindings to put git status inPROMPT.

Note that gitstatus cannot be used as a drop-in replacement for git status command as it doesn'tproduce output in the same format. It does perform the same computation though.

Benchmarks

The following benchmark results were obtained on Intel i9-7900X running Ubuntu 18.04 ina clean chromium repository synced to 9394e49a. Therepository was checked out to an ext4 filesystem on M.2 SSD.

Three functionally equivalent tools for computing git status were benchmarked:

  • gitstatusd
  • git with untracked cache enabled
  • lg2 -- a demo/example executable from libgit2 thatimplements a subset of git functionality on top of libgit2 API; for the purposes of thisbenchmark the subset is sufficient to generate the same data as the other tools

Every tool was benchmark in cold and hot conditions. For git the first run in a repository wasconsidered cold, with the following runs considered hot. lg2 was patched to compute results twicein a single invocation without freeing the repository in between; the second run was considered hot.The same patching was not done for git because git cannot be easily modified to refresh inmemoryindex state between invocations; in fact, this limitation is one of the primary reasons developersuse libgit2. gitstatusd was benchmarked similarly to lg2 with two result computations in thesame invocation.

Two commands were benchmarked: status and describe.

Status

In this benchmark all tools were computing the equivalent of git status. Lower numbers are better.

Tool Cold Hot
gitstatus 291 ms 30.9 ms
git 876 ms 295 ms
lg2 1730 ms 1310 ms

gitstatusd is substantially faster than the alternatives, especially on hot runs. Note that hot runsare of primary importance to the main use case of gitstatus in interactive shells.

The performance of git status fluctuated wildly in this benchmarks for reasons unknown to theauthor. Moreover, performance is sticky -- once git status settles around a number, it staysthere for a long time. Numbers as diverse as 295, 352, 663 and 730 had been observed on hot runs onthe same repository. The number in the table is the lowest (fastest or best) that git status hadshown.

Describe

In this benchmark all tools were computing the equivalent of git describe --tags --exact-matchto find tags that resolve to the same commit as HEAD. Lower numbers are better.

Tool Cold Hot
gitstatus 4.04 ms 0.0345 ms
git 18.0 ms 14.5 ms
lg2 185 ms 45.2 ms

gitstatusd is once again faster than the alternatives, more so on hot runs.

Why fast

Since gitstatusd doesn't have to print all staged/unstaged/untracked files but only reportwhether there are any, it can terminate repository scan early. It can also remember which fileswere dirty on the previous run and check them first on the next run to avoid the scan entirely ifthe files are still dirty. However, the benchmarks above were performed in a clean repository wherethese shortcuts do not trigger. All benchmarked tools had to do the same work -- check the statusof every file in the index to see if it has changed, check every directory for newly created files,etc. And yet, gitstatusd came ahead by a large margin. This section describes what it does thatmakes it so fast.

Most of the following comparisons are done against libgit2 rather than git because of the author'sfamiliarity with the former but not the with latter. libgit2 has clean, well-documented APIs and anelegant implementation, which makes it so much easier to work with and to analyze performancebottlenecks.

Summary for the impatient

Under the benchmark conditions described above, the equivalent of libgit2'sgit_diff_index_to_workdir (the most expensive part of status command) is 46.3 times faster ingitstatusd. The speedup comes from the following sources.

  • gitstatusd uses more efficient data structures and algorithms and employs performance-consciouscoding style throughout the codebase. This reduces CPU time in userspace by 32x compared to libgit2.
  • gitstatusd uses less expensive system calls and makes fewer of them. This reduces CPU time spentin kernel by 1.9x.
  • gitstatusd can utilize multiple cores to scan index and workdir in parallel with almost perfectscaling. This reduces total run time by 12.4x while having virtually no effect on total CPU time.

Problem statement

The most resource-intensive part of the status command is finding the difference between indexand workdir (git_diff_index_to_workdir in libgit2). Index is a list of all files in the gitrepository with their last modification times. This is an obvious simplification but it suffices forthis exposition. On disk, index is stored sorted by file path. Here's an example of git index:

File Last modification time
Makefile 2019-04-01T14:12:32Z
src/hello.c 2019-04-01T14:12:00Z
src/hello.h 2019-04-01T14:12:32Z

This list needs to be compared to the list of files in the working directory. If any of the fileslisted in the index are missing from the workdir or have different last modification time, they are"unstaged" in gitstatusd parlance. If you run git status, they'll be shown as "changes not stagedfor commit". Thus, any implementation of status command has to call stat() or one of itsvariants on every file in the index.

In addition, all files in the working directory for which there is no entry in the index at all are"untracked". git status will show them as "untracked files". Finding untracked files requires someform of work directory traversal.

Single-threaded scan

Let's see how git_diff_index_to_workdir from libgit2 accomplishes these tasks. Here's its CPUprofile from 200 hot runs over chromium repository.

libgit2 CPU profile (hot)

(The CPU profile was created with gperftools andrendered with pprof).

We can see __GI__lxstat taking a lot of time. This is the stat() call for every file in theindex. We can also identify __opendir, __readdir and __GI___close_nocancel -- glibc wrappersfor reading the contents of a directory. This is for finding untracked files. Out of the total 232seconds, 111 seconds -- or 47.7% -- was spent on these calls. The rest is computation -- comparingstrings, sorting arrays, etc.

Now let's take a look at the CPU profile of gitstatusd on the same task.

gitstatusd CPU profile (hot)

The first impression is that this profile looks pruned. This isn't an artifact. The profile wasgenerated with the same tools and the same flags as the profile of libgit2.

Since both profiles were generated from the same workload, absolute numbers can be compared. We cansee that gitstatusd took 62 seconds in total compared to libgit2's 232 seconds. System calls at thecore of the algorithm are cleary visible. __GI___fxstatat is a flavor of stat(), and the otherthree calls -- __libc_openat64, __libc_close and __GI___fxstat are responsible for openingdirectories and finding untracked files. Notice that there is almost nothing else in the profileapart from these calls. The rest of the code accounts for 3.77 seconds of CPU time -- 32 times lessthan in libgit2.

So, one reason gitstatusd is fast is that it has efficient diffing code -- very little time is spentoutside of kernel. However, if we look closely, we can notice that system calls in gitstatusd arealso faster than in libgit2. For example, libgit2 spent 72.07 seconds in __GI__lxstat whilegitstatusd spent only 48.82 seconds in __GI___fxstatat. There are two reasons for this difference.First, libgit2 makes more stat() calls than is strictly required. It's not necessary to statdirectories because index only has files. There are 25k directories in chromium repository (and 300kfiles) -- that's 25k stat() calls that could be avoided. The second reason is that libgit2 andgitstatusd use different flavors of stat(). libgit2 uses lstat(), which takes a path to the fileas input. Its performance is linear in the number of subdirectories in the path because it needs toperform a lookup for every one of them and to check permissions. gitstatusd uses fstatat(), whichtakes a file descriptor to the parent directory and a name of the file. Just a single lookup, lessCPU time.

Similarly to lstat() vs fstatat(), it's faster to open files and directories with openat()from the parent directory file descriptor than with regular open() that accepts full file path.gitstatusd takes advantage of openat() to open directories as fast as possible. It opens about 90%of the directories (this depends on the actual directory structure of the repository) from theimmediate parent -- the most efficient way -- and the remaining 10% it opens from the repository'sroot directory. The reason it's done this way is to keep the maximum number of simultaneously openfile descriptors bounded. libgit2 can have O(repository depth) simultaneously open file descriptors,which may be OK for a single-threaded application but can balloon to a large number when scans aredone by many threads simultaneously, like in gitstatusd.

There is no equivalent to __opendir or __readdir in the gitstatusd profile because it uses theequivalent of untracked cache fromgit. On the first scan of the workdir gitstatusd lists all files just like libgit2. But, unlikelibgit2, it remembers the last modification time of every directory along with the list ofuntracked files under it. On the next scan, gitstatusd can skip listing files in directories whoselast modification time hasn't changed.

To summarize, here's what gitstatusd was doing when the CPU profile was captured:

  1. __libc_openat64: Open every directory for which there are files in the index.
  2. __GI___fxstat: Check last modification time of the directory. Since it's the same as on thelast scan, this directory has the same list of untracked files as before, which is empty (therepository is clean).
  3. __GI___fxstatat: Check last modification time for every file in the index that belongs to thisdirectory.
  4. __libc_close: Close the file descriptor to the directory.

Here's how the very first scan of a repository looks like in gitstatusd:

gitstatusd CPU profile (cold)

(Some glibc functions are mislabel on this profile. explicit_bzero and __nss_passwd_lookup arein reality strcmp and memcmp.)

This is a superset of the previous -- hot -- profile, with an extra syscall and string sorting fordirectory listing. gitstatusd uses getdents64 Linux system call directly, bypassing the glibcwrapper that libgit2 uses. This is 23% faster. The details of this optimization can be found in aseparate document.

Multithreading

The diffing algorithm in gitstatusd was designed from the ground up with the intention of using itconcurrently from multiple threads. With a fast SSD, status is CPU bound, so taking advantage ofall available CPU cores is an obvious way to yield results faster.

gitstatusd exhibits almost perfect scaling from multithreading. Engaging all cores allows it toproduce results 12.4 times faster than in single-threaded execution. This is on Intel i9-7900X with10 cores (20 with hyperthreading) with single-core frequency of 4.3GHz and all-core frequency of4.0GHz.

Note: git status also uses all available cores in some parts of its algorithm while lg2 doeseverything in a single thread.

Postprocessing

Once the difference between the index and the workdir is found, we have a list of candidates --files that may be unstaged or untracked. To make the final judgement, these files need to be checkedagainst .gitignore rules and a few other things.

gitstatusd uses patched libgit2 for this step. This forkadds several optimizations that make libgit2 faster. The patched libgit2 performs more than twiceas fast in the benchmark as the original even without changes in the user code (that is, in thecode that uses the libgit2 APIs). The fork also adds several API extensions, most notable of whichis the support for multi-threaded scans. If lg2 status is modified to take advantage of theseextensions, it outperforms the original libgit2 by a factor of 18. Lastly, the fork fixes a score ofbugs, most of which become apparent only when using libgit2 from multiple threads.

WARNING: Changes to libgit2 are extensive but the testing they underwent isn't. It isnot recommended to use the patched libgit2 in production.

Requirements

  • To compile: binutils, cmake, gcc, g++, git and GNU make.
  • To run: Linux, macOS, FreeBSD, Android, WSL, Cygwin or MSYS2.

Compiling

There are prebuilt gitstatusd binaries in releases. When using the official shell bindingsprovided by gitstatus, the right binary for your architecture gets downloaded automatically.

If prebuilt binaries don't work for you, you'll need to get your hands dirty.

Compiling for personal use

git clone --depth=1 https://github.com/romkatv/gitstatus.git
cd gitstatus
./build -w -s -d docker

Users in mainland China can use the official mirror on gitee.com for faster download.
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.

git clone --depth=1 https://gitee.com/romkatv/gitstatus.git
cd gitstatus
./build -w -s -d docker
  • If it says that -d docker is not supported on your OS, remove this flag.
  • If it says that -s is not supported on your OS, remove this flag.
  • If it tell you to install docker but you cannot or don't want to, remove -d docker.
  • If it says that some command is missing, install it.

If everything goes well, the newly built binary will appear in ./usrbin. It'll be picked upby shell bindings automatically.

When you update shell bindings, they may refuse to work with the binary you've built earlier. Inthis case you'll need to rebuild.

If you are using gitstatus through Powerlevel10k, theinstructions are the same except that you don't need to clone gitstatus. Instead, change yourcurrent directory to /path/to/powerlevel10k/gitstatus (/path/to/powerlevel10k is the directorywhere you've installed Powerlevel10k) and run ./build -w -s -d docker from there as describedabove.

Compiling for distribution

It's currently neither easy nor recommended to package and distribute gitstatus. There are noinstructions you can follow that would allow you to easily update your package when new versions ofgitstatus are released. This may change in the future but not soon.

License

GNU General Public License v3.0. See LICENSE. Contributions are covered by the samelicense.

  • git status 什么是git status 官方文档:https://git-scm.com/docs/git-status git status命令用于显示工作目录和暂存区的状态。使用此命令能看到那些修改被暂存到了, 哪些没有, 哪些文件没有被Git tracked到。 git status不显示已经commit到项目历史中去的信息。看项目历史的信息要使用git log. 总结:git s

  • git status --查看状态 git stash–将所有未提交的修改(工作区和暂存区)保存至堆栈中 git stash save "备注" --和2一样,可以加注释 git stash list–查看当前stash中的内容 git stash drop stash@{0}–删除第0条(栈顶的)那条内容 git stash clear–全部删除 git stash pop–恢复栈顶的内容到本分

  • Git语言 的 git status命令 git status命令用于显示工作目录和暂存区的状态。使用此命令能看到那些修改被暂存到了, 哪些没有, 哪些文件没有被Git tracked到。git status不显示已经commit到项目历史中去的信息。看项目历史的信息要使用git log. 简介 git status […] [--] […] 描述 显示索引文件和当前HEAD提交之间的差异,在工作

  • 1, 状态简览 git status 命令的输出十分详细,但其用语有些繁琐。 如果你使用 git status -s 命令或 git status --short 命令,你将得到一种更为紧凑的格式输出。 运行 git status -s ,状态报告输出如下: $ git status -s M README MM Rakefile A lib/git.rb M lib/simplegit.r

  • 项目场景: clone git中的仓库后,程序员对代码进行本地修改后上传 问题描述 其实主要是git commit 之后无法连接远程仓库,git push总是出错 出错提示是: remote: rejected by xxx remote: Push rejected. + 小熊 error 然后我就开始了找错误的环节 git status 后出现了 Changes not staged for

  • 当执行 git status 的时候,返回结果大致可分为3个部分: 拟提交的变更:这是已经放入暂存区,准备使用 git commit 命令提交的变更 未暂存的变更:这是工作目录和暂存区快照之间存在差异的文件列表 未跟踪的文件:这类文件对于 Git 系统来说是未知的,也是可以被忽略的 如果在 git status 命令后面加上 --ignored选项,还会列出被忽略的文件。 例如: $ git st

  • 转载于:https://coding.net/help/doc/git/push.html 情况1: 当下述指令没有生效,即文件没有被追踪 git add 文件名 则显示 On branch master Your branch is up-to-date with 'origin/master' nothing to commit, working directory clean 情况2: 当下

  • git config core.checkStat minimal core.checkStat Determines which stat fields to match between the index and work tree. The user can set this to default or minimal. Default (or explicitly default),

  • 前情提要:同时在维护三个20g+的库,每次用git和编译的时候都是一种折磨。同事教了我这两个方法。 1、ccache编译加速 2、cd /var/log 删掉syslog和 kern.log。清出来100多g磁盘空间,这电脑才用了三个月。电脑写log比我写日报还勤快。

  • Git查看工作区:git diff与git status。 git diff:用于比较项目中任意两个版本(分支)的差异,也可以用来比较当前的索引和上次提交间的差异。 比较两个节点之间的差异; 比较两个分支之间的差异; 当前的索引和上次提交间的差异 git diff --cached; 在diff后面加--name-status参数,只看文件列表。 git status:用于显示工作目录和暂存区的状

  • git status’ failed with code 128: 错误情况有好几种: 暂时遇到过的有效解决: (1)在终端中执行以下命令: rm .git/index

  • git status失败 错误代码128:error :bad signature 0x0000000 今天使用Sourcetree 报了这样的错 已解决: 第一步先删除 index rm -f .git/index 第二步重新创建 git reset OK

相关阅读

相关文章

相关问答

相关文档