Compare commits

...

461 Commits
v1.6.1 ... vcs

Author SHA1 Message Date
Roman Perepelitsa
78d4693970 vcs: wip 2021-10-16 10:31:41 +02:00
Roman Perepelitsa
6441a01dd3 survive non-writable $TTY 2021-10-14 14:38:27 +02:00
Roman Perepelitsa
edd98053cc disable icanon on the tty after printing instant prompt; this way keys like Ctrl-R and Ctrl-D will get through to zle 2021-10-10 10:34:54 +02:00
Roman Perepelitsa
5acedce0b0 speed up pre-redraw hook 2021-10-08 12:39:30 +02:00
Roman Perepelitsa
3e515a75d2 remove obsolete nordvpn comments 2021-10-06 13:04:28 +02:00
Roman Perepelitsa
ed45177e19 fix nordvpn; it was broken by the latest upstream update (#1590) 2021-10-06 12:51:19 +02:00
Charles Timko
0ce9df66d2 Add RHEL to the OS Icon lists (#1581)
* Add RHEL to the OS Icon lists
2021-09-22 19:49:39 +02:00
Roman Perepelitsa
7a72acf563 clean up font instructions for yakuake 2021-09-14 10:18:27 +02:00
Roman Perepelitsa
fcfeebfb53 Merge branch 'ilsteiner-patch-1' 2021-09-14 10:13:02 +02:00
Isaac Lebwohl-Steiner
0122a63834 Add instructions to manually set font on Yakuake 2021-09-13 18:25:55 -07:00
Roman Perepelitsa
543e2d59cf change formatting in the font installation instructions to make it more obvious where each step begins and ends 2021-09-13 10:31:34 +02:00
Roman Perepelitsa
0745592886 fix a spello in comments in the generated .p10k.zsh 2021-09-13 09:05:43 +02:00
Roman Perepelitsa
d2f78d4b29 Merge commit '20eb8c64bf2520e20739610acb503c8729867a5e' 2021-09-12 10:40:43 +02:00
Roman Perepelitsa
20eb8c64bf Squashed 'gitstatus/' changes from edd92f621..cd98a3c28
cd98a3c28 rebuild gitstatusd-freebsd-amd64 on freebsd 13
b03580cb0 rebuild gitstatusd-freebsd-amd64 (now with proper version)
3c95a47dc update version to v1.5.3
e3a3cd122 rebuild gitstatusd-freebsd-amd64 with clang (#262)
6aed2ae95 compile with clang on freebsd by default (#262)

git-subtree-dir: gitstatus
git-subtree-split: cd98a3c2849600982aa1b7d03f66784710106291
2021-09-12 10:40:43 +02:00
Roman Perepelitsa
4f3d2ffe72 new prompt segment: toolbox (https://github.com/containers/toolbox) #1560 2021-09-10 13:21:31 +02:00
Roman Perepelitsa
c5c9178341 update a link to visual-studio-code-font-settings.jpg (now with annotations) 2021-09-03 10:27:01 +02:00
Roman Perepelitsa
c2c3171927 extra verbose instructions for vscode font changes 2021-09-03 10:10:11 +02:00
Roman Perepelitsa
b5f4d27c74 add a screenshot of vscode font settings 2021-09-03 09:42:31 +02:00
Roman Perepelitsa
2c7241c43d add an extra step to the uninstallation instructions to delete cache files (#1561) 2021-09-02 13:47:29 +02:00
Roman Perepelitsa
b3b0efb69f work around bugs in terminals and window managers by essentially disabling prompt_sp (but keeping prompt_cr) in instant prompt for new TTYs 2021-08-28 10:14:37 +02:00
Roman Perepelitsa
10ad57cc6b Squashed 'gitstatus/' changes from 2ecd9907..edd92f62
edd92f62 build: more debug info in case wget fails
1cfcb46c build: add a workaround for systems with broken wget

git-subtree-dir: gitstatus
git-subtree-split: edd92f6210afb16411274e1d6060c6a68bd1e999
2021-08-23 10:30:28 +02:00
Roman Perepelitsa
e362b69735 Merge commit '10ad57cc6b73b9ed51474edbd5235e6b5f2fd16b' 2021-08-23 10:30:28 +02:00
Roman Perepelitsa
0a1946b965 bug fix: display the intended battery icon when using legacy configs (#1530) 2021-08-21 10:08:59 +02:00
elProxy
379b97e4e7 Fix after #1544 (#1547)
Well, that's embarassing...
2021-08-19 10:16:12 +02:00
elProxy
277ff8b414 Add support for AWS_REGION with fallback to AWS_DEFAULT_REGION (#1544)
See https://fossies.org/linux/aws-cli/CHANGELOG.rst#section-133
2021-08-19 00:27:05 -07:00
Roman Perepelitsa
80ec734a95 Squashed 'gitstatus/' changes from 845f492f..2ecd9907
2ecd9907 add logging to debug https://github.com/romkatv/powerlevel10k/issues/1477
74010456 add a TODO to fix #254
864f1caf Trim '\w' part of bash prompt. (#253)

git-subtree-dir: gitstatus
git-subtree-split: 2ecd990706255d2000fedbde3b2d2353f63d69a1
2021-08-18 17:05:04 +02:00
Roman Perepelitsa
ec44300155 Merge commit '80ec734a953d930838ea6839923c97c3da880a0d' 2021-08-18 17:05:04 +02:00
Roman Perepelitsa
25e5f5985f spello 2021-08-17 19:42:05 +02:00
Roman Perepelitsa
5669c12c66 Revert "add availability to display or not Terraform version"
This reverts commit 20b87731de.
2021-08-17 19:41:29 +02:00
Roman Perepelitsa
e7629449c6 spello 2021-08-17 19:41:15 +02:00
Roman Perepelitsa
ce7d4a4cd3 Merge branch 'ptavares-master' 2021-08-17 19:38:35 +02:00
Patrick Tavares
6aeb13b08a add terraform_version segment to the list of available segments 2021-08-16 18:47:40 +02:00
Patrick Tavares
20b87731de add availability to display or not Terraform version
Show Terraform version segment only when *.tf files are present in current directory
2021-08-16 18:46:46 +02:00
Eric Nielsen
32e76e7721 Update README.md (#1532)
with new changed from version 1.5.0 of Zim.
2021-08-10 19:03:29 +02:00
ahillio
46a3e51896 readme: make 2steps for oh-my-zsh installation explicit. (#1526) 2021-08-08 11:22:22 -07:00
Roman Perepelitsa
799c22f63b Squashed 'gitstatus/' changes from 1edd9e62..845f492f
845f492f build: target march armv8-a instead of armv8
cf21109b build: respect CXX environment variable

git-subtree-dir: gitstatus
git-subtree-split: 845f492f777af84e50b9a4820c7b08729c841f0c
2021-07-25 09:31:56 +02:00
Roman Perepelitsa
8f798f986a Merge commit '799c22f63b93e9d1ab8f01473bf9ebd2e9750f43' 2021-07-25 09:31:56 +02:00
Roman Perepelitsa
e3c8529052 doc: typo 2021-07-23 09:09:40 +02:00
NickVeld
c23f3c3c10 README: Windows Terminal "default"->"defaults" key (#1499) 2021-07-22 20:29:54 +02:00
Roman Perepelitsa
5e1c1caeb1 doc: spello 2021-07-22 12:01:06 +02:00
Roman Perepelitsa
70ae5810d8 Squashed 'gitstatus/' changes from 0440e38b..1edd9e62
1edd9e62 build: respect standard compiler/linker env vars and fortify by default

git-subtree-dir: gitstatus
git-subtree-split: 1edd9e621ec5c8bf038767e529194b5c53b36352
2021-07-22 11:19:22 +02:00
Roman Perepelitsa
a38a1f5be1 Merge commit '70ae5810d81f941a93e2077a2aa080f341deb96e' 2021-07-22 11:19:22 +02:00
Roman Perepelitsa
83d80fa308 don't leak 'state' local parameter 2021-07-17 10:38:22 +02:00
Roman Perepelitsa
2c135dd631 make terraform_version consistent with other *_version segments (#1487) 2021-07-17 10:36:26 +02:00
Roman Perepelitsa
942c4cf640 Merge branch 'alexganwd-master' 2021-07-17 10:13:48 +02:00
Alejandro Gandara
dae5f7f1c9 * Added cache to terraform --version 2021-07-16 20:39:23 +01:00
Alejandro Gandara
9f98915167 * Added new module for showing in prompt the active terraform version.
* Prompt shows, the terraform version in this format: Terraform v0.12.13 . If only if a valid terraform command is used, or an alias to it.

The use case: We have a need to constantly change between terraform versions depending on what project we are working on, it is easy to by mistake upgrade the wrong project to the latest terraform version. This is why the prompt is shown in red as soon as you type terraform, this is a good way to remind you to double check this is the terraform version you want to use.

Future improvements:
Detect current terraform version by checking the terraform workspace state version, and change the terraform color prompt from red to green if you're using the same version.
2021-07-15 00:44:37 +01:00
Alejandro Gandara
f717a91f55 * Added new module for showing in prompt the active terraform version.
* Prompt shows, the terraform version in this format: Terraform v0.12.13 . If only if a valid terraform command is used, or an alias to it.

The use case: We have a need to constantly change between terraform versions depending on what project we are working on, it is easy to by mistake upgrade the wrong project to the latest terraform version. This is why the prompt is shown in red as soon as you type terraform, this is a good way to remind you to double check this is the terraform version you want to use.

Future improvements:
Detect current terraform version by checking the terraform workspace state version, and change the terraform color prompt from red to green if you're using the same version.
2021-07-14 22:52:22 +01:00
Chris Williams
1e7be00e04 show kubecontext show for 'flux' (#1480)
Support for flux v2 CLI

Co-authored-by: Chris Williams <chris.williams@amwell.com>
2021-07-09 08:46:18 -07:00
Roman Perepelitsa
4bcc519547 add 'minify' make target 2021-07-02 14:39:26 +02:00
Roman Perepelitsa
077abf95e0 Squashed 'gitstatus/' changes from 4b4226ca..0440e38b
0440e38b sort files lexicographically
1c3157e9 don't minify from pkg target

git-subtree-dir: gitstatus
git-subtree-split: 0440e38b336aaf992aabf3649bf4a7f65f396c0a
2021-07-02 14:37:14 +02:00
Roman Perepelitsa
05d71fe82c Merge commit '077abf95e0a3c6325a6cdd9761cab75b2f5468c6' 2021-07-02 14:37:14 +02:00
Roman Perepelitsa
63a009669a Squashed 'gitstatus/' changes from 68bf9e0d..4b4226ca
4b4226ca add 'minify' make target and invoke it from 'pkg'

git-subtree-dir: gitstatus
git-subtree-split: 4b4226ca86fef6d0491ab9e863c9df6864dc9642
2021-07-02 14:28:20 +02:00
Roman Perepelitsa
7f4a2741b5 Merge commit '63a009669a7ed6aa6040f0af008adfb62829188c' 2021-07-02 14:28:20 +02:00
Roman Perepelitsa
7759063b74 Squashed 'gitstatus/' changes from 76182238..68bf9e0d
68bf9e0d build: generate byte-for-byte identical gitstatusd if the compiler supports -ffile-prefix-map
d03e8edc add support for s390x architecture
b32bea51 add a few architectures to the build script
7001409e bash: escape non-printable ANSI sequences (#247)

git-subtree-dir: gitstatus
git-subtree-split: 68bf9e0da3695ae989d523527643bef3f88e86ea
2021-07-02 11:02:41 +02:00
Roman Perepelitsa
f4668bc194 Merge commit '7759063b7485ca0701fc8ba1961d0e53568ddd59' 2021-07-02 11:02:41 +02:00
Roman Perepelitsa
3213e2e17f docs: grammar 2021-07-01 12:05:46 +02:00
Roman Perepelitsa
fd7313a5e7 replace @{u} with @{upstream} in the docs (it's the same thing but clearer) 2021-06-30 18:44:10 +02:00
Roman Perepelitsa
0c862a1307 add zwc make target 2021-06-27 18:49:57 +02:00
Roman Perepelitsa
f1ff680487 Squashed 'gitstatus/' changes from 260a5f4b..76182238
76182238 add zwc make target
ea398d90 update libgit2 ref (https://github.com/romkatv/powerlevel10k/issues/1428)

git-subtree-dir: gitstatus
git-subtree-split: 7618223859f614a76ed8019f1a60ee3df18984cf
2021-06-27 18:48:35 +02:00
Roman Perepelitsa
73eff3a033 Merge commit 'f1ff6804871d47381b9bd398a60f4c77174fd1d3' 2021-06-27 18:48:35 +02:00
Roman Perepelitsa
c003c253e8 respect POWERLEVEL9K_INSTANT_PROMPT_COMMAND_LINES if it is explicitly set even when using z4h 2021-06-24 22:40:23 +02:00
Roman Perepelitsa
515422c727 fix a bug that can cause instant prompt to be duplicated in z4h 2021-06-24 21:42:00 +02:00
Roman Perepelitsa
67f494cf54 docs: cross-link two faq entries 2021-06-17 17:59:17 +02:00
Roman Perepelitsa
606d4a85a9 readme: update git commands for ahead/behind arrows 2021-06-17 15:20:14 +02:00
Roman Perepelitsa
717573d845 presume that node --version may depend on package.json (#1388) 2021-06-11 14:09:52 +02:00
Roman Perepelitsa
a49f90d3ba cleanup 2021-06-11 10:19:09 +02:00
Roman Perepelitsa
4e4c14927f update windows terminal font instructions to accound for the fact that they've changed the default settings.json (#1443) 2021-06-11 10:17:23 +02:00
Roman Perepelitsa
2e0989c018 Merge commit '038de6f78b21171615d0b4628471e71efe10d77e' 2021-06-11 09:23:28 +02:00
Roman Perepelitsa
038de6f78b Squashed 'gitstatus/' changes from 96b520b2..260a5f4b
260a5f4b fix a bug in the build script that prevented it from working when using a newer git with init.defaultBranch override in the global config (#242)

git-subtree-dir: gitstatus
git-subtree-split: 260a5f4bc9776d41d500a51464249ce5e555a99e
2021-06-11 09:23:27 +02:00
Roman Perepelitsa
f5d61840ae Merge commit '96f3ca173331c5dba505d2ef5106cb0d605ec3be' 2021-06-10 10:15:02 +02:00
Roman Perepelitsa
96f3ca1733 Squashed 'gitstatus/' changes from 113f1f69..96b520b2
96b520b2 build v1.5.1 binaries for all platforms
ffeb0507 bump version to v1.5.1
1bcbea07 mbuild: disable pacman upgrades on msys
39dbb92f log a warning if unable to parse packed-refs
a9d70ec0 add `-r` flag to gitstatus_start in bash bindings (#241)
abbf9a79 don't use static_assert with one argument as it's not available prior to c++17 (#239)
f8c396e4 drop all tags if packed-refs doesn't have a header line (https://github.com/romkatv/powerlevel10k/issues/1428)

git-subtree-dir: gitstatus
git-subtree-split: 96b520b248ca872646e27b3df4535898356e4637
2021-06-10 10:15:02 +02:00
Roman Perepelitsa
4ba3c010f6 display "wip" in git status if the latest commit's summary contains "wip" or "WIP" (#1425) 2021-06-08 11:47:19 +02:00
Roman Perepelitsa
b9c62ca028 cleanup 2021-06-04 15:02:36 +02:00
Roman Perepelitsa
ba83466e1d Squashed 'gitstatus/' changes from 6d00edd0..113f1f69
113f1f69 release gitstatusd-linux-x86_64 v1.5.0
e193be52 expose HEAD's commit message
815301f1 do not redefine `exec` and `builtin` in bash bindings (#235)
97c2aa17 Make `--version-glob` to receive an argument (#216)
95e549fd Support building on OpenBSD (#208)
b054ddf2 make homebrew instructions work on systems other than darwin-x86_64 (#205)
98f99ade fix brew formula check

git-subtree-dir: gitstatus
git-subtree-split: 113f1f698667d12906d97e3818aec5d760dc6e3d
2021-05-30 10:35:40 +02:00
Roman Perepelitsa
f217e4a39a Merge commit 'ba83466e1da75d9260ebbb145215d9c46d6eadf6' 2021-05-30 10:35:40 +02:00
Roman Perepelitsa
77fa0e6dcc clarify that Windows Terminal is the one from Microsoft 2021-05-29 08:41:20 +02:00
Mark Adamson
d87d557b0f Correct name of Windows Terminal in README (#1424) 2021-05-28 23:39:59 -07:00
Roman Perepelitsa
c7ad00b5a5 add P9K_AWS_PROFILE and P9K_AWS_REGION and use it in default configs 2021-05-28 14:55:27 +02:00
Roman Perepelitsa
10918387b3 Merge branch 'alexjurkiewicz-aws-region' 2021-05-28 14:52:29 +02:00
Alex Jurkiewicz
aa4d366341 Add region support for aws element 2021-05-28 14:25:39 +10:00
Zachary Palumbo
35acee119d Fix icon padding for VCS bookmark icon (#1414) (#1415) 2021-05-26 07:40:05 -07:00
Roman Perepelitsa
c59720647a fix asdf, was broken by 1ad8e5759e (#1409) 2021-05-25 14:01:41 +02:00
Roman Perepelitsa
a3494a52d7 don't leak 'token' local variable in parser (#1407) 2021-05-24 09:19:13 +02:00
Roman Perepelitsa
f774df6c76 pyenv: skip lines that start with "#" (#1376) 2021-05-22 12:19:41 +02:00
Roman Perepelitsa
69d3650958 hide python version in pyenv in lean/classic/rainbow if the rest of the content starts with it (#1376) 2021-05-22 12:09:13 +02:00
Roman Perepelitsa
1ad8e5759e when searching for files in ancestor directories, do match in $HOME (#1376) 2021-05-22 11:57:13 +02:00
Roman Perepelitsa
4d2346da0a set P9K_PYENV_PYTHON_VERSION correctly when dealing with multiple pyenv versions (#1376) 2021-05-22 11:32:24 +02:00
Roman Perepelitsa
0ab7e1ccfd when resolving python --version, handle pyenv shims specially (#1378) 2021-05-22 11:00:19 +02:00
Maxim Baz
9c034101fe Add xplr segment (#1396) 2021-05-15 04:37:58 -07:00
Roman Perepelitsa
f924646194 add P9K_IP_{RX,TX}_BYTES_DELTA to the list of parameters available within POWERLEVEL9K_IP_CONTENT_EXPANSION (#1392) 2021-05-14 11:41:56 +02:00
Roman Perepelitsa
8d1daa4e63 disable colors in the output of taskwarrior; this time for real (#1365) 2021-04-20 22:19:51 +02:00
Roman Perepelitsa
b69bb45ab1 disable colors in the output of taskwarrior (#1365) 2021-04-20 20:53:58 +02:00
Roman Perepelitsa
48ff2e8065 fix style in font instructions 2021-04-18 08:54:54 +02:00
alsoGAMER
836332f578 Font instructions for Asbrú Connection Manager (#1362) 2021-04-17 23:48:26 -07:00
Roman Perepelitsa
607befe822 bump version (#1361) 2021-04-16 22:07:08 +02:00
Roman Perepelitsa
cd865da150 expand c-escapes in kubectl (#1361) 2021-04-16 21:42:41 +02:00
Roman Perepelitsa
b55ad16bdf bug fix: segments whose state contains numbers could not be hidden (#1353) 2021-04-12 12:22:19 +02:00
Roman Perepelitsa
30bd9461b3 replace POWERLEVEL9K_LOAD_THRESHOLD with POWERLEVEL9K_LOAD_{WARNING,CRITICAL}_PCT (#1340) 2021-04-10 14:27:09 +02:00
Tim Ysewyn
8dc91004cb Fix gcloud CLI (#1342)
This reverts #1324 (d3de2e5).
2021-04-04 09:59:17 -07:00
Klas Mellbourn
8b2aab74d4 Add CPU load threshold setting (#1340) 2021-04-04 02:21:39 -07:00
Éverton Arruda
4d15cf977e Get active gcloud profile using list command (#1331)
`gcloud config configure describe` command does not have `--filter` option. To filter
the active profile using `--filter` it is necessary to use `gcloud config configure list`
command
2021-03-30 19:46:50 +02:00
Paweł Cembaluk
af86b53047 Added urxvt to manual font installation section. (#1326) 2021-03-28 05:59:23 -07:00
Roman Perepelitsa
eafd78c3e0 respect POWERLEVEL9K_SHORTEN_DIR_LENGTH when POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_last 2021-03-28 09:54:30 +02:00
Ramon Lucas
d3de2e558c Sets the filter for the current gcloud profile (#1324)
When you have multiple profiles set up, you should only search for what is currently active.
2021-03-27 01:16:50 -07:00
Daniel
58f5470cd9 Add font configuration instructions for WezTerm (#1313) 2021-03-21 09:03:53 -07:00
Roman Perepelitsa
7d786b9c50 remove all CR from prompt (#1304 and #1305) 2021-03-17 08:44:29 +01:00
Roman Perepelitsa
d28e84ca70 don't display git tag when on a branch (#1294) 2021-03-09 12:20:28 +01:00
wzy
6d545d5dd0 Fix #1286 (#1288) 2021-03-07 00:11:28 -08:00
bcochofel
3920940ea8 add fluxctl and stern to show kubecontext (#1268) 2021-02-21 21:03:14 +01:00
bcochofel
6a7115b35b add azure classes (#1274) 2021-02-21 19:02:14 +01:00
Roman Perepelitsa
b816abfed0 work around bugs in add-zle-hook-widget (#1238) 2021-01-26 12:43:07 +01:00
Dhananjay Tanpure
d26bdcd601 Added wlan(w) to regular expression of network interfaces. (#1224)
Name of network interface is generally wlan0 or wlp3s0.
2021-01-15 18:20:46 +01:00
Jaehee Hong
7b0698debf README Homebrew installation script fix (#1222)
make homebrew installation instructions work on darwin-arm64 and linux
2021-01-14 15:51:52 +01:00
Thomas Lauf
68abcc86d4 Change icon for the timewarrior segment to '' (#1217)
Icon was changed in c65260aaab from '🛡️' to ''
2021-01-13 09:05:59 +01:00
Roman Perepelitsa
00232d1b6d doc cleanup 2021-01-09 23:55:54 +01:00
Roman Perepelitsa
a2695675e6 Merge branch 'joxxperez-master' 2021-01-09 23:53:52 +01:00
Roman Perepelitsa
a20dcd0284 Merge branch 'master' of https://github.com/joxxperez/powerlevel10k into joxxperez-master 2021-01-09 23:53:45 +01:00
Roman Perepelitsa
4fa8943960 doc cleanup 2021-01-09 23:53:03 +01:00
Roman Perepelitsa
f0af094382 Merge branch 'YorkGrizzly-master' 2021-01-09 23:50:59 +01:00
Roman Perepelitsa
eadc0d7653 Merge branch 'master' of https://github.com/YorkGrizzly/powerlevel10k into YorkGrizzly-master 2021-01-09 23:50:43 +01:00
Julio Carlos Barrera Juez
33f20f5eae Add tailscale in VPN network interface regex. (#1208) 2021-01-09 23:49:12 +01:00
joxxperez
a43748d427 Add font installation instructions for WSLtty 2021-01-02 20:10:48 +08:00
YorkGrizzly
8bafd1a2ee Update font.md 2020-12-29 09:57:22 +08:00
YorkGrizzly
ea97f031b4 Update font.md 2020-12-29 09:56:53 +08:00
YorkGrizzly
29f16b61e6 Update font.md 2020-12-29 09:54:30 +08:00
Roman Perepelitsa
d524164020 scroll the screen before printing prompt 2020-12-26 13:23:54 +01:00
Roman Perepelitsa
0513e0fee4 simplify uname -m matching on Windows platforms 2020-12-24 05:54:50 +01:00
Roman Perepelitsa
59db4252bb uncomment BACKGROUND and FOREGROUND parameters in p10k-classic.zsh; comments confuse users (#1182) 2020-12-24 05:52:27 +01:00
Roman Perepelitsa
dd62469cc7 fix ram segment on darwin-arm64 (#1181)
Darwin arm64 uses 16kB pages rather than 4kB.

Thanks, @johnalanwoods!
2020-12-24 05:26:15 +01:00
Osman Tas
61c63eea6b Update os icon at zsh.exe on git for windows (#1180)
* Update os icon for windows

Windows icon is not shown on git for windows zsh.exe
Same update also added to wizards.zsh file.

* Update os icon for windows

Windows icon is not shown on git for windows zsh.exe
Same update also added to p10k.zsh file.
2020-12-23 17:58:10 +01:00
Roman Perepelitsa
5ea5d4bc19 disable re_match_pcre, otherwise we can get an error when users enable this option without having zsh/pcre module 2020-12-22 09:16:02 +01:00
Roman Perepelitsa
9609a835ca Squashed 'gitstatus/' changes from 630915cc..6d00edd0
6d00edd0 use a native binary on darwin-arm64
4994f160 add a build server for darwin-arm64
0e922813 invoke port with sudo
576571c7 support macports in addition to homebrew when building on macos
b476570a remove trailing whitespace

git-subtree-dir: gitstatus
git-subtree-split: 6d00edd0bfd1d65a3cbcebfc6e679e8c43726acc
2020-12-21 16:19:22 +01:00
Roman Perepelitsa
9c3ecab81e Merge commit '9609a835ca2eb3089e35269a05369833e9ec0ba2' 2020-12-21 16:19:22 +01:00
Roman Perepelitsa
033e01a272 docs: more info about greyed out vcs 2020-12-21 07:33:17 +01:00
Samuel Gräfenstein
38a5492b5f Remove trailing whitespace (#1173) 2020-12-20 16:28:52 +01:00
Roman Perepelitsa
a5d0525c6a refactor my_git_formatter to make it easier to change it so that tag is always displayed 2020-12-20 07:54:27 +01:00
Roman Perepelitsa
4807bd8da2 doc cleanup 2020-12-20 07:33:10 +01:00
Roman Perepelitsa
7a6eef4918 docs: make p10k configure method of installing MesloLGS NF for iTerm2 more prominent 2020-12-19 08:46:09 +01:00
Roman Perepelitsa
b9b3399b35 nordvpn: pick up nordvpnd.sock from the new location (changed with the latest nordvpn update); remove MISSING state; (#1167) 2020-12-18 16:25:33 +01:00
Roman Perepelitsa
fc854fa719 fix a comment above POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION in lean style 2020-12-18 08:40:26 +01:00
Roman Perepelitsa
e8afa806ce wizard: allow uppercase letters in choices 2020-12-16 16:07:00 +01:00
Josh Skidmore
c14fe96b7c remove accidental logging (#1161) 2020-12-14 18:18:23 +01:00
Roman Perepelitsa
439ce3ed4e dir: add _NON_EXISTENT similarly to _NON_WRITABLE (requires POWERLEVEL9K_DIR_SHOW_WRITABLE=v3)
See #1160.
2020-12-14 08:01:05 +01:00
Roman Perepelitsa
1d99a0bac3 wifi: recognize SSID with spaces (#1152) 2020-12-12 09:42:41 +01:00
Roman Perepelitsa
b90b36251d fix iterm2 integration when not using zsh4humans v5 (#192 and #1138) 2020-12-02 08:56:34 +01:00
Roman Perepelitsa
2c3bcd8b5d use tmux bypass in iterm2 integration only if _z4h_iterm_cmd is defined 2020-11-28 09:55:13 +01:00
Roman Perepelitsa
c7914c051d remove spurious slashes from tmux bypass sequences 2020-11-26 13:21:57 +01:00
Roman Perepelitsa
96646e8b9c enable iterm2 shell integration when running in tmux in zsh4humans 2020-11-26 12:05:00 +01:00
ratijas
ecf1de25a4 Makefile: optimize recursive invocation (#1127) 2020-11-26 11:51:39 +01:00
Roman Perepelitsa
ff79e502ad don't allow overriding the path to zsh 2020-11-26 11:17:01 +01:00
Roman Perepelitsa
29759b7b0a Squashed 'gitstatus/' changes from e2276e72..630915cc
630915cc don't allow overriding the path to zsh

git-subtree-dir: gitstatus
git-subtree-split: 630915ccb1700f1f880d25cb04da23bf68fc4293
2020-11-26 11:16:16 +01:00
Roman Perepelitsa
bc3158cba5 Merge commit '29759b7b0a83e08ce52dd240c9463f2c8090c416' 2020-11-26 11:16:16 +01:00
Roman Perepelitsa
a7f417245d Squashed 'gitstatus/' changes from 4211e33b..e2276e72
e2276e72 install: fix the check for rosetta on darwin-arm64

git-subtree-dir: gitstatus
git-subtree-split: e2276e729e0155391126ee4aa090aa38b345b9fd
2020-11-26 08:53:49 +01:00
Roman Perepelitsa
9d6444a557 Merge commit 'a7f417245dc21b666f5647c4a0d55a1f51fe6cac' 2020-11-26 08:53:49 +01:00
Roman Perepelitsa
622069e60f Squashed 'gitstatus/' changes from 1dcba393..4211e33b
4211e33b bug fix: make install work in standalone mode

git-subtree-dir: gitstatus
git-subtree-split: 4211e33b0b9ed9ad41898eb5a2b4f6a4a37b1db2
2020-11-26 07:59:45 +01:00
Roman Perepelitsa
381bd09e67 Merge commit '622069e60f9573c714f482dd6c76858229f4732a' 2020-11-26 07:59:45 +01:00
Roman Perepelitsa
4050729e48 wizard: after installing the font on iTerm2, ask for system reboot if iTerm2 session restoration is enabled 2020-11-26 07:19:35 +01:00
Roman Perepelitsa
7cb7ee07be docs: add missing colons 2020-11-25 17:55:01 +01:00
Roman Perepelitsa
ade5b86226 docs: s/Windows Terminal/Microsoft Terminal/ 2020-11-25 17:53:00 +01:00
Roman Perepelitsa
e1e50dc84c Merge branch 'ravinderpal-patch-1' 2020-11-25 17:52:03 +01:00
Ravinder Pal Singh
8db9836617 Added instructions for IntelliJ 2020-11-25 16:46:34 +00:00
Roman Perepelitsa
98a8ec6f31 prompt_length cleanup 2020-11-25 16:36:03 +01:00
Roman Perepelitsa
685682da90 Revert "docs: experiment with image dimensions"
This reverts commit 507018f079.
2020-11-25 12:09:55 +01:00
Roman Perepelitsa
507018f079 docs: experiment with image dimensions 2020-11-25 11:52:44 +01:00
Roman Perepelitsa
fb89173a42 Squashed 'gitstatus/' changes from 313fa4f1..1dcba393
1dcba393 bash bindings: provide better diagnostic for the common errors in gitstatus_start
603fb2ce move the rosetta check to `install`
3829edac provide better diagnostic for the common errors in gitstatus_start
e8c9d9cd propagate error messages from install to gitstatus_start; add a special diagnostic for rosetta2

git-subtree-dir: gitstatus
git-subtree-split: 1dcba3930654c54976f112b96acf2ce23006d9e5
2020-11-25 11:14:38 +01:00
Roman Perepelitsa
7e363af9ed Merge commit 'fb89173a4257e0cfe3bfc772bc0cdbccd61e3be0' 2020-11-25 11:14:38 +01:00
Roman Perepelitsa
98b96e06cc Squashed 'gitstatus/' changes from ae213c54..313fa4f1
313fa4f1 make target pkg work with `make -C`

git-subtree-dir: gitstatus
git-subtree-split: 313fa4f1bfabe99a04027d0bdf6cbecff2e963c7
2020-11-24 12:46:19 +01:00
Roman Perepelitsa
6b254621e7 Merge commit '98b96e06cc98ef2578adbf83ac4bb35a74485e8f' 2020-11-24 12:46:19 +01:00
Roman Perepelitsa
139413f535 add Makefile 2020-11-24 12:46:00 +01:00
Roman Perepelitsa
39cf063480 Squashed 'gitstatus/' changes from 90cbb46b..ae213c54
ae213c54 add "pkg" target to makefile
a208375b comments
cdeb063d Give ListDir() the same semantics on BSD as on Linux

git-subtree-dir: gitstatus
git-subtree-split: ae213c540dd6924d3fb153a0860276897c9ba6c8
2020-11-24 12:32:19 +01:00
Roman Perepelitsa
21df7db2b2 Merge commit '39cf063480f6a378dadc802c174e96112c38c0c0' 2020-11-24 12:32:19 +01:00
Felix Yan
31ede3c1d3 Correct some typos in zsh conf (#1119) 2020-11-22 19:02:27 +01:00
Roman Perepelitsa
28301be914 Squashed 'gitstatus/' changes from cb363c77..90cbb46b
90cbb46b s/darwin-aarch64/darwin-arm64/ (#188)

git-subtree-dir: gitstatus
git-subtree-split: 90cbb46b7b185dc95ab96301ec49754f7e801395
2020-11-17 12:42:57 +01:00
Roman Perepelitsa
04f75a10a5 Merge commit '28301be914dec6da5d1bbd21cd8511cd71b419ee' 2020-11-17 12:42:57 +01:00
Roman Perepelitsa
790f4719ab Squashed 'gitstatus/' changes from f9acc15a..cb363c77
cb363c77 build: use zsh if possible when langued with bash < 4.0 (#188)
efb0fd1b make mbuild work with darwin-aarch64
c9e6e608 do not hardcode path to homebrew, retrieve it with `brew --prefix`
df4eba94 work around bugs in ancient versions of bash (#188)

git-subtree-dir: gitstatus
git-subtree-split: cb363c778a37575b0d5371d7e355d80ec7ae5498
2020-11-17 10:02:15 +01:00
Roman Perepelitsa
ece7213a3d Merge commit '790f4719ab3db8d62f8dec6fbb89a91d87e81df0' 2020-11-17 10:02:15 +01:00
Roman Perepelitsa
b7167a64c6 Squashed 'gitstatus/' changes from 4f9746fb..f9acc15a
f9acc15a use gitstatusd-darwin-x86_64 binary on darwin-aarch64 (#188)

git-subtree-dir: gitstatus
git-subtree-split: f9acc15a3aa224206c4eba0e4a26b5d3c00325be
2020-11-17 08:32:05 +01:00
Roman Perepelitsa
bf830b5bf1 Merge commit 'b7167a64c60401b184346147d8297f3803f56e06' 2020-11-17 08:32:05 +01:00
Roman Perepelitsa
7969eb3f18 retain instant prompt and state dumps when TERM or TERM_PROGRAM change (#1098) 2020-11-16 09:02:31 +01:00
Roman Perepelitsa
09be56bb53 fix a bug introduced in 8488f7c (#1107) 2020-11-15 11:05:31 +01:00
Roman Perepelitsa
f8ae544e6e properly restore prompt after SIGINT when transient prompt is disabled 2020-11-15 09:41:55 +01:00
Roman Perepelitsa
8488f7c75c set P9K_PROMPT=transient for the duration of zle-line-finish (#1105) 2020-11-15 09:19:25 +01:00
Roman Perepelitsa
feaf120ddc replace >>! with >>; the former does not work in non-zsh shells; users who set noclobber presumably know how to deal with errors from this option 2020-11-11 07:04:02 +01:00
Roman Perepelitsa
8840fe550e correct install-without-internet instructions w.r.t. ZSH_THEME 2020-11-11 07:02:38 +01:00
Roman Perepelitsa
271836403d remove artificial term scrolling before instant prompt 2020-11-10 09:59:41 +01:00
Roman Perepelitsa
e2db860745 print instant prompt only if TERM is the same 2020-11-10 07:41:14 +01:00
Roman Perepelitsa
f6c24d2053 reset text attributes at the end of precmd 2020-11-08 13:33:59 +01:00
Roman Perepelitsa
2fc7257486 docs 2020-11-06 09:13:04 +01:00
Roman Perepelitsa
d7861fcfa0 wrap z4h-clear-screen-{soft,hard}-top 2020-11-01 18:23:10 +01:00
Roman Perepelitsa
5e2422df50 fix old/new TTY detection 2020-10-31 07:10:11 +01:00
Roman Perepelitsa
76e5a69262 save/restore screen through z4h to make it work over ssh 2020-10-30 16:27:35 +01:00
Roman Perepelitsa
85f9e75918 drop __p9k_initial_screen_empty; rely on _Z4H_TMUX_CMD instead 2020-10-30 13:52:17 +01:00
Roman Perepelitsa
aa6d40b733 fix a typo in docs 2020-10-28 09:21:22 +01:00
Stephen
b9be4f968b Updating README.md typo (#1077) 2020-10-28 09:20:36 +01:00
Roman Perepelitsa
2ea3356d66 add __p9k_initial_screen_empty for z4h integration 2020-10-27 12:49:39 +01:00
Roman Perepelitsa
8f0db0c95a expand the list of things powerlevel10k does not affect 2020-10-27 08:01:42 +01:00
Roman Perepelitsa
74c6e18363 check mtime of $TTY on startup if it points to a different tty than in the parent; see #930 2020-10-26 12:26:45 +01:00
Roman Perepelitsa
49d5617989 doc cleanup 2020-10-25 06:14:39 +01:00
Roman Perepelitsa
78301e82c7 clean up kitty font instructions and duplicate them in readme.md 2020-10-25 06:14:12 +01:00
Subhaditya Nath
98494aaf7b Added font instructions for kitty (#1070) 2020-10-25 06:10:26 +01:00
Roman Perepelitsa
d86bbe75de Squashed 'gitstatus/' changes from 6ba954a4..4f9746fb
4f9746fb rebuild all binaries
6a3d54a5 upgrade libgit2

git-subtree-dir: gitstatus
git-subtree-split: 4f9746fb6e949475ff37986883796adf937055da
2020-10-22 14:54:18 +02:00
Roman Perepelitsa
68c89ec2bc Merge commit 'd86bbe75dece6c4276bfa99246880b92f9e053aa' 2020-10-22 14:54:18 +02:00
Roman Perepelitsa
de0e022177 typo 2020-10-21 16:24:38 +02:00
Roman Perepelitsa
f0159ca642 better wizard intro when installing z4h 2020-10-21 16:23:15 +02:00
Roman Perepelitsa
fdc0ee3708 add font.md 2020-10-21 16:05:26 +02:00
Roman Perepelitsa
2bcf38f554 add #font anchor to the readme 2020-10-21 15:53:29 +02:00
Roman Perepelitsa
47713ea2df recognize double-quoted strings when parsing kube configs; other types of quotes are still not supported; see #1061 2020-10-21 13:40:10 +02:00
Kid
e43209409b Correct Python version in README (#1065) 2020-10-21 12:28:59 +02:00
Roman Perepelitsa
b1aeeda6a8 make it more difficult to incorrectly restart iTerm2 after installing Meslo 2020-10-19 16:36:25 +02:00
Roman Perepelitsa
a124a71032 typo 2020-10-19 14:51:01 +02:00
Roman Perepelitsa
fb2805e5ab wizard: suppress the into message if z4h would print its welcome 2020-10-19 14:46:22 +02:00
Roman Perepelitsa
967e845819 wizard: suppress the outro message if z4h would print its welcome 2020-10-19 14:39:01 +02:00
Roman Perepelitsa
b770e6a3e5 fix a typo that was preventing zinit-specific diagnostic from being printed as part of an error message 2020-10-09 10:52:43 +02:00
Roman Perepelitsa
0da94e8ef1 Squashed 'gitstatus/' changes from a760bda8..6ba954a4
6ba954a4 make lowercasing work with Turkish locale

git-subtree-dir: gitstatus
git-subtree-split: 6ba954a4a6fb2b86e7d10b4b19db7757b4876525
2020-10-05 15:05:41 +02:00
Roman Perepelitsa
9b981b89c5 Merge commit '0da94e8ef1b84581ce8b4eb397c9e465640711ea' 2020-10-05 15:05:41 +02:00
Roman Perepelitsa
3aec0c6b36 make configuration options work when using Turkish locale (#1036) 2020-10-05 14:46:48 +02:00
Roman Perepelitsa
060af91a80 fix a TOC link 2020-10-02 10:10:38 +02:00
Roman Perepelitsa
2875595647 add offline installation instructions (#1033) 2020-10-02 10:08:44 +02:00
Roman Perepelitsa
42aa719e48 dir: ignore separator overrides if cwd is / and POWERLEVEL9K_DIR_OMIT_FIRST_CHARACTER is true 2020-09-29 13:40:06 +02:00
Roman Perepelitsa
3586cc8d7e expand c-escapes in POWERLEVEL9K_DIR_PATH_SEPARATOR 2020-09-29 13:26:21 +02:00
Roman Perepelitsa
71b39f0da9 mention that zsh-theme-powerlevel10k community package is to be avoided (#1018) 2020-09-21 07:31:02 +02:00
Roman Perepelitsa
80d9e57388 docs: remove -u from yay commands (#1018) 2020-09-19 12:28:55 +02:00
Roman Perepelitsa
3b772824c0 don't print instant prompt if it was generated with the different value of terminfo[colors] 2020-09-16 15:01:49 +02:00
Roman Perepelitsa
f14b58e44f wizard: recognize source $POWERLEVEL9K_CONFIG_FILE in .zshrc 2020-09-15 19:50:11 +02:00
Roman Perepelitsa
4d1fba340f bug fix: superfluous dash in P9K_KUBECONTEXT_CLOUD_ZONE (#1013) 2020-09-15 10:45:04 +02:00
Roman Perepelitsa
760f7cb7a5 Squashed 'gitstatus/' changes from 3f874d9c..a760bda8
a760bda8 disable certificate checks in curl; we are checking sha256 anyway

git-subtree-dir: gitstatus
git-subtree-split: a760bda882be6e79a5ff0ab0ccc0e576781dca7a
2020-09-11 09:16:24 +02:00
Roman Perepelitsa
afb854d279 Merge commit '760f7cb7a55203be03ddf789dd3c06e75377423f' 2020-09-11 09:16:24 +02:00
Roman Perepelitsa
54d40b924c support POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=if-different; see #730 2020-09-09 12:10:26 +02:00
Lucas Larson
12cef82077 repair spelling of "occurrences" (#998) 2020-09-07 22:30:55 +02:00
Roman Perepelitsa
622130980c add scalaenv prompt segment; see #991 2020-09-03 10:53:19 +02:00
Roman Perepelitsa
6c8c6eea1b typo in comments 2020-09-02 09:24:58 +02:00
Roman Perepelitsa
3b2f474c9f support generic POWERLEVEL9K_* parameters for segments with dashes in their names 2020-09-01 13:31:05 +02:00
Roman Perepelitsa
47c842fe8e wifi: further cleanup 2020-09-01 11:12:55 +02:00
Roman Perepelitsa
cababbeee2 wifi on linux: fix bugs and speed things up 2020-09-01 11:03:55 +02:00
Roman Perepelitsa
dd5948e5e3 Merge branch 'master' of https://github.com/sys-lectern/powerlevel10k into sys-lectern-master 2020-09-01 10:03:08 +02:00
Roman Perepelitsa
536d90a335 support per-state POWERLEVEL9K_BATTERY_HIDE_ABOVE_THRESHOLD; see #988 2020-08-31 12:55:00 +02:00
Roman Perepelitsa
a3727dcaef wizard: don't refuse to start if ZDOTDIR is not writable; see #987 2020-08-30 13:40:12 +02:00
Roman Perepelitsa
c1db3926fe docs: explicitly mention that powerlevel10k doesn't affect key bindings 2020-08-28 10:25:23 +02:00
Roman Perepelitsa
b673e6a7dd bug fix: trigger transient prompt on send-break 2020-08-28 09:38:16 +02:00
Roman Perepelitsa
f2bf019758 wizard: simplify instant prompt screen; make sure all screens work with 47x14 terminal size 2020-08-27 09:57:12 +02:00
Roman Perepelitsa
2a4c962c21 add "gpd" to the list of default VPN network interfaces; see #979 2020-08-27 08:15:13 +02:00
Roman Perepelitsa
7eb501c0f5 suport hex flags in the output of ifconfig; see #979 2020-08-26 10:48:53 +02:00
sys-lectern
6853fcd8e5 minor fixes and syntax improvements 2020-08-25 16:13:41 -04:00
sys-lectern
c464fd25e6 Merge branch 'master' of https://github.com/sys-lectern/powerlevel10k 2020-08-25 16:07:49 -04:00
sys-lectern
16cb58d15f minor fixes and syntax improvements 2020-08-25 16:04:47 -04:00
Roman Perepelitsa
6279aa57d7 add k9s, helmfile and terragrunt to some SHOW_ON_COMMAND params; #904 2020-08-24 09:02:52 +02:00
Roman Perepelitsa
2f4c3c4cec use portable sed syntax 2020-08-24 08:22:03 +02:00
David
d62961131c Delete log.txt 2020-08-23 17:36:08 -04:00
hal9000
8f90ed6d49 linux wifi widget 2020-08-23 17:29:03 -04:00
Roman Perepelitsa
dca8774f1b add an empty line after the branch/tag truncation logic 2020-08-22 09:35:48 +02:00
Roman Perepelitsa
711490252e enable extended_glob in p10k-{pure,robbyrussell}.zsh to fix unset -m 2020-08-21 11:08:14 +02:00
Roman Perepelitsa
525e2545db wizard: when using z4h, automatically enable instant prompt in quiet mode 2020-08-20 14:59:07 +02:00
Roman Perepelitsa
c425a5e635 bump version 2020-08-19 22:12:14 +02:00
Roman Perepelitsa
03ab8e9c7e don't expand _p9k_dir when dir is hidden via 'p10k display'; see #952 2020-08-18 11:25:43 +02:00
Roman Perepelitsa
f63d6a31c1 Squashed 'gitstatus/' changes from fcebf0b0..3f874d9c
3f874d9c fix bash bindings when noclobber is set; see #171

git-subtree-dir: gitstatus
git-subtree-split: 3f874d9c5933d184b9b06472dcf25e8debb326a8
2020-08-15 08:59:44 +02:00
Roman Perepelitsa
ebfaae2ab6 Merge commit 'f63d6a31c1348e304e40e4e0dcca750128ad2cfd' 2020-08-15 08:59:44 +02:00
Roman Perepelitsa
7a0bf995c7 formatting 2020-08-08 23:52:52 +02:00
Roman Perepelitsa
b53b43533a fix wording in docs 2020-08-08 23:52:12 +02:00
Stephen
d5d6ee11ff Add MesloLGS font config steps for Alacritty (#929)
* Add MesloLGS font config steps for Alacritty

* Adding Alacritty font config changes

Shortened steps and included official doc.

* Simplified and updated Alacritty font config steps

Required YAML included in full. Quotes added to font family due to space in family name. Unnecessary detail has been removed.

* Reduced the required Alacritty font config YAML

Removed additional styles from the "MesloLGS NF" family, they are implied and not necessary to include.
2020-08-08 23:50:04 +02:00
Roman Perepelitsa
c48b81ecb7 remove minimum screen size requirement for the wizard 2020-08-06 16:03:04 +02:00
Roman Perepelitsa
03e61879b5 add p10k display -r 2020-08-03 10:58:14 +02:00
Roman Perepelitsa
cb59280c40 Squashed 'gitstatus/' changes from b157d02a..fcebf0b0
fcebf0b0 support armv8l; see #165; thanks, @ppoffice!
c2e03bc5 use fully-qualified remote ref name; see powerlevel10k/issues/915
dc827169 Merge branch 'master' of github.com:romkatv/gitstatus
4ad671ca build: set -march=armv8 when compiling for Apple's arm64 architecture

git-subtree-dir: gitstatus
git-subtree-split: fcebf0b0f7aff181f2ecc441458d8fc443125ad0
2020-07-29 23:05:36 +02:00
Roman Perepelitsa
422b7a94b9 Merge commit 'cb59280c407e60d6e993c24eaddb0feb5dd373a5' 2020-07-29 23:05:36 +02:00
Roman Perepelitsa
05ff662568 add more info about the resizing bug and patch 2020-07-29 09:45:02 +02:00
Roman Perepelitsa
598ff99f1b reduce the default value of POWERLEVEL9K_VCS_MAX_SYNC_LATENCY_SECONDS to 10ms 2020-07-29 08:37:51 +02:00
Roman Perepelitsa
2ba87f4d1f ksh_arrays compatibility 2020-07-23 08:17:34 +02:00
Roman Perepelitsa
15818346bf Squashed 'gitstatus/' changes from 7546f4de..b157d02a
b157d02a treat exit code 159 (SIGSYS) from gistatusd as terminal

git-subtree-dir: gitstatus
git-subtree-split: b157d02a9a89892f228064e06b5a817a24eea9ed
2020-07-20 15:06:02 +02:00
Roman Perepelitsa
88d5fb6145 Merge commit '15818346bf496c7e5f8cb21b2f273c47d386b7db' 2020-07-20 15:06:02 +02:00
Roman Perepelitsa
7a114ad6fb detect artix linux for the sake of displaying os logo; see #898 2020-07-19 11:26:19 +02:00
Roman Perepelitsa
86b747f434 bug fix: infinite loop when cwd doesn't exist; see #900 2020-07-19 11:10:50 +02:00
Roman Perepelitsa
24278ccd39 Merge commit 'bf2aa14becccd2b55180bc2aeff44d3a6015a580' 2020-07-19 11:09:40 +02:00
Roman Perepelitsa
bf2aa14bec Squashed 'gitstatus/' changes from 89e9ebfd..7546f4de
7546f4de return norepo-sync when cwd does not exist
8ccd4d34 comments

git-subtree-dir: gitstatus
git-subtree-split: 7546f4def34558e44ee660a9970da80f760e99c8
2020-07-19 11:09:39 +02:00
Roman Perepelitsa
6dfd92f8c1 allow optional offset in POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER; see #896 2020-07-18 10:43:49 +02:00
Roman Perepelitsa
620e69fef1 replace NETWORK_ICON; the original (U+F877) triggers a bug on macOS; see #895 2020-07-16 15:58:19 +02:00
Roman Perepelitsa
4635fcacee don't fetch gitstatusd if there is no git 2020-07-14 06:46:05 +02:00
Roman Perepelitsa
a28d45005e Merge branch 'master' of github.com:romkatv/powerlevel10k 2020-07-13 14:24:45 +02:00
Roman Perepelitsa
cfc35853df start downloading gitstatusd while wizard is running 2020-07-13 14:24:38 +02:00
Kamlesh
d0edcbc966 Add Instructions to make powerlevel10 work for Guake (#882)
Update instructions for the `Guake` terminal
2020-07-12 08:19:01 +02:00
Henry Bley-Vroman
34952e4a85 Fix POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD comments (#880) 2020-07-11 22:53:38 +02:00
Roman Perepelitsa
6c9d0977a6 make omz installation command work when executed from weird shells (no quoting though; that would be too much) 2020-07-11 10:07:34 +02:00
Roman Perepelitsa
0c341b6702 Merge branch 'ancarpan-terraform-show-default' 2020-07-04 08:08:49 +02:00
Roman Perepelitsa
06ed564092 Merge branch 'terraform-show-default' of https://github.com/ancarpan/powerlevel10k into ancarpan-terraform-show-default 2020-07-04 07:44:51 +02:00
Roman Perepelitsa
2d74ac9d06 prefix all source calls with builtin 2020-07-04 07:03:46 +02:00
Roman Perepelitsa
ff8654ccd5 Squashed 'gitstatus/' changes from 0f5402c8..89e9ebfd
89e9ebfd survive zsh environments where `source` is a broken function

git-subtree-dir: gitstatus
git-subtree-split: 89e9ebfd59f5ddfea4d97ae1e510273415a57a51
2020-07-04 07:00:17 +02:00
Roman Perepelitsa
9486385824 Merge commit 'ff8654ccd5b4f22710a235e7f715913277612451' 2020-07-04 07:00:17 +02:00
Andrea Carpani
882cede0ae Add option to show terraform workspace even if it's default 2020-07-03 19:05:48 +02:00
Roman Perepelitsa
e0ed693e6d add CMB* to battery directory patterns; see #858 2020-07-02 10:39:35 +02:00
Roman Perepelitsa
be66f21f53 replace NETWORK_ICON
The original icon (U+FBF1) is in fact a ligature. Some terminals do
funky things when displaying it. For example:

    print '\uFBF1 42 abc'

When executed in a VTE-based terminal with Nerd Fonts, it'll print this:

    42 X abc

Here X stands for the glyph that Nerd Fonts uses for U+FBF1.
2020-07-02 06:33:12 +02:00
Roman Perepelitsa
a88e11f54b disable background transparency in iTerm2 2020-06-28 11:11:27 +02:00
Roman Perepelitsa
db6f909958 suppress the expected error message from PlistBuddy 2020-06-28 10:45:46 +02:00
Roman Perepelitsa
ae32fd58b3 fix fvm styling that has been broken by the last commit; fixes #645 2020-06-25 11:09:19 +02:00
Roman Perepelitsa
6a1e993a05 make fvm segment work with fvm >= 1.0.0; see #842 2020-06-25 09:03:59 +02:00
Roman Perepelitsa
eadfdba707 Squashed 'gitstatus/' changes from 0d23fbd1..0f5402c8
0f5402c8 bug fix: properly propagate switches to gitstatusd

git-subtree-dir: gitstatus
git-subtree-split: 0f5402c8686a593cd21b95ade28a154dd51749bb
2020-06-22 19:18:34 +02:00
Roman Perepelitsa
1be10ebcf7 Merge commit 'eadfdba7073e625562ae02841608ea37a46e0120' 2020-06-22 19:18:34 +02:00
mundry
d394a4e038 Fix typo in config comment. (#836)
Co-authored-by: mundry <mundry@users.noreply.github.com>
2020-06-21 11:04:23 +02:00
mundry
178c3bccf0 Add font configuration instructions for Terminator. (#835)
Co-authored-by: mundry <mundry@users.noreply.github.com>
2020-06-21 11:03:36 +02:00
Roman Perepelitsa
8854cb6000 survive files with windows EOL (on linux, yes; people do that); see #827 2020-06-16 19:51:53 +02:00
Roman Perepelitsa
eda706c8ff notes 2020-06-15 17:56:32 +02:00
Roman Perepelitsa
54bbe0a0a3 speed up pasting in terminals without bracketed paste 2020-06-15 17:33:35 +02:00
Roman Perepelitsa
0717e57ff4 Squashed 'gitstatus/' changes from 1c74c8db..0d23fbd1
0d23fbd1 comments
9c19c9c4 fix a typo in install that prevents the locally built gitstatusd from being used
92fd143f doc cleanup

git-subtree-dir: gitstatus
git-subtree-split: 0d23fbd117ad6afe52fdbd96d08cf38f941be4d3
2020-06-14 10:29:29 +02:00
Roman Perepelitsa
5e5d3f5aff Merge commit '0717e57ff46201ff04e7d62cda8677e174a83be6' 2020-06-14 10:29:29 +02:00
Roman Perepelitsa
4c15d633dd respect POWERLEVEL9K_PROMPT_CHAR_ERROR_VIINS_CONTENT_EXPANSION in simple transient prompt; see #820 2020-06-14 09:21:28 +02:00
Roman Perepelitsa
3e17260622 no need to call zle -R when using z4h 2020-06-13 17:04:31 +02:00
Roman Perepelitsa
b015817892 Squashed 'gitstatus/' changes from 38d62a47..1c74c8db
1c74c8db add linux-ppc64le
968779b5 fix tar invocation
300f657c create anonymous tar archives
a5581d18 force C locale during build (perl complains)
2616cd47 support downloads to usrbin
1e6075dd enable static linking on linux
2975d25f update libgit2 ref
bea6f868 add ppc64le target

git-subtree-dir: gitstatus
git-subtree-split: 1c74c8db0f422aa6f95ced878351e0c6944a9bd6
2020-06-13 11:15:44 +02:00
Roman Perepelitsa
4d14f9e0ba Merge commit 'b0158178925484c058e6175e174b639237532c63' 2020-06-13 11:15:44 +02:00
Roman Perepelitsa
faa510d54c explicitly mention that POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND overrides POWERLEVEL9K_ASDF_FOREGROUND; see #817 2020-06-12 13:00:03 +02:00
Roman Perepelitsa
a43b1b34d8 add an icon and asdf colors for julia; see #817 2020-06-12 12:55:02 +02:00
Roman Perepelitsa
e2196ce32e match terminal escape sequences in instant prompt output more aggressively 2020-06-12 08:30:03 +02:00
Roman Perepelitsa
05eaf8162c tweak omz update pattern; maybe it'll help with #729 2020-06-11 15:29:47 +02:00
Roman Perepelitsa
fb5a0a6cca make error message about incorrect powerlevel10k loading visible in more cases 2020-06-11 11:06:02 +02:00
Roman Perepelitsa
fa2e337cbd Squashed 'gitstatus/' changes from 3050dfca8..38d62a475
38d62a475 fix gitstatus on older zsh versions

git-subtree-dir: gitstatus
git-subtree-split: 38d62a475fadf5f8d418fc772d680ba234ba8609
2020-06-10 07:57:51 +02:00
Roman Perepelitsa
b7d90c8467 Merge commit 'fa2e337cbdd88ccd297d25fa9847225211801d3e' 2020-06-10 07:57:51 +02:00
Roman Perepelitsa
e3f582f246 survive people explicitly disabling conda prompt and then expecting to see it 2020-06-09 21:33:10 +02:00
Roman Perepelitsa
c6e599ddd5 fix a typo in remote-git-url => icon conversion (bitbucket was shows as github); see #808 2020-06-09 20:25:55 +02:00
Roman Perepelitsa
a700031279 don't start gitstatusd from instant prompt if there is no git command 2020-06-09 13:49:03 +02:00
Roman Perepelitsa
a3c1b7164b Squashed 'gitstatus/' changes from 830aaa999..3050dfca8
3050dfca8 add `command` in front of commands

git-subtree-dir: gitstatus
git-subtree-split: 3050dfca875a8070aaa0dc890afcc756574078c6
2020-06-09 13:26:38 +02:00
Roman Perepelitsa
00cf3b1167 Merge commit 'a3c1b7164be115f097682088c6feac283c40a1f2' 2020-06-09 13:26:38 +02:00
Roman Perepelitsa
d75147503e remove git from POWERLEVEL9K_VCS_BACKENDS if there is no git command 2020-06-09 13:08:12 +02:00
Roman Perepelitsa
6e120b9eec don't initialize vcs if there is no git command 2020-06-09 12:58:48 +02:00
Roman Perepelitsa
b93f9663c5 Squashed 'gitstatus/' changes from ec9c39499..830aaa999
830aaa999 try wget without --no-config if it fails with

git-subtree-dir: gitstatus
git-subtree-split: 830aaa99976c0f5addedf336414f9bf82e2699d6
2020-06-08 10:52:47 +02:00
Roman Perepelitsa
bf88ce9120 Merge commit 'b93f9663c5e4e9d914c4389fc405bc7ec7382b57' 2020-06-08 10:52:47 +02:00
Roman Perepelitsa
54c9822834 Squashed 'gitstatus/' changes from 643091154..ec9c39499
ec9c39499 don't download gitstatusd if there is a bad version in usrbin

git-subtree-dir: gitstatus
git-subtree-split: ec9c39499a96ea8b181db15c76df5089959d3111
2020-06-08 09:41:59 +02:00
Roman Perepelitsa
ed9d5a7088 Merge commit '54c98228342043612ea83eaaf13ac13659c26295' 2020-06-08 09:41:59 +02:00
Roman Perepelitsa
05dad31f3f add uninstall instructions for arch linux 2020-06-06 18:39:43 +02:00
Roman Perepelitsa
b2be33d556 fix formatting 2020-06-06 18:36:52 +02:00
Roman Perepelitsa
04656da7c1 add install and update instructions for arch linux 2020-06-06 18:31:48 +02:00
Roman Perepelitsa
50dec9f9f5 default to POWERLEVEL9K_MODE=ascii if there is no UTF-8 locale 2020-06-06 09:16:26 +02:00
Roman Perepelitsa
7760aa66d7 don't unset POWERLEVEL9K_GITSTATUS_DIR in config templates 2020-06-03 18:30:40 +02:00
Roman Perepelitsa
9fd719c834 Squashed 'gitstatus/' changes from a827370d7..643091154
643091154 suppress rc files for curl and wget; see #146

git-subtree-dir: gitstatus
git-subtree-split: 6430911548516bd9bb0e4a5088993302d45cc499
2020-06-03 09:56:40 +02:00
Roman Perepelitsa
2ba6182373 Merge commit '9fd719c834910c9734b0def927cc079654943a8d' 2020-06-03 09:56:40 +02:00
Roman Perepelitsa
f82d0de0d3 remove spurious error messages from _p9k_worker_stop 2020-06-03 09:22:15 +02:00
Roman Perepelitsa
2b1d0e599c make todo segment work if todo-cli is installed from apt; see #785 2020-06-02 10:30:35 +02:00
Roman Perepelitsa
62c0a12a10 Squashed 'gitstatus/' changes from ab1aea155..a827370d7
a827370d7 suppress error message from redirect

git-subtree-dir: gitstatus
git-subtree-split: a827370d7f11f6e6aac4a044efaef6a99ab1f1f4
2020-06-01 16:30:50 +02:00
Roman Perepelitsa
537c2b04e1 Merge commit '62c0a12a10518607814fa661905b3c8ec0f16590' 2020-06-01 16:30:50 +02:00
Roman Perepelitsa
55a9f366a8 typo in docs 2020-06-01 07:32:48 +02:00
Roman Perepelitsa
801bfbb294 jump straight to ascii if TERM is "dump" or "linux" 2020-05-31 11:49:03 +02:00
Roman Perepelitsa
285bf7ba60 ignore POWERLEVEL9K_GITSTATUS_DIR when deciding whether to auto-trigger the wizard 2020-05-31 11:45:21 +02:00
Roman Perepelitsa
189ecf8e16 remove traling ")" from conda even if it is not followed by space 2020-05-30 18:48:03 +02:00
Roman Perepelitsa
094d1b3a47 Squashed 'gitstatus/' changes from 45489634e..ab1aea155
ab1aea155 remove "this command failed" message; it makes users to post truncated messages that are useless for debugging

git-subtree-dir: gitstatus
git-subtree-split: ab1aea155f051ecab8b70154f136eef81976dd25
2020-05-30 12:58:18 +02:00
Roman Perepelitsa
127737816a Merge commit '094d1b3a47cf40746807820cababab3a829ac334' 2020-05-30 12:58:18 +02:00
Roman Perepelitsa
a4a71cff9e speed up pasting in terminals without bracketed paste; see #568 2020-05-29 14:43:43 +02:00
Roman Perepelitsa
a3d887cd43 add __p9k_root_dir and GITSTATUS_AUTO_INSTALL to param_pat 2020-05-29 09:25:40 +02:00
romkatv
f3fb34dd99 more conservative instant prompt activation and cleanup on premature shell exit; see #770 2020-05-28 08:58:53 +02:00
romkatv
d8d6efc4ec add more details to uninstall instructions 2020-05-27 07:41:54 +02:00
romkatv
c9e3cfe5db add rm -f ~/.p10k.zsh to uninistall instructions 2020-05-27 07:37:19 +02:00
romkatv
937204640a add uninistall instructions 2020-05-27 07:35:31 +02:00
romkatv
86d980cdb5 Squashed 'gitstatus/' changes from be42ea1be..45489634e
45489634e check for cygwin via OSTYPE

git-subtree-dir: gitstatus
git-subtree-split: 45489634e1b9a2a11fc8dc94482516142d61a9c8
2020-05-26 21:50:05 +02:00
romkatv
b38a7bf4af Merge commit '86d980cdb5d8eed683322f61db7c22c02640dd67' 2020-05-26 21:50:05 +02:00
romkatv
102aefadab Squashed 'gitstatus/' changes from 331e9ff65..be42ea1be
be42ea1be support split gitstatusd release binaries

git-subtree-dir: gitstatus
git-subtree-split: be42ea1be4a7938464aea568d719bf4c01e4b1f4
2020-05-26 20:51:58 +02:00
romkatv
ee68d4db4b Merge commit '102aefadaba271a48a74f13953dd88067f9862c6' 2020-05-26 20:51:58 +02:00
romkatv
b3875f5193 avoid spurious error (even if harmless and invisible) on worker cleanup 2020-05-26 20:15:53 +02:00
romkatv
ad18cd78db call taskwarrior via command task 2020-05-26 13:47:34 +02:00
romkatv
4cd2700a85 bug fix: update taskwarrior when a pending task becomes overdue; see #763 2020-05-26 13:45:56 +02:00
romkatv
7d35c7ebb8 remove execute permission from wizard.zsh 2020-05-26 11:31:11 +02:00
romkatv
6696212dde Squashed 'gitstatus/' changes from cc956ca78..331e9ff65
331e9ff65 docs: remove packaging instructions and instead discourage it
c1fff558c install: add a link to #compiling docs if no gitstatusd is found
8632b85ab build: use extra optimization and hardening flags
c0a71c757 mbuild: don't stop on first failure
52e0359ec build: don't link statically when doing a local linux build

git-subtree-dir: gitstatus
git-subtree-split: 331e9ff65df96e7423c0b7a01e5e98d6c6b7d428
2020-05-26 11:14:53 +02:00
romkatv
90df734bf8 Merge commit '6696212ddecbb5ffae8d71467518cc5651c90b3f' 2020-05-26 11:14:53 +02:00
romkatv
ba6c79e277 docs: remove packaging instructions and instead discourage it 2020-05-26 11:14:33 +02:00
romkatv
9a4bbcd930 Squashed 'gitstatus/' changes from 0127fd26a..cc956ca78
cc956ca78 interrupt p10k instant prompt before printing "gitstatus failed to initialize" error
db3603bc8 improve "gitstatus failed to initialize" error message
e164594ea work around bugs in cygwin
55af96ade cleanup + todo
0e70dbc56 add an empty line before the error message

git-subtree-dir: gitstatus
git-subtree-split: cc956ca7878ef6d00bb1f35861864d0a40ffac75
2020-05-26 08:37:25 +02:00
romkatv
619ddaf6f1 Merge commit '9a4bbcd930afa54bc605e3020fb38353161e5a84' 2020-05-26 08:37:25 +02:00
romkatv
2ade5d786b disable instant prompt when gitstatus fails to initialize 2020-05-26 08:37:05 +02:00
romkatv
ed3287b737 Revert "mention that arch linux packages for powerlevel10k are broken and should not be used"
This reverts commit 6b69030bfb.
2020-05-25 17:07:41 +02:00
romkatv
5e9a4eb072 asdf: filter multiple versions the same way as upstream; see #764 2020-05-25 12:18:02 +02:00
romkatv
644488afcc change the default anaconda format in config templates
Also document how to configure it.

See #762.
2020-05-25 07:31:19 +02:00
romkatv
45eeb08fc3 add P9K_ANACONDA_PYTHON_VERSION 2020-05-25 07:26:52 +02:00
romkatv
a963533cbd mention that powerlevel10k once again can be installed from AUR 2020-05-24 13:08:05 +02:00
romkatv
be83ec430c replace Ⅴ (roman numeral 5) with V (ascii); see #754 2020-05-23 14:20:37 +02:00
romkatv
6b69030bfb mention that arch linux packages for powerlevel10k are broken and should not be used 2020-05-22 21:36:40 +02:00
romkatv
ee44f9e112 Squashed 'gitstatus/' changes from 063ed450..0127fd26
0127fd26 set user.name in the test git repo
00564e95 Merge pull request #135 from Aloxaf/Aloxaf-patch-1
6003278c build: no gpg sign

git-subtree-dir: gitstatus
git-subtree-split: 0127fd26a0d102cc5d5b2b10e511fa99138d78e3
2020-05-22 08:44:25 +02:00
romkatv
936b0d6dea Merge commit 'ee44f9e112a71ef6fa5aacdf65a43862a5a89279' 2020-05-22 08:44:25 +02:00
romkatv
c713ded9e7 set P9K_TTY=old in preexec 2020-05-20 12:05:11 +02:00
romkatv
499de79a2b remove instant prompt if it corresponds to a different config 2020-05-20 12:04:49 +02:00
romkatv
8cfe934f15 Squashed 'gitstatus/' changes from 78a2ec25..063ed450
063ed450 Revert "close p10k instant prompt descriptors in daemon"

git-subtree-dir: gitstatus
git-subtree-split: 063ed45083386c98ab4d10f08794bec2a0c534d1
2020-05-20 07:07:00 +02:00
romkatv
fb0dc597fa Merge commit '8cfe934f157a69976f55a796b65d4ed0a0344719' 2020-05-20 07:07:00 +02:00
romkatv
519de2c569 Revert "close instant prompt descriptors in worker"
This reverts commit 646a826440.
2020-05-20 07:06:38 +02:00
romkatv
8e86b0c8d4 don't hide empty line when invoking zsh immediately after clear 2020-05-19 20:25:29 +02:00
romkatv
00cfdb48a8 Squashed 'gitstatus/' changes from c99d28aa..78a2ec25
78a2ec25 close p10k instant prompt descriptors in daemon

git-subtree-dir: gitstatus
git-subtree-split: 78a2ec251e99ffa48ea10bbb48ab54ba6401701c
2020-05-19 20:20:39 +02:00
romkatv
072fc38ebe Merge commit '00cfdb48a8c74150814792e4deccff2a1ce91ad0' 2020-05-19 20:20:39 +02:00
romkatv
646a826440 close instant prompt descriptors in worker 2020-05-19 20:20:21 +02:00
romkatv
fb9bc2d3f5 Merge branch 'master' of github.com:romkatv/powerlevel10k 2020-05-19 10:33:03 +02:00
romkatv
f3ae4032c1 Squashed 'gitstatus/' changes from 4c1b9564..c99d28aa
c99d28aa block SIGINT in zle callback when being used from p10k
f52fd5b5 wrap most of gitstatus_start code in `{..} always {...}` to handle SIGINT properly
e4e88a5a mention that gitstatus can be cloned from gitee.com

git-subtree-dir: gitstatus
git-subtree-split: c99d28aa6be9db6f78c99c3f56626f2afcc3a466
2020-05-19 10:25:42 +02:00
romkatv
1e2a0cc34a Merge commit 'f3ae4032c1923404afc9fd8b6af2bf28bc406d61' 2020-05-19 10:25:42 +02:00
xPMo
debacbf530 Fix warning for parameter _p9k__preexec_cmd (#735)
With `setopt warn_create_global`, I get this warning:

    _p9k_preexec2:1: scalar parameter _p9k__preexec_cmd created globally in function _p9k_preexec2
2020-05-19 10:16:12 +02:00
romkatv
e3beeea0ae block SIGINT in nested p10k calls; allow SIGINT in gitstatus_start 2020-05-19 10:10:43 +02:00
romkatv
c0ff47bea5 don't print instant prompt warning when omz update fails 2020-05-19 09:00:42 +02:00
romkatv
be5c067125 bug fix: vcs and vpn_ip weren't respecting hide/show state 2020-05-19 08:31:00 +02:00
romkatv
5d1bfe8ed7 cleanup 2020-05-19 07:59:57 +02:00
romkatv
5e932c225e bug fix: properly disable instant prompt when auto-wizard is aborted 2020-05-19 07:45:04 +02:00
romkatv
c0091537a9 fix gcloud for the case when there are multiple configurations
Fixes #731
2020-05-18 07:42:38 +02:00
romkatv
6998d06a91 pyenv: display python version alongside environment name if unequal
See #730.
2020-05-18 07:14:08 +02:00
romkatv
f96c1eb5e5 add POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false to config templates
See #730.
2020-05-18 07:06:41 +02:00
romkatv
00d2cc7237 add POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV 2020-05-18 06:58:24 +02:00
romkatv
fdac99c57c notes 2020-05-18 06:44:28 +02:00
romkatv
48c6ff4701 expand dir color faq 2020-05-17 08:28:02 +02:00
romkatv
6c83ace41c uncomment POWERLEVEL9K_DIR_BACKGROUND 2020-05-17 08:28:02 +02:00
romkatv
b62a164998 add chinese translation for the reference to gitee.com
See #124.

Thanks, @ayalhw.
2020-05-16 13:39:48 +02:00
romkatv
af0b387182 mention that powerlevel10k can be cloned from gitee.com; see #124 2020-05-16 13:29:03 +02:00
romkatv
ff18dbaf74 Squashed 'gitstatus/' changes from caf5bfd3..4c1b9564
4c1b9564 Merge pull request #128 from carlosedp/arch
1d2816f2 Add support to build on ppc64le and riscv64 archs
5614639c update version in packaging instructions
d728a1da add homebrew installation instructions; fixed #118

git-subtree-dir: gitstatus
git-subtree-split: 4c1b95643bc78dfe8e4d9566823b72881637c2ef
2020-05-15 18:06:30 +02:00
romkatv
bf0255931b Merge commit 'ff18dbaf74beb38f9e0721558f2c66552b5862a4' 2020-05-15 18:06:30 +02:00
romkatv
9f33d6567b work around bugs in zsh that break vim shell; see #717 2020-05-15 06:59:29 +02:00
romkatv
d9b90027ba disable tty echo for the whole duration of wizard 2020-05-14 20:26:16 +02:00
romkatv
2841cecaa9 fix ask_python screen in the wizard 2020-05-14 20:19:09 +02:00
romkatv
3ddb8025d5 fix POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN example 2020-05-14 12:06:46 +02:00
romkatv
accbe293cb wizard: print a message when aborted with ctrl-c 2020-05-14 11:24:53 +02:00
romkatv
574754eaf6 abort instant prompt if wizard needs to run on startup 2020-05-14 11:24:30 +02:00
romkatv
7e6abbb891 maintain _p9k_dumped_instant_prompt_sigs invariant 2020-05-14 09:55:13 +02:00
romkatv
6f5e834b8e update version in packaging instructions 2020-05-13 12:48:04 +02:00
romkatv
46c76dd707 Squashed 'gitstatus/' changes from 1a80249d..caf5bfd3
caf5bfd3 update build instructions
8bbfd153 update build instructions

git-subtree-dir: gitstatus
git-subtree-split: caf5bfd3d4b6099aeed13604936976e610a08e18
2020-05-13 10:44:36 +02:00
romkatv
1f8bd78e3a Merge commit '46c76dd7071bedab192aa65756ba09024e29da0c' 2020-05-13 10:44:36 +02:00
romkatv
d0f6cd0b50 update build instructions 2020-05-13 10:44:19 +02:00
romkatv
a6009c74a9 Squashed 'gitstatus/' changes from f81313e2..1a80249d
1a80249d build: verify libgit2 tarball sha256
74c3d56f bash: call mktemp only once and avoid `mktemp -u`
b139dec4 install: use mktemp if available
9f594d24 bash: block SIGQUIT and SIGTSTP in daemon
73f47ea3 add sha256 verification for downloaded gitstatusd; enable gitee mirror

git-subtree-dir: gitstatus
git-subtree-split: 1a80249d2b6a53fd076ae846f8fcc501183ca5a5
2020-05-13 09:40:53 +02:00
romkatv
e7f0bac67d Merge commit 'a6009c74a92f1c01d725364635aa38eba67bc15a' 2020-05-13 09:40:53 +02:00
romkatv
d23b2c3792 Squashed 'gitstatus/' changes from 901c366b..f81313e2
f81313e2 move gitee disable higher
8cfb83f3 print [ok] after download when stderr is not tty
d185a2ab disable gitee mirror until there is sha256 verification in place
fb88a401 show progress when downloading gitstatusd
ae988158 add a mirror for gitstatusd release files
c673d327 disable aliases before calling zcompile

git-subtree-dir: gitstatus
git-subtree-split: f81313e27271ee1fb3fd22b8e72382e9516776ef
2020-05-12 21:55:25 +02:00
romkatv
71f5f42997 Merge commit 'd23b2c3792712662408c332a52547dc903cc5c43' 2020-05-12 21:55:25 +02:00
romkatv
16b44fd9da add p10k clear-instant-prompt; disable instant prompt when gitstatus is likely to download a new binary 2020-05-12 21:52:51 +02:00
romkatv
d075b5a5cb disable aliases before calling zcompile 2020-05-12 08:34:01 +02:00
romkatv
bda74564e3 Squashed 'gitstatus/' changes from d3bc3e34..901c366b
901c366b survive `cd` being a function that fails instead of doing its job; see #123

git-subtree-dir: gitstatus
git-subtree-split: 901c366b8ecb736e579784b132442ee51bcb0840
2020-05-11 19:27:10 +02:00
romkatv
f43d8b9e0e Merge commit 'bda74564e3b1b9bbaa77f840215f7d99d0b77ed8' 2020-05-11 19:27:10 +02:00
romkatv
1db7094966 Squashed 'gitstatus/' changes from cd5673b4..d3bc3e34
d3bc3e34 work around bugs in curl on cygwin; see #706
61a67c66 print error message from curl/wget before the summary

git-subtree-dir: gitstatus
git-subtree-split: d3bc3e346cc71ee1a29d0c4316449feb3dcb3116
2020-05-11 18:19:51 +02:00
romkatv
e27a8fbcbf Merge commit '1db7094966bc86658166709db26b7750d8fc2821' 2020-05-11 18:19:51 +02:00
romkatv
d047ed87a1 Squashed 'gitstatus/' changes from 6a4f0fc0..cd5673b4
cd5673b4 increase default gitstatus_start timeout from 5s to 30s

git-subtree-dir: gitstatus
git-subtree-split: cd5673b4971bf1a6d9aa8d57963e954e619b950e
2020-05-11 15:29:08 +02:00
romkatv
1a976f989a Merge commit 'd047ed87a1093b27aff3324ba57860c9c42f7b65' 2020-05-11 15:29:08 +02:00
romkatv
bc85e46f8b Squashed 'gitstatus/' changes from 32b7b674..6a4f0fc0
6a4f0fc0 add missing \n in error messages
d2fd0166 update build instructions

git-subtree-dir: gitstatus
git-subtree-split: 6a4f0fc0cadc214bbe4051bf87d87033558aeed8
2020-05-10 17:44:24 +02:00
romkatv
75bbc4439f Merge commit 'bc85e46f8b8a548379144732a3218bfe686f2f63' 2020-05-10 17:44:24 +02:00
romkatv
b305649e53 docs 2020-05-10 16:58:41 +02:00
romkatv
1c39df6280 document how to package powerlevel10k for distribution 2020-05-10 16:38:06 +02:00
romkatv
0acaefe57f Squashed 'gitstatus/' changes from 6b9ba17..32b7b67
32b7b67 silly typo bug dammit!

git-subtree-dir: gitstatus
git-subtree-split: 32b7b674326b109bbe639d6dc662ede2d4df3ad2
2020-05-10 16:17:00 +02:00
romkatv
ec81904184 Merge commit '0acaefe57f32e2afffab13a988d8b29d3f6eb1aa' 2020-05-10 16:17:00 +02:00
romkatv
1531d6e543 Squashed 'gitstatus/' content from commit 6b9ba17
git-subtree-dir: gitstatus
git-subtree-split: 6b9ba179c6655286c4c399e7926d5098dd6bd706
2020-05-10 15:58:05 +02:00
romkatv
97fac973af Merge commit '1531d6e5439daae01627b2645684876b75eaf5eb' as 'gitstatus' 2020-05-10 15:58:05 +02:00
romkatv
c159f3aaef nuke gitstatus (going to replace with subtree) 2020-05-10 15:56:56 +02:00
romkatv
9fc454fc08 mangle gitstatus function names 2020-05-10 13:31:28 +02:00
romkatv
317a9896d8 bump version 2020-05-10 07:28:07 +02:00
romkatv
a238426d97 bug fix: preserve empty array elements in _p9k_declare (see #678) 2020-05-10 06:54:43 +02:00
romkatv
22396b86f3 when shortening with truncate_to_unique, never produce just dots for a segment 2020-05-09 19:53:01 +02:00
Roman Perepelitsa
3de6154ee7 notes 2020-05-09 15:13:01 +02:00
romkatv
f2cffc978c better p10k help display 2020-05-09 11:30:11 +02:00
romkatv
ca114f2508 work around more zsh bugs w.r.t. SIGWINCH; see #694 2020-05-08 06:23:17 +02:00
Olivier MARY
541d573551 Fix _p9k_precmd:trap:17: undefined signal: return (#695)
Signed-off-by: Olivier MARY <olivier@omary.fr>
2020-05-07 11:02:31 +02:00
romkatv
94bbbc1ca8 ignore cursor shape escape sequence in the output when instant prompt is active 2020-05-07 09:47:18 +02:00
romkatv
dc1f023344 work around more zsh bugs w.r.t. local traps 2020-05-06 23:13:56 +02:00
romkatv
aceb1c5c77 fix a typo: ${x:-} => ${x:--} 2020-05-06 23:00:33 +02:00
romkatv
9a47e80ff9 dump state after every initialization but never synchronously 2020-05-06 21:31:48 +02:00
romkatv
5d41bf4703 block SIGINT while powerlevel10k is running 2020-05-06 21:05:01 +02:00
romkatv
c7efd5badb always dump instant prompt whenever dumping state 2020-05-06 12:50:17 +02:00
romkatv
fc1fd9beb5 delete instant prompt cache when config changes while instant prompt is active 2020-05-06 12:02:15 +02:00
romkatv
30457e2be6 allow patterns in POWERLEVEL9K_VIRTUALENV_GENERIC_NAMES; see #686 2020-05-05 11:52:36 +02:00
romkatv
3688d12857 cd => cd -q to survive weird chpwd hooks 2020-05-04 17:58:57 +02:00
romkatv
3f08f1392a pull upstream changes from gitstatus 2020-05-04 17:48:35 +02:00
romkatv
48a36ebc17 pull upstream changes from gitstatus 2020-05-04 17:44:03 +02:00
romkatv
2cc815deaf 'cd /' in the worker to avoid locking cwd 2020-05-04 13:24:22 +02:00
romkatv
b2c77eb370 pull upstream changes from gitstatus 2020-05-04 13:22:59 +02:00
romkatv
9b1353f112 perform single word shell expansions on PROMPT_EOL_MARK (see #683) 2020-05-03 07:05:47 +02:00
romkatv
1b5ee70b3d pull upstream changes from gitstatus 2020-05-02 16:45:11 +02:00
romkatv
06603a2d62 work around bugs in kitty where it hangs on close
All terminals quit when the child process terminates. Except
kitty. Kitty doesn't quit until there are no open file
descriptors to the tty.

And the best thing? This is "better". Having the balls to
claim this nasty bug as feature is worthy of admiration.
2020-05-02 16:30:47 +02:00
romkatv
fdb0bb6af7 set P9K_PYENV_PYTHON_VERSION in prompt_pyenv; see #679 2020-05-02 14:32:17 +02:00
romkatv
459af1f238 allow dir styling based on the pair: $PWD and whether it's writable
fixes #678
2020-05-02 13:36:40 +02:00
Roman Perepelitsa
f14ffcff2c typo 2020-04-29 21:51:01 +02:00
romkatv
7c4b0f36c0 support POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=first; see #669 2020-04-28 21:14:06 +02:00
romkatv
eb66d0e66a pull upstream changes from gitstatus 2020-04-28 19:56:27 +02:00
romkatv
10ba0e45be pull upstream changes from gitstatus 2020-04-28 16:03:56 +02:00
96 changed files with 11820 additions and 1615 deletions

14
Makefile Normal file
View File

@@ -0,0 +1,14 @@
ZSH := $(shell command -v zsh 2> /dev/null)
all:
zwc:
$(MAKE) -C gitstatus zwc
$(or $(ZSH),:) -fc 'for f in *.zsh-theme internal/*.zsh; do zcompile -R -- $$f.zwc $$f || exit; done'
minify:
$(MAKE) -C gitstatus minify
rm -rf -- .git .gitattributes .gitignore LICENSE Makefile README.md font.md powerlevel10k.png
pkg: zwc
$(MAKE) -C gitstatus pkg

429
README.md
View File

@@ -89,10 +89,10 @@ Note how the effect of every command is instantly reflected by the very next pro
| Command | Prompt Indicator | Meaning |
|-------------------------------|:----------------:|----------------------------------------------------------------------:|
| `timew start hack linux` | `🛡️ hack linux` | time tracking enabled in [timewarrior](https://timewarrior.net/) |
| `timew start hack linux` | ` hack linux` | time tracking enabled in [timewarrior](https://timewarrior.net/) |
| `touch x y` | `?2` | 2 untracked files in the Git repo |
| `rm COPYING` | `!1` | 1 unstaged change in the Git repo |
| `echo 2.7.3 >.python-version` | `🐍 2.7.3` | the current python version in [pyenv](https://github.com/pyenv/pyenv) |
| `echo 3.7.3 >.python-version` | `🐍 3.7.3` | the current python version in [pyenv](https://github.com/pyenv/pyenv) |
Other Zsh themes capable of displaying the same information either produce prompt lag or print
prompt that doesn't reflect the current state of the system and then refresh it later. With
@@ -183,8 +183,8 @@ Here's the relevant parameter for kubernetes context:
```zsh
# Show prompt segment "kubecontext" only when the command you are typing
# invokes kubectl, helm, kubens, kubectx, oc, istioctl or kogito.
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
# invokes kubectl, helm, kubens, kubectx, oc, istioctl, kogito, k9s, helmfile, flux, fluxctl or stern.
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern'
```
To customize when different prompt segments are shown, open `~/.p10k.zsh`, search for
@@ -266,7 +266,7 @@ enable as many segments as you like. It won't slow down your prompt or Zsh start
| `os_icon` | your OS logo (apple for macOS, swirl for debian, etc.) |
| `dir` | current working directory |
| `vcs` | Git repository status |
| `prompt_char` | multi-functional prompt symbol; changes depending on vi mode: ``, ``, ``, `▶` for insert, command, visual and replace mode respectively; turns red on error |
| `prompt_char` | multi-functional prompt symbol; changes depending on vi mode: ``, ``, `V`, `▶` for insert, command, visual and replace mode respectively; turns red on error |
| `context` | user@hostname |
| `status` | exit code of the last command |
| `command_execution_time` | duration (wall time) of the last command |
@@ -299,6 +299,7 @@ enable as many segments as you like. It won't slow down your prompt or Zsh start
| `package` | `name@version` from [package.json](https://docs.npmjs.com/files/package.json) |
| `kubecontext` | current [kubernetes](https://kubernetes.io/) context |
| `terraform` | [terraform](https://www.terraform.io) workspace |
| `terraform_version` | [terraform](https://www.terraform.io) version |
| `aws` | [aws profile](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) |
| `aws_eb_env` | [aws elastic beanstalk](https://aws.amazon.com/elasticbeanstalk/) environment |
| `azure` | [azure](https://docs.microsoft.com/en-us/cli/azure) account name |
@@ -307,6 +308,7 @@ enable as many segments as you like. It won't slow down your prompt or Zsh start
| `nordvpn` | [nordvpn](https://nordvpn.com/) connection status |
| `ranger` | [ranger](https://github.com/ranger/ranger) shell |
| `nnn` | [nnn](https://github.com/jarun/nnn) shell |
| `xplr` | [xplr](https://github.com/sayanarijit/xplr) shell |
| `vim_shell` | [vim](https://www.vim.org/) shell (`:sh`) |
| `midnight_commander` | [midnight commander](https://midnight-commander.org/) shell |
| `nix_shell` | [nix shell](https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html) indicator |
@@ -348,7 +350,15 @@ Powerlevel10k.
```zsh
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>! ~/.zshrc
echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
```
Users in mainland China can use the official mirror on gitee.com for faster download.<br>
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.
```zsh
git clone --depth=1 https://gitee.com/romkatv/powerlevel10k.git ~/powerlevel10k
echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
```
This is the simplest kind of installation and it works even if you are using a plugin manager. Just
@@ -357,11 +367,17 @@ make sure to disable the current theme in your plugin manager. See
### Oh My Zsh
```zsh
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/themes/powerlevel10k
```
1. Clone the repository:
```zsh
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
```
Users in mainland China can use the official mirror on gitee.com for faster download.<br>
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.
Set `ZSH_THEME="powerlevel10k/powerlevel10k"` in `~/.zshrc`.
```zsh
git clone --depth=1 https://gitee.com/romkatv/powerlevel10k.git ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k
```
2. Set `ZSH_THEME="powerlevel10k/powerlevel10k"` in `~/.zshrc`.
### Prezto
@@ -369,7 +385,7 @@ Add `zstyle :prezto:module:prompt theme powerlevel10k` to `~/.zpreztorc`.
### Zim
Add `zmodule romkatv/powerlevel10k` to `~/.zimrc` and run `zimfw install`.
Add `zmodule romkatv/powerlevel10k --use degit` to `~/.zimrc` and run `zimfw install`.
### Antibody
@@ -406,16 +422,24 @@ supported by Powerlevel10k.
```zsh
brew install romkatv/powerlevel10k/powerlevel10k
echo 'source /usr/local/opt/powerlevel10k/powerlevel10k.zsh-theme' >>! ~/.zshrc
echo "source $(brew --prefix)/opt/powerlevel10k/powerlevel10k.zsh-theme" >>~/.zshrc
```
### Arch Linux
```zsh
pacman -S --noconfirm zsh-theme-powerlevel10k
echo 'source /usr/share/zsh-theme-powerlevel10k/powerlevel10k.zsh-theme' >>! ~/.zshrc
yay -S --noconfirm zsh-theme-powerlevel10k-git
echo 'source /usr/share/zsh-theme-powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
```
[zsh-theme-powerlevel10k-git](https://aur.archlinux.org/packages/zsh-theme-powerlevel10k-git/)
referenced above is the official Powerlevel10k package.
There is also [zsh-theme-powerlevel10k](
https://www.archlinux.org/packages/community/x86_64/zsh-theme-powerlevel10k/) community package.
Historically, [it has been breaking often and for extended periods of time](
https://github.com/romkatv/powerlevel10k/pull/786). **Do not use it.**
## Configuration
### For new users
@@ -477,7 +501,7 @@ the default system fonts. The full choice of style options is available only whe
👇 **Recommended font**: Meslo Nerd Font patched for Powerlevel10k. 👇
### <a name='recommended-meslo-nerd-font-patched-for-powerlevel10k'></a>Meslo Nerd Font patched for Powerlevel10k
### <a name='recommended-meslo-nerd-font-patched-for-powerlevel10k'></a><a name='font'></a>Meslo Nerd Font patched for Powerlevel10k
Gorgeous monospace font designed by Jim Lyles for Bitstream, customized by the same for Apple,
further customized by André Berg, and finally patched by yours truly with customized scripts
@@ -496,48 +520,100 @@ If you are using a different terminal, proceed with manual font installation.
#### Manual font installation
Download these four ttf files:
- [MesloLGS NF Regular.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Regular.ttf)
- [MesloLGS NF Bold.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold.ttf)
- [MesloLGS NF Italic.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Italic.ttf)
- [MesloLGS NF Bold Italic.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold%20Italic.ttf)
Double-click on each file and click "Install". This will make `MesloLGS NF` font available to all
applications on your system. Configure your terminal to use this font:
- **iTerm2**: Open *iTerm2 → Preferences → Profiles → Text* and set *Font* to `MesloLGS NF`.
Alternatively, type `p10k configure` and answer `Yes` when asked whether to install
*Meslo Nerd Font*.
- **Apple Terminal** Open *Terminal → Preferences → Profiles → Text*, click *Change* under *Font*
and select `MesloLGS NF` family.
- **Hyper**: Open *Hyper → Edit → Preferences* and change the value of `fontFamily` under
`module.exports.config` to `MesloLGS NF`.
- **Visual Studio Code**: Open *File → Preferences → Settings*, enter
`terminal.integrated.fontFamily` in the search box and set the value to `MesloLGS NF`.
- **GNOME Terminal** (the default Ubuntu terminal): Open *Terminal → Preferences* and click on the
selected profile under *Profiles*. Check *Custom font* under *Text Appearance* and select
`MesloLGS NF Regular`.
- **Konsole**: Open *Settings → Edit Current Profile → Appearance*, click *Select Font* and select
`MesloLGS NF Regular`.
- **Tilix**: Open *Tilix → Preferences* and click on the selected profile under *Profiles*. Check
*Custom font* under *Text Appearance* and select `MesloLGS NF Regular`.
- **Windows Console Host** (the old thing): Click the icon in the top left corner, then
*Properties → Font* and set *Font* to `MesloLGS NF`.
- **Windows Terminal** (the new thing): Open *Settings* (`Ctrl+,`), search for `fontFace` and set
value to `MesloLGS NF` for every profile.
- **Termux**: Type `p10k configure` and answer `Yes` when asked whether to install
*Meslo Nerd Font*.
- **Blink** Type `config`, go to *Appearance*, tap *Add a new font*, tap *Open Gallery*, select
*MesloLGS NF.css*, tap *import* and type `exit` in the home view to reload the font.
- **Terminus**: Open *Settings → Appearance* and set *Font* to `MesloLGS NF`.
**IMPORTANT:** Run `p10k configure` after changing terminal font. The old `~/.p10k.zsh` may work
incorrectly with the new font.
1. Download these four ttf files:
- [MesloLGS NF Regular.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Regular.ttf)
- [MesloLGS NF Bold.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold.ttf)
- [MesloLGS NF Italic.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Italic.ttf)
- [MesloLGS NF Bold Italic.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold%20Italic.ttf)
1. Double-click on each file and click "Install". This will make `MesloLGS NF` font available to all
applications on your system.
1. Configure your terminal to use this font:
- **iTerm2**: Type `p10k configure` and answer `Yes` when asked whether to install
*Meslo Nerd Font*. Alternatively, open *iTerm2 → Preferences → Profiles → Text* and set *Font* to
`MesloLGS NF`.
- **Apple Terminal**: Open *Terminal → Preferences → Profiles → Text*, click *Change* under *Font*
and select `MesloLGS NF` family.
- **Hyper**: Open *Hyper → Edit → Preferences* and change the value of `fontFamily` under
`module.exports.config` to `MesloLGS NF`.
- **Visual Studio Code**: Open *File → Preferences → Settings* (PC) or
*Code → Preferences → Settings* (Mac), enter `terminal.integrated.fontFamily` in the search box at
the top of *Settings* tab and set the value below to `MesloLGS NF`.
Consult [this screenshot](
https://raw.githubusercontent.com/romkatv/powerlevel10k-media/389133fb8c9a2347929a23702ce3039aacc46c3d/visual-studio-code-font-settings.jpg)
to see how it should look like or see [this issue](
https://github.com/romkatv/powerlevel10k/issues/671) for extra information.
- **GNOME Terminal** (the default Ubuntu terminal): Open *Terminal → Preferences* and click on the
selected profile under *Profiles*. Check *Custom font* under *Text Appearance* and select
`MesloLGS NF Regular`.
- **Konsole**: Open *Settings → Edit Current Profile → Appearance*, click *Select Font* and select
`MesloLGS NF Regular`.
- **Tilix**: Open *Tilix → Preferences* and click on the selected profile under *Profiles*. Check
*Custom font* under *Text Appearance* and select `MesloLGS NF Regular`.
- **Windows Console Host** (the old thing): Click the icon in the top left corner, then
*Properties → Font* and set *Font* to `MesloLGS NF`.
- **Windows Terminal** by Microsoft (the new thing): Open `settings.json` (<kbd>Ctrl+Shift+,</kbd>),
search for `fontFace` and set the value to `MesloLGS NF` for every profile. If you don't find
`fontFace`, add it under *profiles → defaults*. See [this settings file](
https://raw.githubusercontent.com/romkatv/dotfiles-public/aba0e6c4657d705ed6c344d700d659977385f25c/dotfiles/microsoft-terminal-settings.json)
for example.
- **IntelliJ** (and other IDEs by Jet Brains): Open *IDE → Edit → Preferences → Editor →
Color Scheme → Console Font*. Select *Use console font instead of the default* and set the font
name to `MesloLGS NF`.
- **Termux**: Type `p10k configure` and answer `Yes` when asked whether to install
*Meslo Nerd Font*.
- **Blink**: Type `config`, go to *Appearance*, tap *Add a new font*, tap *Open Gallery*, select
*MesloLGS NF.css*, tap *import* and type `exit` in the home view to reload the font.
- **Terminus**: Open *Settings → Appearance* and set *Font* to `MesloLGS NF`.
- **Terminator**: Open *Preferences* using the context menu. Under *Profiles* select the *General*
tab (should be selected already), uncheck *Use the system fixed width font* (if not already)
and select `MesloLGS NF Regular`. Exit the Preferences dialog by clicking *Close*.
- **Guake**: Right Click on an open terminal and open *Preferences*. Under *Appearance*
tab, uncheck *Use the system fixed width font* (if not already) and select `MesloLGS NF Regular`.
Exit the Preferences dialog by clicking *Close*.
- **MobaXterm**: Open *Settings* → *Configuration* → *Terminal* → (under *Terminal look and feel*)
and change *Font* to `MesloLGS NF`.
- **Asbrú Connection Manager**: Open *Preferences → Local Shell Options → Look and Feel*, enable
*Use these personal options* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
To change the font for the remote host connections, go to *Preferences → Terminal Options →
Look and Feel* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
- **WSLtty**: Right click on an open terminal and then on *Options*. In the *Text* section, under
*Font*, click *"Select..."* and set Font to `MesloLGS NF Regular`.
- **Yakuake**: Click *≡* → *Manage Profiles* → *New* → *Appearance*. Click *Choose* next to the
*Font* dropdown, select `MesloLGS NF` and click *OK*. Click *OK* to save the profile. Select the
new profile and click *Set as Default*.
- **Alacritty**: Create or open `~/.config/alacritty/alacritty.yml` and add the following section
to it:
```yaml
font:
normal:
family: "MesloLGS NF"
```
- **Kitty**: Create or open `~/.config/kitty/kitty.conf` and add the following line to it:
```text
font_family MesloLGS NF
```
Restart Kitty by closing all sessions and opening a new session.
- **WezTerm**: Create or open `$HOME/.config/wezterm/wezterm.lua` and add the following:
```lua
local wezterm = require 'wezterm';
return {
font = wezterm.font("MesloLGS NF"),
}
```
If the file already exists, only add the line with the font to the existing return.
Also add the first line if it is not already present.
- **urxvt**: Create or open `~/.Xresources` and add the following line to it:
```text
URxvt.font: xft:MesloLGS NF:size=11
```
You can adjust the font size to your preference. After changing the configuration use `xrdb ~/.Xresources` to reload the config.
The new config is applied for all new terminals.
1. Run `p10k configure` to generate a new `~/.p10k.zsh`. The old config may work
incorrectly with the new font.
_Using a different terminal and know how to set the font for it? Share your knowledge by sending a
PR to expand the list!_
@@ -573,22 +649,102 @@ Powerlevel10k is released under the
The command to update Powerlevel10k depends on how it was installed.
| Installation | Update command |
|-------------------------|------------------------------------------------|
| [Manual](#manual) | `git -C ~/powerlevel10k pull` |
| [Oh My Zsh](#oh-my-zsh) | `git -C $ZSH_CUSTOM/themes/powerlevel10k pull` |
| [Prezto](#prezto) | `zprezto-update` |
| [Zim](#zim) | `zimfw update` |
| [Antigen](#antigen) | `antigen update` |
| [Zplug](#zplug) | `zplug update` |
| [Zgen](#zgen) | `zgen update` |
| [Zplugin](#zplugin) | `zplugin update` |
| [Zinit](#zinit) | `zinit update` |
| [Homebrew](#homebrew) | `brew update && brew upgrade` |
| Installation | Update command |
|---------------------------|-------------------------------------------------------------|
| [Manual](#manual) | `git -C ~/powerlevel10k pull` |
| [Oh My Zsh](#oh-my-zsh) | `git -C ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k pull` |
| [Prezto](#prezto) | `zprezto-update` |
| [Zim](#zim) | `zimfw update` |
| [Antigen](#antigen) | `antigen update` |
| [Zplug](#zplug) | `zplug update` |
| [Zgen](#zgen) | `zgen update` |
| [Zplugin](#zplugin) | `zplugin update` |
| [Zinit](#zinit) | `zinit update` |
| [Homebrew](#homebrew) | `brew update && brew upgrade` |
| [Arch Linux](#arch-linux) | `yay -S --noconfirm zsh-theme-powerlevel10k-git` |
**IMPORTANT**: Restart Zsh after updating Powerlevel10k. [Do not use `source ~/.zshrc`](
#weird-things-happen-after-typing-source-zshrc).
### How do I uninstall Powerlevel10k?
1. Remove all references to "p10k" from `~/.zshrc`. You might have this snippet at the top:
```zsh
if [[ -r "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh" ]]; then
source "${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh"
fi
```
And this at the bottom:
```zsh
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
```
These are added by the [configuration wizard](#configuration-wizard). Remove them.
2. Remove all references to "powerlevel10k" from `~/.zshrc`, `~/.zpreztorc` and `~/.zimrc` (some
of these files may be missing -- this is normal). These references have been added manually by
yourself when installing Powerlevel10k. Refer to the [installation instructions](#installation)
if you need a reminder.
3. Verify that all references to "p10k" and "powerlevel10k" are gone from `~/.zshrc`, `~/.zpreztorc`
and `~/.zimrc`.
```zsh
grep -E 'p10k|powerlevel10k' ~/.zshrc ~/.zpreztorc ~/.zimrc 2>/dev/null
```
If this command produces output, there are still references to "p10k" or "powerlevel10k". You
need to remove them.
4. Delete Powerlevel10k configuration file. This file is created by the
[configuration wizard](#configuration-wizard) and may contain manual edits by yourself.
```zsh
rm -f ~/.p10k.zsh
```
5. Delete Powerlevel10k source files. These files have been downloaded when you've installed
Powerlevel10k. The command to delete them depends on which installation method you'd chosen.
Refer to the [installation instructions](#installation) if you need a reminder.
| Installation | Uninstall command |
|---------------------------|------------------------------------------------------------------|
| [Manual](#manual) | `rm -rf ~/powerlevel10k` |
| [Oh My Zsh](#oh-my-zsh) | `rm -rf -- ${ZSH_CUSTOM:-$HOME/.oh-my-zsh/custom}/themes/powerlevel10k` |
| [Prezto](#prezto) | n/a |
| [Zim](#zim) | `zimfw uninstall` |
| [Antigen](#antigen) | `antigen purge romkatv/powerlevel10k` |
| [Zplug](#zplug) | `zplug clean` |
| [Zgen](#zgen) | `zgen reset` |
| [Zplugin](#zplugin) | `zplugin delete romkatv/powerlevel10k` |
| [Zinit](#zinit) | `zinit delete romkatv/powerlevel10k` |
| [Homebrew](#homebrew) | `brew uninstall powerlevel10k; brew untap romkatv/powerlevel10k` |
| [Arch Linux](#arch-linux) | `yay -R --noconfirm zsh-theme-powerlevel10k-git` |
6. Restart Zsh. [Do not use `source ~/.zshrc`](#weird-things-happen-after-typing-source-zshrc).
7. Delete Powerlevel10k cache files.
```zsh
rm -rf -- "${XDG_CACHE_HOME:-$HOME/.cache}"/p10k-*(N) "${XDG_CACHE_HOME:-$HOME/.cache}"/gitstatus
```
### How do I install Powerlevel10k on a machine without Internet access?
1. Run this command on the machine without Internet access:
```sh
uname -sm | tr '[A-Z]' '[a-z]'
```
2. Run these commands on a machine connected to the Internet after replacing the value of
`target_uname` with the output of the previous command:
```sh
target_uname="replace this with the output of the previous command"
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
GITSTATUS_CACHE_DIR="$HOME"/powerlevel10k/gitstatus/usrbin ~/powerlevel10k/gitstatus/install -f -s "${target_uname% *}" -m "${target_uname#* }"
```
3. Copy `~/powerlevel10k` from the machine connected to the Internet to the one without Internet
access.
4. Add `source ~/powerlevel10k/powerlevel10k.zsh-theme` to `~/.zshrc` on the machine without
Internet access:
```zsh
echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
```
5. If `~/.zshrc` on the machine without Internet access sets `ZSH_THEME`, remove that line.
```zsh
sed -i.bak '/^ZSH_THEME=/d' ~/.zshrc
```
To update, remove `~/powerlevel10k` on both machines and repeat steps 1-3.
### Where can I ask for help and report bugs?
The best way to ask for help and to report bugs is to [open an issue](
@@ -612,14 +768,17 @@ Powerlevel10k defines prompt and nothing else. It sets [prompt-related options](
https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/prompt-highlight.png)
Everything within the highlighted areas on the screenshot is produced by Powerlevel10k.
Powerlevel10k has no control over the terminal content or color outside these areas.
Powerlevel10k has no control over the terminal content or colors outside these areas.
Powerlevel10k does not affect:
- Terminal window title.
- Terminal window/tab title.
- Colors used by `ls`.
- Content and style of command completions.
- The behavior of `git` command.
- The content and style of <kbd>Tab</kbd> completions.
- Command line colors (syntax highlighting, autosuggestions, etc.).
- Key bindings.
- Aliases.
- Prompt parameters other than `PS1` and `RPS1`.
- Zsh options other than those [related to prompt](
http://zsh.sourceforge.net/Doc/Release/Options.html#Prompting).
@@ -631,7 +790,7 @@ Powerlevel10k does not affect:
# Add powerlevel10k to the list of Oh My Zsh themes.
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git $ZSH_CUSTOM/themes/powerlevel10k
# Replace ZSH_THEME="powerlevel9k/powerlevel9k" with ZSH_THEME="powerlevel10k/powerlevel10k".
sed 's/powerlevel9k/powerlevel10k/g' -i ~/.zshrc
sed -i.bak 's/powerlevel9k/powerlevel10k/g' ~/.zshrc
# Restart Zsh.
exec zsh
```
@@ -752,17 +911,18 @@ Zsh but it won't do anything.
When using Lean, Classic or Rainbow style, Git status may look like this:
```text
feature:master ⇣42⇡42 ⇠42⇢42 *42 merge ~42 +42 !42 ?42
feature:master wip ⇣42⇡42 ⇠42⇢42 *42 merge ~42 +42 !42 ?42
```
| Symbol | Meaning | Source |
| --------- | -------------------------------------------------------------------- | ------------------------------------------------------ |
| `feature` | current branch; replaced with `#tag` or `@commit` if not on a branch | `git status --ignore-submodules=dirty` |
| `master` | remote tracking branch; only shown if different from local branch | `git rev-parse --abbrev-ref --symbolic-full-name @{u}` |
| `⇣42` | this many commits behind the remote | `git status --ignore-submodules=dirty` |
| `⇡42` | this many commits ahead of the remote | `git status --ignore-submodules=dirty` |
| `⇠42` | this many commits behind the push remote | `git rev-list --left-right --count HEAD...@{push}` |
| `⇢42` | this many commits ahead of the push remote | `git rev-list --left-right --count HEAD...@{push}` |
| `master` | remote tracking branch; only shown if different from local branch | `git rev-parse --abbrev-ref --symbolic-full-name @{upstream}` |
| `wip` | the latest commit's summary contains "wip" or "WIP" | `git show --pretty=%s --no-patch HEAD` |
| `⇣42` | this many commits behind the remote | `git rev-list --right-only --count HEAD...@{upstream}` |
| `⇡42` | this many commits ahead of the remote | `git rev-list --left-only --count HEAD...@{upstream}` |
| `⇠42` | this many commits behind the push remote | `git rev-list --right-only --count HEAD...@{push}` |
| `⇢42` | this many commits ahead of the push remote | `git rev-list --left-only --count HEAD...@{push}` |
| `*42` | this many stashes | `git stash list` |
| `merge` | repository state | `git status --ignore-submodules=dirty` |
| `~42` | this many merge conflicts | `git status --ignore-submodules=dirty` |
@@ -788,7 +948,7 @@ When using Lean, Classic or Rainbow style, `~/.p10k.zsh` contains the following
```zsh
# Don't show Git status in prompt for repositories whose workdir matches this pattern.
# For example, if set to '~', the Git repository at $HOME/.git will be ignored.
# Multiple patterns can be combined with '|': '~|~/some/dir'.
# Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
```
@@ -801,13 +961,27 @@ tl;dr: When Git status in prompt is greyed out, it means Powerlevel10k is curren
up-to-date Git status in the background. Prompt will get automatically refreshed when this
computation completes.
When your current directory is withing a Git repository, Powerlevel10k computes up-to-date Git
When your current directory is within a Git repository, Powerlevel10k computes up-to-date Git
status after every command. If the repository is large, or the machine is slow, this computation
can take quite a bit of time. If it takes longer than 20 milliseconds (configurable via
can take quite a bit of time. If it takes longer than 10 milliseconds (configurable via
`POWERLEVEL9K_VCS_MAX_SYNC_LATENCY_SECONDS`), Powerlevel10k displays the last known Git status in
grey and continues to compute up-to-date Git status in the background. When the computation
completes, Powerlevel10k refreshes prompt with new information, this time with colored Git status.
When using *Rainbow* style, Git status is displayed as black on grey while it's still being
computed. Depending on the terminal color palette, this may be difficult to read. In this case you
might want to change the background color to something ligher for more contrast. To do that, open
`~/.p10k.zsh`, search for `POWERLEVEL9K_VCS_LOADING_BACKGROUND`, uncomment it if it's commented out,
and change the value.
```zsh
typeset -g POWERLEVEL9K_VCS_LOADING_BACKGROUND=244
```
Type `source ~/.p10k.zsh` to apply your changes to the current Zsh session.
*Related*: [How do I change prompt colors?](#how-do-i-change-prompt-colors)
### How do I add username and/or hostname to prompt?
When using Lean, Classic or Rainbow style, prompt shows `username@hostname` when you are logged in
@@ -850,8 +1024,8 @@ a relevant tool.
```zsh
# Show prompt segment "kubecontext" only when the command you are typing
# invokes kubectl, helm, kubens, kubectx, oc, istioctl or kogito.
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
# invokes kubectl, helm, kubens, kubectx, oc, istioctl, kogito, k9s, helmfile, flux, fluxctl or stern.
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern'
```
Configs created by `p10k configure` may contain parameters of this kind. To customize when different
@@ -867,7 +1041,7 @@ function kube-toggle() {
if (( ${+POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND} )); then
unset POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND
else
POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern'
fi
p10k reload
if zle; then
@@ -926,6 +1100,10 @@ To see how different colors look in your terminal, run the following command:
for i in {0..255}; do print -Pn "%K{$i} %k%F{$i}${(l:3::0:)i}%f " ${${(M)$((i%6)):#3}:+$'\n'}; done
```
*Related:*
- [Directory is difficult to see in prompt when using Rainbow style.](
#directory-is-difficult-to-see-in-prompt-when-using-rainbow-style)
### Why does Powerlevel10k spawn extra processes?
Powerlevel10k uses [gitstatus](https://github.com/romkatv/gitstatus) as the backend behind `vcs`
@@ -962,7 +1140,7 @@ times faster than powerlevel9k/master and 17 times faster than powerlevel9k/next
Powerlevel10k was forked from Powerlevel9k in March 2019 after a week-long discussion in
[powerlevel9k#1170](https://github.com/Powerlevel9k/powerlevel9k/issues/1170). Powerlevel9k was
already a mature project with large user base and release cycle measured in months. Powerlevel10k
already a mature project with a large user base and a release cycle measured in months. Powerlevel10k
was spun off to iterate on performance improvements and new features at much higher pace.
Powerlevel9k and Powerlevel10k are independent projects. When using one, you shouldn't install the
@@ -1031,7 +1209,7 @@ Similarly, if you enable transient prompt, sparse prompt (with an empty line bef
great choice.
If you are using vi keymap, choose prompt with `prompt_char` in it (shown as green `` in the
wizard). This symbol changes depending on vi mode: ``, ``, ``, `▶` for insert, command, visual
wizard). This symbol changes depending on vi mode: ``, ``, `V`, `` for insert, command, visual
and replace mode respectively. When a command fails, the symbol turns red. *Lean* style always has
`prompt_char` in it. *Rainbow* and *Classic* styles have it only in the two-line configuration
without left frame.
@@ -1066,14 +1244,6 @@ command is reflected in the *next* prompt.
For details, see [this post on /r/zsh](
https://www.reddit.com/r/zsh/comments/eg49ff/powerlevel10k_prompt_history_exit_code_colors/fc5huku).
### Is there an AUR package for Powerlevel10k?
There is [zsh-theme-powerlevel10k-git](
https://aur.archlinux.org/packages/zsh-theme-powerlevel10k-git/). It's owned by an unaffiliated
volunteer.
There is also an [official Powerlevel10k package](#arch-linux) for Pacman.
### What is the minimum supported Zsh version?
Zsh 5.1 or newer should work. Fast startup requires Zsh >= 5.4.
@@ -1099,7 +1269,6 @@ from Nerd Fonts. The final font is released under the terms of
[Apache License](
https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/MesloLGS%20NF%20License.txt).
MesloLGS NF font can be recreated with the following command (requires `git` and `docker`):
```zsh
@@ -1110,6 +1279,12 @@ cd nerd-fonts
If everything goes well, four `ttf` files will appear in `./out`.
### How to package Powerlevel10k for distribution?
It's currently neither easy nor recommended to package and distribute Powerlevel10k. There are no
instructions you can follow that would allow you to easily update your package when new versions of
Powerlevel10k are released. This may change in the future but not soon.
## Troubleshooting
### Question mark in prompt
@@ -1301,11 +1476,6 @@ configuration wizard won't offer prompt styles that use them. *Fix*: Restart you
install [the recommended font](#meslo-nerd-font-patched-for-powerlevel10k). Verify by running
`p10k configure` and checking that all glyphs render correctly.
The minimum screen size at which configuration wizard can function is 55 columns by 21 lines.
However, not all prompt styles are offered at such small screen size as there is simply not enough
space to present them. *Fix*: Resize your terminal to at least 84 columns by 25 lines prior to
running `p10k configure`. Verify with `print ${COLUMNS}x${LINES}`.
### Cannot install the recommended font
Once you download [the recommended font](#meslo-nerd-font-patched-for-powerlevel10k),
@@ -1327,11 +1497,11 @@ From [Zsh documentation](
http://zsh.sourceforge.net/Doc/Release/Parameters.html#index-ZLE_005fRPROMPT_005fINDENT):
> `ZLE_RPROMPT_INDENT <S>`
>
>
> If set, used to give the indentation between the right hand side of the right prompt in the line
> editor as given by `RPS1` or `RPROMPT` and the right hand side of the screen. If not set, the
> value `1` is used.
>
>
> Typically this will be used to set the value to `0` so that the prompt appears flush with the
> right hand side of the screen.
@@ -1407,25 +1577,27 @@ theme (so that you end up with no theme) and then installing Powerlevel10k manua
```zsh
git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/powerlevel10k
echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>! ~/.zshrc
echo 'source ~/powerlevel10k/powerlevel10k.zsh-theme' >>~/.zshrc
```
This method of installation won't make anything slower or otherwise sub-par.
### Directory is difficult to see in prompt when using Rainbow style
In Classic style the current working directory is shown with bright white white text on blue
background. The white is fixed and always looks the same but the appearance of "blue" is defined
by your terminal color palette. If it's very light, it's difficult to see white text on it.
In Rainbow style the current working directory is shown with bright white text on blue background.
The white is fixed and always looks the same but the appearance of "blue" is defined by your
terminal color palette. If it's very light, it may be difficult to see white text on it.
There are several ways to fix this.
- Type `p10k configure` and choose a more readable prompt style.
- [Change terminal color palette](#change-the-color-palette-used-by-your-terminal). Try Tango Dark
or Solarized Dark, or change just the "blue" color.
- [Change directory background color](#set-colors-through-Powerlevel10k-configuration-parameters).
The parameter you are looking for is called `POWERLEVEL9K_DIR_BACKGROUND`. You can find it in
in `~/.p10k.zsh`. Uncomment it if it's commented out and try different values.
- [Change directory background and/or foreground color](#set-colors-through-Powerlevel10k-configuration-parameters).
The parameters you are looking for are called `POWERLEVEL9K_DIR_BACKGROUND`,
`POWERLEVEL9K_DIR_FOREGROUND`, `POWERLEVEL9K_DIR_SHORTENED_FOREGROUND`,
`POWERLEVEL9K_DIR_ANCHOR_FOREGROUND` and `POWERLEVEL9K_DIR_ANCHOR_BOLD`. You can find them in
in `~/.p10k.zsh`.
### Horrific mess when resizing terminal window
@@ -1522,14 +1694,19 @@ upon terminal shrinking due to the command line wrapping around.
#### Zsh patch
The bug described above has been fixed in [this branch](
The bug described above has been partially fixed (only for some terminals) in [this branch](
https://github.com/romkatv/zsh/tree/fix-winchanged). The idea behind the fix is to use `sc` (save
cursor) terminal capability before printing prompt and `rc` (restore cursor) to move cursor back
to the same position when prompt needs to be refreshed.
to the original position when prompt needs to be refreshed.
*Note*: The patch doesn't work on Alacritty. On the plus side, it doesn't make things worse.
The patch works only on terminals that reflow saved cursor position together with text when the
terminal window is resized. The patch has no observable effect on terminals that don't reflow text
on resize (both patched and unpatched Zsh behave correctly) and on terminals that reflow text but
not saved cursor position (both patched and unpatched Zsh redraw prompt at the same incorrect
position). In other words, the patch fixes the resizing issue on some terminals while keeping the
behavior unchanged on others.
There are two alternative approaches to fixing the bug that may seem to work at fight glance but in
There are two alternative approaches to fixing the bug that may seem to work at first glance but in
fact don't:
- Instead of `sc`, use `u7` terminal capability to query the current cursor position and then `cup`
@@ -1549,19 +1726,21 @@ There is no ETA for the patch making its way into upstream Zsh. See [discussion]
There are a few mitigation options for this issue.
- Apply [the patch](#zsh-patch) and [rebuild Zsh from source](
https://github.com/zsh-users/zsh/blob/master/INSTALL). It won't help if you are using Alacritty.
https://github.com/zsh-users/zsh/blob/master/INSTALL). It won't help if you are using Alacritty,
Kitty or some other terminal that reflows text on resize but doesn't reflow saved cursor position.
On such terminals the patch will have no visible effect.
- Disable text reflowing on window resize in terminal settings. If your terminal doesn't have this
setting, try a different terminal.
- Avoid long lines between the start of prompt and cursor.
1. Disable ruler with `POWERLEVEL9K_SHOW_RULER=false`.
1. Disable prompt connection with `POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' '`.
1. Disable right frame with `POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX=` and
`POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX=` and
`POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX=`.
1. Remove all elements from `POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS`. Right prompt on the last prompt
line will cause resizing issues only when the cursor is below it. This isn't very common, so
you might want to keep some elements in `POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS` provided that
none of them are succeeded by `newline`.
2. Disable prompt connection with `POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR=' '`.
3. Disable right frame with `POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX=''`,
`POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX=''` and
`POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX=''`.
4. Set `POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=()`. Right prompt on the last prompt line will cause
resizing issues only when the cursor is below it. This isn't very common, so you might want to
keep some elements in `POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS` provided that none of them are
succeeded by `newline`.
### Icons cut off in Konsole
@@ -1649,6 +1828,8 @@ typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='${P9K_CONTENT}' # not bold
- [License](#license)
- [FAQ](#faq)
- [How do I update Powerlevel10k?](#how-do-i-update-powerlevel10k)
- [How do I uninstall Powerlevel10k?](#how-do-i-uninstall-powerlevel10k)
- [How do I install Powerlevel10k on a machine without Internet access?](#how-do-i-install-powerlevel10k-on-a-machine-without-internet-access)
- [Where can I ask for help and report bugs?](#where-can-i-ask-for-help-and-report-bugs)
- [Which aspects of shell and terminal does Powerlevel10k affect?](#which-aspects-of-shell-and-terminal-does-powerlevel10k-affect)
- [I'm using Powerlevel9k with Oh My Zsh. How do I migrate?](#im-using-powerlevel9k-with-oh-my-zsh-how-do-i-migrate)
@@ -1669,10 +1850,10 @@ typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='${P9K_CONTENT}' # not bold
- [What is the best prompt style in the configuration wizard?](#what-is-the-best-prompt-style-in-the-configuration-wizard)
- [How to make Powerlevel10k look like robbyrussell Oh My Zsh theme?](#how-to-make-powerlevel10k-look-like-robbyrussell-oh-my-zsh-theme)
- [Can prompts for completed commands display error status for *those* commands instead of the commands preceding them?](#can-prompts-for-completed-commands-display-error-status-for-those-commands-instead-of-the-commands-preceding-them)
- [Is there an AUR package for Powerlevel10k?](#is-there-an-aur-package-for-powerlevel10k)
- [What is the minimum supported Zsh version?](#what-is-the-minimum-supported-zsh-version)
- [How were these screenshots and animated gifs created?](#how-were-these-screenshots-and-animated-gifs-created)
- [How was the recommended font created?](#how-was-the-recommended-font-created)
- [How to package Powerlevel10k for distribution?](#how-to-package-powerlevel10k-for-distribution)
- [Troubleshooting](#troubleshooting)
- [Question mark in prompt](#question-mark-in-prompt)
- [Icons, glyphs or powerline symbols don't render](#icons-glyphs-or-powerline-symbols-dont-render)

View File

@@ -17,7 +17,7 @@
# Unset all configuration options. This allows you to apply configuration changes without
# restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`.
unset -m 'POWERLEVEL9K_*|DEFAULT_USER'
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
autoload -Uz is-at-least && is-at-least 5.1 || return
@@ -66,18 +66,22 @@
jenv # java version from jenv (https://github.com/jenv/jenv)
plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
haskell_stack # haskell version from stack (https://haskellstack.org/)
kubecontext # current kubernetes context (https://kubernetes.io/)
terraform # terraform workspace (https://www.terraform.io)
# terraform_version # terraform version (https://www.terraform.io)
aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
gcloud # google cloud cli account and project (https://cloud.google.com/)
google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
toolbox # toolbox name (https://github.com/containers/toolbox)
context # user@hostname
nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
ranger # ranger shell (https://github.com/ranger/ranger)
nnn # nnn shell (https://github.com/jarun/nnn)
xplr # xplr shell (https://github.com/sayanarijit/xplr)
vim_shell # vim shell indicator (:sh)
midnight_commander # midnight commander shell (https://midnight-commander.org/)
nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
@@ -190,7 +194,7 @@
# Prompt symbol in command vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION=''
# Prompt symbol in visual vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION=''
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
# Prompt symbol in overwrite vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶'
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
@@ -242,10 +246,16 @@
stack.yaml
)
typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
# If set to true, remove everything before the last (deepest) subdirectory that contains files
# matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
# /foo/bar/git_repo/baz, prompt will display git_repo/baz. This assumes that /foo/bar/git_repo
# contains a marker (.git) and other directories don't.
# If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
# files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
# /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
# or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
# and other directories don't.
#
# Optionally, "first" and "last" can be followed by ":<offset>" where <offset> is an integer.
# This moves the truncation point to the right (positive offset) or to the left (negative offset)
# relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
# respectively.
typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
# Don't shorten this many last directory segments. They are anchors.
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
@@ -267,51 +277,68 @@
# the full directory that was used in previous commands.
typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
# Enable special styling for non-writable directories.
typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=true
# Show this icon when the current directory is not writable. POWERLEVEL9K_DIR_SHOW_WRITABLE
# above must be set to true for this parameter to have effect.
# typeset -g POWERLEVEL9K_DIR_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
# Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON
# and POWERLEVEL9K_DIR_CLASSES below.
typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
# Custom prefix.
# typeset -g POWERLEVEL9K_DIR_PREFIX='%248Fin '
# The default icon shown next to non-writable and non-existent directories when
# POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3.
# typeset -g POWERLEVEL9K_LOCK_ICON='⭐'
# POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons for different directories.
# It must be an array with 3 * N elements. Each triplet consists of:
# POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different
# directories. It must be an array with 3 * N elements. Each triplet consists of:
#
# 1. A pattern against which the current directory is matched. Matching is done with
# 1. A pattern against which the current directory ($PWD) is matched. Matching is done with
# extended_glob option enabled.
# 2. Directory class for the purpose of styling.
# 3. Icon.
# 3. An empty string.
#
# Triplets are tried in order. The first triplet whose pattern matches $PWD wins. If there
# are no matches, the directory will have no icon.
# Triplets are tried in order. The first triplet whose pattern matches $PWD wins.
#
# Example:
# If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories
# acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively.
#
# For example, given these settings:
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=(
# '~/work(|/*)' WORK '(╯°□°)╯︵ ┻━┻'
# '~(|/*)' HOME ''
# '*' DEFAULT '')
# '~/work(|/*)' WORK ''
# '~(|/*)' HOME ''
# '*' DEFAULT '')
#
# With these settings, the current directory in the prompt may look like this:
# Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one
# of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or
# WORK_NON_EXISTENT.
#
# (╯°□°)╯︵ ┻━┻ ~/work/projects/important/urgent
#
# Or like this:
#
# ⌂ ~/best/powerlevel10k
#
# You can also set different colors for directories of different classes. Remember to override
# FOREGROUND, SHORTENED_FOREGROUND and ANCHOR_FOREGROUND for every directory class that you wish
# to have its own color.
# Simply assigning classes to directories doesn't have any visible effects. It merely gives you an
# option to define custom colors and icons for different directory classes.
#
# # Styling for WORK.
# typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31
# typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103
# typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39
#
# # Styling for WORK_NOT_WRITABLE.
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=31
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=103
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=39
#
# # Styling for WORK_NON_EXISTENT.
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=31
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=103
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=39
#
# If a styling parameter isn't explicitly defined for some class, it falls back to the classless
# parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls
# back to POWERLEVEL9K_DIR_FOREGROUND.
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=()
# Custom prefix.
# typeset -g POWERLEVEL9K_DIR_PREFIX='%248Fin '
#####################################[ vcs: git status ]######################################
# Branch icon. Set this parameter to '\uF126 ' for the popular Powerline branch icon.
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
@@ -322,7 +349,7 @@
# Formatter for Git status.
#
# Example output: master ⇣42⇡42 *42 merge ~42 +42 !42 ?42.
# Example output: master wip ⇣42⇡42 *42 merge ~42 +42 !42 ?42.
#
# You can edit the function to customize how Git status looks.
#
@@ -355,28 +382,42 @@
fi
local res
local where # branch or tag
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}"
where=${(V)VCS_STATUS_LOCAL_BRANCH}
elif [[ -n $VCS_STATUS_TAG ]]; then
res+="${meta}#"
where=${(V)VCS_STATUS_TAG}
local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
# If local branch name is at most 32 characters long, show it in full.
# Otherwise show the first 12 … the last 12.
# Tip: To always show local branch name in full without truncation, delete the next line.
(( $#branch > 32 )) && branch[13,-13]="…" # <-- this line
res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
fi
# If local branch name or tag is at most 32 characters long, show it in full.
# Otherwise show the first 12 … the last 12.
# Tip: To always show local branch name in full without truncation, delete the next line.
(( $#where > 32 )) && where[13,-13]="…"
res+="${clean}${where//\%/%%}" # escape %
if [[ -n $VCS_STATUS_TAG
# Show tag only if not on a branch.
# Tip: To always show tag, delete the next line.
&& -z $VCS_STATUS_LOCAL_BRANCH # <-- this line
]]; then
local tag=${(V)VCS_STATUS_TAG}
# If tag name is at most 32 characters long, show it in full.
# Otherwise show the first 12 … the last 12.
# Tip: To always show tag name in full without truncation, delete the next line.
(( $#tag > 32 )) && tag[13,-13]="…" # <-- this line
res+="${meta}#${clean}${tag//\%/%%}"
fi
# Display the current Git commit if there is no branch or tag.
# Tip: To always display the current Git commit, remove `[[ -z $where ]] &&` from the next line.
[[ -z $where ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Display the current Git commit if there is no branch and no tag.
# Tip: To always display the current Git commit, delete the next line.
[[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line
res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Show tracking branch name if it differs from local branch.
if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" # escape %
res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
fi
# Display "wip" if the latest commit's summary contains "wip" or "WIP".
if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
res+=" ${modified}wip"
fi
# ⇣42 if behind the remote.
@@ -425,7 +466,7 @@
# Don't show Git status in prompt for repositories whose workdir matches this pattern.
# For example, if set to '~', the Git repository at $HOME/.git will be ignored.
# Multiple patterns can be combined with '|': '~|~/some/dir'.
# Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
# Disable the default Git status formatting.
@@ -449,7 +490,7 @@
# isn't in an svn or hg reposotiry.
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
# These settings are used for respositories other than Git or when gitstatusd fails and
# These settings are used for repositories other than Git or when gitstatusd fails and
# Powerlevel10k has to fall back to using vcs_info.
typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76
typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76
@@ -492,7 +533,7 @@
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘'
###################[ command_execution_time: duration of the last command ]###################
# Show duration of the last command if takes longer than this many seconds.
# Show duration of the last command if takes at least this many seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
# Show this many fractional digits. Zero means round to seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
@@ -521,6 +562,7 @@
###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]###############
# Default asdf color. Only used to display tools for which there is no color override (see below).
# Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND.
typeset -g POWERLEVEL9K_ASDF_FOREGROUND=66
# There are four parameters that can be used to hide asdf tools. Each parameter describes
@@ -566,7 +608,7 @@
typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
# If set to non-empty value, hide tools unless there is a file matching the specified file pattern
# in the current directory, or its parent diretory, or its grandparent directory, and so on.
# in the current directory, or its parent directory, or its grandparent directory, and so on.
#
# Note: If this parameter is set to empty value, it won't hide tools.
# Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments.
@@ -653,6 +695,11 @@
# typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar'
# Julia version from asdf.
typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=70
# typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar'
##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]###########
# NordVPN connection indicator color.
typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=39
@@ -674,6 +721,12 @@
# Custom icon.
# typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='⭐'
##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]##################
# xplr shell color.
typeset -g POWERLEVEL9K_XPLR_FOREGROUND=72
# Custom icon.
# typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='⭐'
###########################[ vim_shell: vim shell indicator (:sh) ]###########################
# Vim shell indicator color.
typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=34
@@ -789,7 +842,7 @@
##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]##############
# Taskwarrior color.
typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74
# Taskwarrior segment format. The following parameters are available within the expansion.
#
# - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`.
@@ -835,6 +888,9 @@
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=37
# Don't show Python version next to the virtual environment name.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
# If set to "false", won't show virtualenv if pyenv is already shown.
# If set to "if-different", won't show virtualenv if it's the same as pyenv.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
# Custom icon.
@@ -843,10 +899,33 @@
#####################[ anaconda: conda environment (https://conda.io/) ]######################
# Anaconda environment color.
typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=37
# Don't show Python version next to the anaconda environment name.
typeset -g POWERLEVEL9K_ANACONDA_SHOW_PYTHON_VERSION=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_ANACONDA_{LEFT,RIGHT}_DELIMITER=
# Anaconda segment format. The following parameters are available within the expansion.
#
# - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment.
# - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment.
# - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below).
# - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version).
#
# CONDA_PROMPT_MODIFIER can be configured with the following command:
#
# conda config --set env_prompt '({default_env}) '
#
# The last argument is a Python format string that can use the following variables:
#
# - prefix The same as CONDA_PREFIX.
# - default_env The same as CONDA_DEFAULT_ENV.
# - name The last segment of CONDA_PREFIX.
# - stacked_env Comma-separated list of names in the environment stack. The first element is
# always the same as default_env.
#
# Note: '({default_env}) ' is the default value of env_prompt.
#
# The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER
# without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former
# is empty.
typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
# Custom icon.
# typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='⭐'
@@ -860,6 +939,19 @@
typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide python version if it's equal to "system".
typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
# Pyenv segment format. The following parameters are available within the expansion.
#
# - P9K_CONTENT Current pyenv environment (pyenv version-name).
# - P9K_PYENV_PYTHON_VERSION Current python version (python --version).
#
# The default format has the following logic:
#
# 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or
# starts with "$P9K_PYENV_PYTHON_VERSION/".
# 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION".
typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}'
# Custom icon.
# typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
@@ -1054,6 +1146,19 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
#######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]#######
# Scala color.
typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=160
# Hide scala version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
# If set to false, hide scala version if it's the same as global:
# $(scalaenv version-name) == $(scalaenv global).
typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide scala version if it's equal to "system".
typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
# Custom icon.
# typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]###########
# Haskell color.
typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=172
@@ -1069,6 +1174,8 @@
# typeset -g POWERLEVEL9K_HASKELL_STACK_VISUAL_IDENTIFIER_EXPANSION='⭐'
################[ terraform: terraform workspace (https://www.terraform.io) ]#################
# Don't show terraform workspace if it's literally "default".
typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
# POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current terraform workspace gets matched.
# More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
@@ -1082,7 +1189,7 @@
# typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD
# '*test*' TEST
# '*' DEFAULT)
# '*' OTHER)
#
# If your current terraform workspace is "project_test", its class is TEST because "project_test"
# doesn't match the pattern '*prod*' but does match '*test*'.
@@ -1095,14 +1202,20 @@
typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD # These values are examples that are unlikely
# '*test*' TEST # to match your needs. Customize them as needed.
'*' DEFAULT)
typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_FOREGROUND=38
# typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
'*' OTHER)
typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=38
# typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='⭐'
#############[ terraform_version: terraform version (https://www.terraform.io) ]##############
# Terraform version color.
typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=38
# Custom icon.
# typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
#############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]#############
# Show kubecontext only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show kubecontext.
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern'
# Kubernetes context classes for the purpose of using different colors, icons and expansions with
# different contexts.
@@ -1189,7 +1302,7 @@
#[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]#
# Show aws only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show aws.
typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi'
typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi|terragrunt'
# POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current AWS profile gets matched.
@@ -1221,6 +1334,12 @@
typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=208
# typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
# AWS segment format. The following parameters are available within the expansion.
#
# - P9K_AWS_PROFILE The name of the current AWS profile.
# - P9K_AWS_REGION The region associated with the current AWS profile.
typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}'
#[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]#
# AWS Elastic Beanstalk environment color.
typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=70
@@ -1230,7 +1349,7 @@
##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]##########
# Show azure only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show azure.
typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi'
typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
# Azure account name color.
typeset -g POWERLEVEL9K_AZURE_FOREGROUND=32
# Custom icon.
@@ -1255,7 +1374,7 @@
# P9K_GCLOUD_PROJECT_ID | gcloud config get-value project
# P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)'
#
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced with '%%'.
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'.
#
# Obtaining project name requires sending a request to Google servers. This can take a long time
# and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud
@@ -1280,7 +1399,7 @@
#[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]#
# Show google_app_cred only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show google_app_cred.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi'
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
# Google application credentials classes for the purpose of using different colors, icons and
# expansions with different credentials.
@@ -1328,9 +1447,19 @@
# P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id
# P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email
#
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced by '%%'.
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
##############[ toolbox: toolbox name (https://github.com/containers/toolbox) ]###############
# Toolbox color.
typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=178
# Don't display the name of the toolbox if it matches fedora-toolbox-*.
typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}'
# Custom icon.
# typeset -g POWERLEVEL9K_TOOLBOX_VISUAL_IDENTIFIER_EXPANSION='⭐'
# Custom prefix.
# typeset -g POWERLEVEL9K_TOOLBOX_PREFIX='%248Fin '
###############################[ public_ip: public IP address ]###############################
# Public IP color.
typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94
@@ -1345,7 +1474,7 @@
typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
# Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN
# to see the name of the interface.
typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(wg|(.*tun))[0-9]*'
typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*'
# If set to true, show one segment per matching network interface. If set to false, show only
# one segment corresponding to the first matching network interface.
# Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION.
@@ -1359,17 +1488,19 @@
# The following parameters are accessible within the expansion:
#
# Parameter | Meaning
# ----------------------+---------------
# P9K_IP_IP | IP address
# P9K_IP_INTERFACE | network interface
# P9K_IP_RX_BYTES | total number of bytes received
# P9K_IP_TX_BYTES | total number of bytes sent
# P9K_IP_RX_RATE | receive rate (since last prompt)
# P9K_IP_TX_RATE | send rate (since last prompt)
# ----------------------+-------------------------------------------
# P9K_IP_IP | IP address
# P9K_IP_INTERFACE | network interface
# P9K_IP_RX_BYTES | total number of bytes received
# P9K_IP_TX_BYTES | total number of bytes sent
# P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt
# P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt
# P9K_IP_RX_RATE | receive rate (since last prompt)
# P9K_IP_TX_RATE | send rate (since last prompt)
typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='${P9K_IP_RX_RATE:+%70F⇣$P9K_IP_RX_RATE }${P9K_IP_TX_RATE:+%215F⇡$P9K_IP_TX_RATE }%38F$P9K_IP_IP'
# Show information for the first network interface whose name matches this regular expression.
# Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces.
typeset -g POWERLEVEL9K_IP_INTERFACE='e.*'
typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*'
# Custom icon.
# typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='⭐'
@@ -1412,15 +1543,11 @@
# Parameter | Meaning
# ----------------------+---------------
# P9K_WIFI_SSID | service set identifier, a.k.a. network name
# P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"
# P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown
# P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second
# P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0
# P9K_WIFI_NOISE | noise in dBm, from -120 to 0
# P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE)
#
# All parameters except P9K_WIFI_BARS are extracted from the output of the following command:
#
# /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I
####################################[ time: current time ]####################################
# Current time color.

View File

@@ -17,7 +17,7 @@
# Unset all configuration options. This allows you to apply configuration changes without
# restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`.
unset -m 'POWERLEVEL9K_*|DEFAULT_USER'
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
autoload -Uz is-at-least && is-at-least 5.1 || return
@@ -66,18 +66,22 @@
jenv # java version from jenv (https://github.com/jenv/jenv)
plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
haskell_stack # haskell version from stack (https://haskellstack.org/)
kubecontext # current kubernetes context (https://kubernetes.io/)
terraform # terraform workspace (https://www.terraform.io)
# terraform_version # terraform version (https://www.terraform.io)
aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
gcloud # google cloud cli account and project (https://cloud.google.com/)
google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
toolbox # toolbox name (https://github.com/containers/toolbox)
context # user@hostname
nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
ranger # ranger shell (https://github.com/ranger/ranger)
nnn # nnn shell (https://github.com/jarun/nnn)
xplr # xplr shell (https://github.com/sayanarijit/xplr)
vim_shell # vim shell indicator (:sh)
midnight_commander # midnight commander shell (https://midnight-commander.org/)
nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
@@ -186,7 +190,7 @@
# Prompt symbol in command vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION=''
# Prompt symbol in visual vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION=''
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
# Prompt symbol in overwrite vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶'
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
@@ -236,10 +240,16 @@
stack.yaml
)
typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
# If set to true, remove everything before the last (deepest) subdirectory that contains files
# matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
# /foo/bar/git_repo/baz, prompt will display git_repo/baz. This assumes that /foo/bar/git_repo
# contains a marker (.git) and other directories don't.
# If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
# files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
# /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
# or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
# and other directories don't.
#
# Optionally, "first" and "last" can be followed by ":<offset>" where <offset> is an integer.
# This moves the truncation point to the right (positive offset) or to the left (negative offset)
# relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
# respectively.
typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
# Don't shorten this many last directory segments. They are anchors.
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
@@ -261,51 +271,72 @@
# the full directory that was used in previous commands.
typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
# Enable special styling for non-writable directories.
typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=true
# Show this icon when the current directory is not writable. POWERLEVEL9K_DIR_SHOW_WRITABLE
# above must be set to true for this parameter to have effect.
# typeset -g POWERLEVEL9K_DIR_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
# Enable special styling for non-writable directories. See POWERLEVEL9K_LOCK_ICON and
# POWERLEVEL9K_DIR_CLASSES below.
typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v2
# Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON
# and POWERLEVEL9K_DIR_CLASSES below.
typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
# The default icon shown next to non-writable and non-existent directories when
# POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3.
# typeset -g POWERLEVEL9K_LOCK_ICON='⭐'
# POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different
# directories. It must be an array with 3 * N elements. Each triplet consists of:
#
# 1. A pattern against which the current directory ($PWD) is matched. Matching is done with
# extended_glob option enabled.
# 2. Directory class for the purpose of styling.
# 3. An empty string.
#
# Triplets are tried in order. The first triplet whose pattern matches $PWD wins.
#
# If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories
# acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively.
#
# For example, given these settings:
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=(
# '~/work(|/*)' WORK ''
# '~(|/*)' HOME ''
# '*' DEFAULT '')
#
# Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one
# of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or
# WORK_NON_EXISTENT.
#
# Simply assigning classes to directories doesn't have any visible effects. It merely gives you an
# option to define custom colors and icons for different directory classes.
#
# # Styling for WORK.
# typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=4
# typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=4
# typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=4
#
# # Styling for WORK_NOT_WRITABLE.
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=4
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=4
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=4#
#
# Styling for WORK_NON_EXISTENT.
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=4
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=4
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=4
#
# If a styling parameter isn't explicitly defined for some class, it falls back to the classless
# parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls
# back to POWERLEVEL9K_DIR_FOREGROUND.
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=()
# Custom prefix.
# typeset -g POWERLEVEL9K_DIR_PREFIX='%fin '
# POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons for different directories.
# It must be an array with 3 * N elements. Each triplet consists of:
#
# 1. A pattern against which the current directory is matched. Matching is done with
# extended_glob option enabled.
# 2. Directory class for the purpose of styling.
# 3. Icon.
#
# Triplets are tried in order. The first triplet whose pattern matches $PWD wins. If there
# are no matches, the directory will have no icon.
#
# Example:
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=(
# '~/work(|/*)' WORK '(╯°□°)╯︵ ┻━┻'
# '~(|/*)' HOME '⌂'
# '*' DEFAULT '')
#
# With these settings, the current directory in the prompt may look like this:
#
# (╯°□°)╯︵ ┻━┻ ~/work/projects/important/urgent
#
# Or like this:
#
# ⌂ ~/best/powerlevel10k
#
# You can also set different colors for directories of different classes. Remember to override
# FOREGROUND, SHORTENED_FOREGROUND and ANCHOR_FOREGROUND for every directory class that you wish
# to have its own color.
#
# typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31
# typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103
# typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=()
#####################################[ vcs: git status ]######################################
# Branch icon. Set this parameter to '\uF126 ' for the popular Powerline branch icon.
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
@@ -316,7 +347,7 @@
# Formatter for Git status.
#
# Example output: master ⇣42⇡42 *42 merge ~42 +42 !42 ?42.
# Example output: master wip ⇣42⇡42 *42 merge ~42 +42 !42 ?42.
#
# You can edit the function to customize how Git status looks.
#
@@ -349,28 +380,42 @@
fi
local res
local where # branch or tag
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}"
where=${(V)VCS_STATUS_LOCAL_BRANCH}
elif [[ -n $VCS_STATUS_TAG ]]; then
res+="${meta}#"
where=${(V)VCS_STATUS_TAG}
local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
# If local branch name is at most 32 characters long, show it in full.
# Otherwise show the first 12 … the last 12.
# Tip: To always show local branch name in full without truncation, delete the next line.
(( $#branch > 32 )) && branch[13,-13]="…" # <-- this line
res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
fi
# If local branch name or tag is at most 32 characters long, show it in full.
# Otherwise show the first 12 … the last 12.
# Tip: To always show local branch name in full without truncation, delete the next line.
(( $#where > 32 )) && where[13,-13]="…"
res+="${clean}${where//\%/%%}" # escape %
if [[ -n $VCS_STATUS_TAG
# Show tag only if not on a branch.
# Tip: To always show tag, delete the next line.
&& -z $VCS_STATUS_LOCAL_BRANCH # <-- this line
]]; then
local tag=${(V)VCS_STATUS_TAG}
# If tag name is at most 32 characters long, show it in full.
# Otherwise show the first 12 … the last 12.
# Tip: To always show tag name in full without truncation, delete the next line.
(( $#tag > 32 )) && tag[13,-13]="…" # <-- this line
res+="${meta}#${clean}${tag//\%/%%}"
fi
# Display the current Git commit if there is no branch or tag.
# Tip: To always display the current Git commit, remove `[[ -z $where ]] &&` from the next line.
[[ -z $where ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Display the current Git commit if there is no branch and no tag.
# Tip: To always display the current Git commit, delete the next line.
[[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line
res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Show tracking branch name if it differs from local branch.
if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" # escape %
res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
fi
# Display "wip" if the latest commit's summary contains "wip" or "WIP".
if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
res+=" ${modified}wip"
fi
# ⇣42 if behind the remote.
@@ -419,7 +464,7 @@
# Don't show Git status in prompt for repositories whose workdir matches this pattern.
# For example, if set to '~', the Git repository at $HOME/.git will be ignored.
# Multiple patterns can be combined with '|': '~|~/some/dir'.
# Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
# Disable the default Git status formatting.
@@ -443,7 +488,7 @@
# isn't in an svn or hg reposotiry.
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
# These settings are used for respositories other than Git or when gitstatusd fails and
# These settings are used for repositories other than Git or when gitstatusd fails and
# Powerlevel10k has to fall back to using vcs_info.
typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=2
typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=2
@@ -486,7 +531,7 @@
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘'
###################[ command_execution_time: duration of the last command ]###################
# Show duration of the last command if takes longer than this many seconds.
# Show duration of the last command if takes at least this many seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
# Show this many fractional digits. Zero means round to seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
@@ -515,6 +560,7 @@
###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]###############
# Default asdf color. Only used to display tools for which there is no color override (see below).
# Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND.
typeset -g POWERLEVEL9K_ASDF_FOREGROUND=6
# There are four parameters that can be used to hide asdf tools. Each parameter describes
@@ -560,7 +606,7 @@
typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
# If set to non-empty value, hide tools unless there is a file matching the specified file pattern
# in the current directory, or its parent diretory, or its grandparent directory, and so on.
# in the current directory, or its parent directory, or its grandparent directory, and so on.
#
# Note: If this parameter is set to empty value, it won't hide tools.
# Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments.
@@ -647,6 +693,11 @@
# typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar'
# Julia version from asdf.
typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=2
# typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar'
##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]###########
# NordVPN connection indicator color.
typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=6
@@ -668,6 +719,12 @@
# Custom icon.
# typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='⭐'
##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]##################
# xplr shell color.
typeset -g POWERLEVEL9K_XPLR_FOREGROUND=3
# Custom icon.
# typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='⭐'
###########################[ vim_shell: vim shell indicator (:sh) ]###########################
# Vim shell indicator color.
typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=3
@@ -766,7 +823,7 @@
##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]##############
# Taskwarrior color.
typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=6
# Taskwarrior segment format. The following parameters are available within the expansion.
#
# - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`.
@@ -812,6 +869,9 @@
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=6
# Don't show Python version next to the virtual environment name.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
# If set to "false", won't show virtualenv if pyenv is already shown.
# If set to "if-different", won't show virtualenv if it's the same as pyenv.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
# Custom icon.
@@ -820,10 +880,33 @@
#####################[ anaconda: conda environment (https://conda.io/) ]######################
# Anaconda environment color.
typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=6
# Don't show Python version next to the anaconda environment name.
typeset -g POWERLEVEL9K_ANACONDA_SHOW_PYTHON_VERSION=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_ANACONDA_{LEFT,RIGHT}_DELIMITER=
# Anaconda segment format. The following parameters are available within the expansion.
#
# - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment.
# - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment.
# - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below).
# - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version).
#
# CONDA_PROMPT_MODIFIER can be configured with the following command:
#
# conda config --set env_prompt '({default_env}) '
#
# The last argument is a Python format string that can use the following variables:
#
# - prefix The same as CONDA_PREFIX.
# - default_env The same as CONDA_DEFAULT_ENV.
# - name The last segment of CONDA_PREFIX.
# - stacked_env Comma-separated list of names in the environment stack. The first element is
# always the same as default_env.
#
# Note: '({default_env}) ' is the default value of env_prompt.
#
# The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER
# without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former
# is empty.
typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
# Custom icon.
# typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='⭐'
@@ -837,6 +920,19 @@
typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide python version if it's equal to "system".
typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
# Pyenv segment format. The following parameters are available within the expansion.
#
# - P9K_CONTENT Current pyenv environment (pyenv version-name).
# - P9K_PYENV_PYTHON_VERSION Current python version (python --version).
#
# The default format has the following logic:
#
# 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or
# starts with "$P9K_PYENV_PYTHON_VERSION/".
# 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION".
typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}'
# Custom icon.
# typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
@@ -1031,6 +1127,19 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
#######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]#######
# Scala color.
typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=1
# Hide scala version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
# If set to false, hide scala version if it's the same as global:
# $(scalaenv version-name) == $(scalaenv global).
typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide scala version if it's equal to "system".
typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
# Custom icon.
# typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]###########
# Haskell color.
typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=3
@@ -1048,7 +1157,7 @@
#############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]#############
# Show kubecontext only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show kubecontext.
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern'
# Kubernetes context classes for the purpose of using different colors, icons and expansions with
# different contexts.
@@ -1133,6 +1242,8 @@
# typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='%fat '
################[ terraform: terraform workspace (https://www.terraform.io) ]#################
# Don't show terraform workspace if it's literally "default".
typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
# POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current terraform workspace gets matched.
# More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
@@ -1146,7 +1257,7 @@
# typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD
# '*test*' TEST
# '*' DEFAULT)
# '*' OTHER)
#
# If your current terraform workspace is "project_test", its class is TEST because "project_test"
# doesn't match the pattern '*prod*' but does match '*test*'.
@@ -1159,14 +1270,20 @@
typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD # These values are examples that are unlikely
# '*test*' TEST # to match your needs. Customize them as needed.
'*' DEFAULT)
typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_FOREGROUND=4
# typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
'*' OTHER)
typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=4
# typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='⭐'
#############[ terraform_version: terraform version (https://www.terraform.io) ]##############
# Terraform version color.
typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=4
# Custom icon.
# typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
#[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]#
# Show aws only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show aws.
typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi'
typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi|terragrunt'
# POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current AWS profile gets matched.
@@ -1198,6 +1315,12 @@
typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=3
# typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
# AWS segment format. The following parameters are available within the expansion.
#
# - P9K_AWS_PROFILE The name of the current AWS profile.
# - P9K_AWS_REGION The region associated with the current AWS profile.
typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}'
#[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]#
# AWS Elastic Beanstalk environment color.
typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=2
@@ -1207,7 +1330,7 @@
##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]##########
# Show azure only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show azure.
typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi'
typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
# Azure account name color.
typeset -g POWERLEVEL9K_AZURE_FOREGROUND=4
# Custom icon.
@@ -1232,7 +1355,7 @@
# P9K_GCLOUD_PROJECT_ID | gcloud config get-value project
# P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)'
#
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced with '%%'.
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'.
#
# Obtaining project name requires sending a request to Google servers. This can take a long time
# and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud
@@ -1257,7 +1380,7 @@
#[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]#
# Show google_app_cred only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show google_app_cred.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi'
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
# Google application credentials classes for the purpose of using different colors, icons and
# expansions with different credentials.
@@ -1305,9 +1428,19 @@
# P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id
# P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email
#
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced by '%%'.
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
##############[ toolbox: toolbox name (https://github.com/containers/toolbox) ]###############
# Toolbox color.
typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=3
# Don't display the name of the toolbox if it matches fedora-toolbox-*.
typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}'
# Custom icon.
# typeset -g POWERLEVEL9K_TOOLBOX_VISUAL_IDENTIFIER_EXPANSION='⭐'
# Custom prefix.
# typeset -g POWERLEVEL9K_TOOLBOX_PREFIX='%fin '
###############################[ public_ip: public IP address ]###############################
# Public IP color.
typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=6
@@ -1322,7 +1455,7 @@
typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
# Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN
# to see the name of the interface.
typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(wg|(.*tun))[0-9]*'
typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*'
# If set to true, show one segment per matching network interface. If set to false, show only
# one segment corresponding to the first matching network interface.
# Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION.
@@ -1336,17 +1469,19 @@
# The following parameters are accessible within the expansion:
#
# Parameter | Meaning
# ----------------------+---------------
# P9K_IP_IP | IP address
# P9K_IP_INTERFACE | network interface
# P9K_IP_RX_BYTES | total number of bytes received
# P9K_IP_TX_BYTES | total number of bytes sent
# P9K_IP_RX_RATE | receive rate (since last prompt)
# P9K_IP_TX_RATE | send rate (since last prompt)
# ----------------------+-------------------------------------------
# P9K_IP_IP | IP address
# P9K_IP_INTERFACE | network interface
# P9K_IP_RX_BYTES | total number of bytes received
# P9K_IP_TX_BYTES | total number of bytes sent
# P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt
# P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt
# P9K_IP_RX_RATE | receive rate (since last prompt)
# P9K_IP_TX_RATE | send rate (since last prompt)
typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='$P9K_IP_IP${P9K_IP_RX_RATE:+ %2F⇣$P9K_IP_RX_RATE}${P9K_IP_TX_RATE:+ %3F⇡$P9K_IP_TX_RATE}'
# Show information for the first network interface whose name matches this regular expression.
# Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces.
typeset -g POWERLEVEL9K_IP_INTERFACE='e.*'
typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*'
# Custom icon.
# typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='⭐'
@@ -1389,15 +1524,11 @@
# Parameter | Meaning
# ----------------------+---------------
# P9K_WIFI_SSID | service set identifier, a.k.a. network name
# P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"
# P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown
# P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second
# P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0
# P9K_WIFI_NOISE | noise in dBm, from -120 to 0
# P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE)
#
# All parameters except P9K_WIFI_BARS are extracted from the output of the following command:
#
# /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I
####################################[ time: current time ]####################################
# Current time color.

View File

@@ -17,7 +17,7 @@
# Unset all configuration options. This allows you to apply configuration changes without
# restarting zsh. Edit ~/.p10k.zsh and type `source ~/.p10k.zsh`.
unset -m 'POWERLEVEL9K_*|DEFAULT_USER'
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
autoload -Uz is-at-least && is-at-least 5.1 || return
@@ -66,18 +66,22 @@
jenv # java version from jenv (https://github.com/jenv/jenv)
plenv # perl version from plenv (https://github.com/tokuhirom/plenv)
phpenv # php version from phpenv (https://github.com/phpenv/phpenv)
scalaenv # scala version from scalaenv (https://github.com/scalaenv/scalaenv)
haskell_stack # haskell version from stack (https://haskellstack.org/)
kubecontext # current kubernetes context (https://kubernetes.io/)
terraform # terraform workspace (https://www.terraform.io)
# terraform_version # terraform version (https://www.terraform.io)
aws # aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html)
aws_eb_env # aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/)
azure # azure account name (https://docs.microsoft.com/en-us/cli/azure)
gcloud # google cloud cli account and project (https://cloud.google.com/)
google_app_cred # google application credentials (https://cloud.google.com/docs/authentication/production)
toolbox # toolbox name (https://github.com/containers/toolbox)
context # user@hostname
nordvpn # nordvpn connection status, linux only (https://nordvpn.com/)
ranger # ranger shell (https://github.com/ranger/ranger)
nnn # nnn shell (https://github.com/jarun/nnn)
xplr # xplr shell (https://github.com/sayanarijit/xplr)
vim_shell # vim shell indicator (:sh)
midnight_commander # midnight commander shell (https://midnight-commander.org/)
nix_shell # nix shell (https://nixos.org/nixos/nix-pills/developing-with-nix-shell.html)
@@ -173,8 +177,8 @@
#################################[ os_icon: os identifier ]##################################
# OS identifier color.
typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=
# Make the icon bold.
typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='${P9K_CONTENT}'
# Custom icon.
# typeset -g POWERLEVEL9K_OS_ICON_CONTENT_EXPANSION='⭐'
################################[ prompt_char: prompt symbol ]################################
# Green prompt symbol if the last command succeeded.
@@ -186,7 +190,7 @@
# Prompt symbol in command vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION=''
# Prompt symbol in visual vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION=''
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V'
# Prompt symbol in overwrite vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶'
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true
@@ -236,10 +240,16 @@
stack.yaml
)
typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})"
# If set to true, remove everything before the last (deepest) subdirectory that contains files
# matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
# /foo/bar/git_repo/baz, prompt will display git_repo/baz. This assumes that /foo/bar/git_repo
# contains a marker (.git) and other directories don't.
# If set to "first" ("last"), remove everything before the first (last) subdirectory that contains
# files matching $POWERLEVEL9K_SHORTEN_FOLDER_MARKER. For example, when the current directory is
# /foo/bar/git_repo/nested_git_repo/baz, prompt will display git_repo/nested_git_repo/baz (first)
# or nested_git_repo/baz (last). This assumes that git_repo and nested_git_repo contain markers
# and other directories don't.
#
# Optionally, "first" and "last" can be followed by ":<offset>" where <offset> is an integer.
# This moves the truncation point to the right (positive offset) or to the left (negative offset)
# relative to the marker. Plain "first" and "last" are equivalent to "first:0" and "last:0"
# respectively.
typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false
# Don't shorten this many last directory segments. They are anchors.
typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1
@@ -261,51 +271,68 @@
# the full directory that was used in previous commands.
typeset -g POWERLEVEL9K_DIR_HYPERLINK=false
# Enable special styling for non-writable directories.
typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=true
# Show this icon when the current directory is not writable. POWERLEVEL9K_DIR_SHOW_WRITABLE
# above must be set to true for this parameter to have effect.
# typeset -g POWERLEVEL9K_DIR_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
# Enable special styling for non-writable and non-existent directories. See POWERLEVEL9K_LOCK_ICON
# and POWERLEVEL9K_DIR_CLASSES below.
typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3
# Custom prefix.
# typeset -g POWERLEVEL9K_DIR_PREFIX='%fin '
# The default icon shown next to non-writable and non-existent directories when
# POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3.
# typeset -g POWERLEVEL9K_LOCK_ICON='⭐'
# POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons for different directories.
# It must be an array with 3 * N elements. Each triplet consists of:
# POWERLEVEL9K_DIR_CLASSES allows you to specify custom icons and colors for different
# directories. It must be an array with 3 * N elements. Each triplet consists of:
#
# 1. A pattern against which the current directory is matched. Matching is done with
# 1. A pattern against which the current directory ($PWD) is matched. Matching is done with
# extended_glob option enabled.
# 2. Directory class for the purpose of styling.
# 3. Icon.
# 3. An empty string.
#
# Triplets are tried in order. The first triplet whose pattern matches $PWD wins. If there
# are no matches, the directory will have no icon.
# Triplets are tried in order. The first triplet whose pattern matches $PWD wins.
#
# Example:
# If POWERLEVEL9K_DIR_SHOW_WRITABLE is set to v3, non-writable and non-existent directories
# acquire class suffix _NOT_WRITABLE and NON_EXISTENT respectively.
#
# For example, given these settings:
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=(
# '~/work(|/*)' WORK '(╯°□°)╯︵ ┻━┻'
# '~(|/*)' HOME ''
# '*' DEFAULT '')
# '~/work(|/*)' WORK ''
# '~(|/*)' HOME ''
# '*' DEFAULT '')
#
# With these settings, the current directory in the prompt may look like this:
# Whenever the current directory is ~/work or a subdirectory of ~/work, it gets styled with one
# of the following classes depending on its writability and existence: WORK, WORK_NOT_WRITABLE or
# WORK_NON_EXISTENT.
#
# (╯°□°)╯︵ ┻━┻ ~/work/projects/important/urgent
#
# Or like this:
#
# ⌂ ~/best/powerlevel10k
#
# You can also set different colors for directories of different classes. Remember to override
# FOREGROUND, SHORTENED_FOREGROUND and ANCHOR_FOREGROUND for every directory class that you wish
# to have its own color.
# Simply assigning classes to directories doesn't have any visible effects. It merely gives you an
# option to define custom colors and icons for different directory classes.
#
# # Styling for WORK.
# typeset -g POWERLEVEL9K_DIR_WORK_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_DIR_WORK_FOREGROUND=31
# typeset -g POWERLEVEL9K_DIR_WORK_SHORTENED_FOREGROUND=103
# typeset -g POWERLEVEL9K_DIR_WORK_ANCHOR_FOREGROUND=39
#
# # Styling for WORK_NOT_WRITABLE.
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND=31
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_SHORTENED_FOREGROUND=103
# typeset -g POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_ANCHOR_FOREGROUND=39
#
# # Styling for WORK_NON_EXISTENT.
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_FOREGROUND=31
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_SHORTENED_FOREGROUND=103
# typeset -g POWERLEVEL9K_DIR_WORK_NON_EXISTENT_ANCHOR_FOREGROUND=39
#
# If a styling parameter isn't explicitly defined for some class, it falls back to the classless
# parameter. For example, if POWERLEVEL9K_DIR_WORK_NOT_WRITABLE_FOREGROUND is not set, it falls
# back to POWERLEVEL9K_DIR_FOREGROUND.
#
# typeset -g POWERLEVEL9K_DIR_CLASSES=()
# Custom prefix.
# typeset -g POWERLEVEL9K_DIR_PREFIX='%fin '
#####################################[ vcs: git status ]######################################
# Branch icon. Set this parameter to '\uF126 ' for the popular Powerline branch icon.
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
@@ -316,7 +343,7 @@
# Formatter for Git status.
#
# Example output: master ⇣42⇡42 *42 merge ~42 +42 !42 ?42.
# Example output: master wip ⇣42⇡42 *42 merge ~42 +42 !42 ?42.
#
# You can edit the function to customize how Git status looks.
#
@@ -349,28 +376,42 @@
fi
local res
local where # branch or tag
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}"
where=${(V)VCS_STATUS_LOCAL_BRANCH}
elif [[ -n $VCS_STATUS_TAG ]]; then
res+="${meta}#"
where=${(V)VCS_STATUS_TAG}
local branch=${(V)VCS_STATUS_LOCAL_BRANCH}
# If local branch name is at most 32 characters long, show it in full.
# Otherwise show the first 12 … the last 12.
# Tip: To always show local branch name in full without truncation, delete the next line.
(( $#branch > 32 )) && branch[13,-13]="…" # <-- this line
res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}"
fi
# If local branch name or tag is at most 32 characters long, show it in full.
# Otherwise show the first 12 … the last 12.
# Tip: To always show local branch name in full without truncation, delete the next line.
(( $#where > 32 )) && where[13,-13]="…"
res+="${clean}${where//\%/%%}" # escape %
if [[ -n $VCS_STATUS_TAG
# Show tag only if not on a branch.
# Tip: To always show tag, delete the next line.
&& -z $VCS_STATUS_LOCAL_BRANCH # <-- this line
]]; then
local tag=${(V)VCS_STATUS_TAG}
# If tag name is at most 32 characters long, show it in full.
# Otherwise show the first 12 … the last 12.
# Tip: To always show tag name in full without truncation, delete the next line.
(( $#tag > 32 )) && tag[13,-13]="…" # <-- this line
res+="${meta}#${clean}${tag//\%/%%}"
fi
# Display the current Git commit if there is no branch or tag.
# Tip: To always display the current Git commit, remove `[[ -z $where ]] &&` from the next line.
[[ -z $where ]] && res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Display the current Git commit if there is no branch and no tag.
# Tip: To always display the current Git commit, delete the next line.
[[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && # <-- this line
res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}"
# Show tracking branch name if it differs from local branch.
if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then
res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" # escape %
res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}"
fi
# Display "wip" if the latest commit's summary contains "wip" or "WIP".
if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then
res+=" ${modified}wip"
fi
# ⇣42 if behind the remote.
@@ -419,7 +460,7 @@
# Don't show Git status in prompt for repositories whose workdir matches this pattern.
# For example, if set to '~', the Git repository at $HOME/.git will be ignored.
# Multiple patterns can be combined with '|': '~|~/some/dir'.
# Multiple patterns can be combined with '|': '~(|/foo)|/bar/baz/*'.
typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'
# Disable the default Git status formatting.
@@ -443,7 +484,7 @@
# isn't in an svn or hg reposotiry.
typeset -g POWERLEVEL9K_VCS_BACKENDS=(git)
# These settings are used for respositories other than Git or when gitstatusd fails and
# These settings are used for repositories other than Git or when gitstatusd fails and
# Powerlevel10k has to fall back to using vcs_info.
typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76
typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76
@@ -486,7 +527,7 @@
typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘'
###################[ command_execution_time: duration of the last command ]###################
# Show duration of the last command if takes longer than this many seconds.
# Show duration of the last command if takes at least this many seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3
# Show this many fractional digits. Zero means round to seconds.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
@@ -515,6 +556,7 @@
###############[ asdf: asdf version manager (https://github.com/asdf-vm/asdf) ]###############
# Default asdf color. Only used to display tools for which there is no color override (see below).
# Tip: Override this parameter for ${TOOL} with POWERLEVEL9K_ASDF_${TOOL}_FOREGROUND.
typeset -g POWERLEVEL9K_ASDF_FOREGROUND=66
# There are four parameters that can be used to hide asdf tools. Each parameter describes
@@ -560,7 +602,7 @@
typeset -g POWERLEVEL9K_ASDF_SHOW_SYSTEM=true
# If set to non-empty value, hide tools unless there is a file matching the specified file pattern
# in the current directory, or its parent diretory, or its grandparent directory, and so on.
# in the current directory, or its parent directory, or its grandparent directory, and so on.
#
# Note: If this parameter is set to empty value, it won't hide tools.
# Note: SHOW_ON_UPGLOB isn't specific to asdf. It works with all prompt segments.
@@ -647,6 +689,11 @@
# typeset -g POWERLEVEL9K_ASDF_HASKELL_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_ASDF_HASKELL_SHOW_ON_UPGLOB='*.foo|*.bar'
# Julia version from asdf.
typeset -g POWERLEVEL9K_ASDF_JULIA_FOREGROUND=70
# typeset -g POWERLEVEL9K_ASDF_JULIA_VISUAL_IDENTIFIER_EXPANSION='⭐'
# typeset -g POWERLEVEL9K_ASDF_JULIA_SHOW_ON_UPGLOB='*.foo|*.bar'
##########[ nordvpn: nordvpn connection status, linux only (https://nordvpn.com/) ]###########
# NordVPN connection indicator color.
typeset -g POWERLEVEL9K_NORDVPN_FOREGROUND=39
@@ -668,6 +715,12 @@
# Custom icon.
# typeset -g POWERLEVEL9K_NNN_VISUAL_IDENTIFIER_EXPANSION='⭐'
##################[ xplr: xplr shell (https://github.com/sayanarijit/xplr) ]##################
# xplr shell color.
typeset -g POWERLEVEL9K_XPLR_FOREGROUND=72
# Custom icon.
# typeset -g POWERLEVEL9K_XPLR_VISUAL_IDENTIFIER_EXPANSION='⭐'
###########################[ vim_shell: vim shell indicator (:sh) ]###########################
# Vim shell indicator color.
typeset -g POWERLEVEL9K_VIM_SHELL_FOREGROUND=34
@@ -766,7 +819,7 @@
##############[ taskwarrior: taskwarrior task count (https://taskwarrior.org/) ]##############
# Taskwarrior color.
typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74
# Taskwarrior segment format. The following parameters are available within the expansion.
#
# - P9K_TASKWARRIOR_PENDING_COUNT The number of pending tasks: `task +PENDING count`.
@@ -812,6 +865,9 @@
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=37
# Don't show Python version next to the virtual environment name.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
# If set to "false", won't show virtualenv if pyenv is already shown.
# If set to "if-different", won't show virtualenv if it's the same as pyenv.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_WITH_PYENV=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
# Custom icon.
@@ -820,10 +876,33 @@
#####################[ anaconda: conda environment (https://conda.io/) ]######################
# Anaconda environment color.
typeset -g POWERLEVEL9K_ANACONDA_FOREGROUND=37
# Don't show Python version next to the anaconda environment name.
typeset -g POWERLEVEL9K_ANACONDA_SHOW_PYTHON_VERSION=false
# Separate environment name from Python version only with a space.
typeset -g POWERLEVEL9K_ANACONDA_{LEFT,RIGHT}_DELIMITER=
# Anaconda segment format. The following parameters are available within the expansion.
#
# - CONDA_PREFIX Absolute path to the active Anaconda/Miniconda environment.
# - CONDA_DEFAULT_ENV Name of the active Anaconda/Miniconda environment.
# - CONDA_PROMPT_MODIFIER Configurable prompt modifier (see below).
# - P9K_ANACONDA_PYTHON_VERSION Current python version (python --version).
#
# CONDA_PROMPT_MODIFIER can be configured with the following command:
#
# conda config --set env_prompt '({default_env}) '
#
# The last argument is a Python format string that can use the following variables:
#
# - prefix The same as CONDA_PREFIX.
# - default_env The same as CONDA_DEFAULT_ENV.
# - name The last segment of CONDA_PREFIX.
# - stacked_env Comma-separated list of names in the environment stack. The first element is
# always the same as default_env.
#
# Note: '({default_env}) ' is the default value of env_prompt.
#
# The default value of POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION expands to $CONDA_PROMPT_MODIFIER
# without the surrounding parentheses, or to the last path component of CONDA_PREFIX if the former
# is empty.
typeset -g POWERLEVEL9K_ANACONDA_CONTENT_EXPANSION='${${${${CONDA_PROMPT_MODIFIER#\(}% }%\)}:-${CONDA_PREFIX:t}}'
# Custom icon.
# typeset -g POWERLEVEL9K_ANACONDA_VISUAL_IDENTIFIER_EXPANSION='⭐'
@@ -837,6 +916,19 @@
typeset -g POWERLEVEL9K_PYENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide python version if it's equal to "system".
typeset -g POWERLEVEL9K_PYENV_SHOW_SYSTEM=true
# Pyenv segment format. The following parameters are available within the expansion.
#
# - P9K_CONTENT Current pyenv environment (pyenv version-name).
# - P9K_PYENV_PYTHON_VERSION Current python version (python --version).
#
# The default format has the following logic:
#
# 1. Display just "$P9K_CONTENT" if it's equal to "$P9K_PYENV_PYTHON_VERSION" or
# starts with "$P9K_PYENV_PYTHON_VERSION/".
# 2. Otherwise display "$P9K_CONTENT $P9K_PYENV_PYTHON_VERSION".
typeset -g POWERLEVEL9K_PYENV_CONTENT_EXPANSION='${P9K_CONTENT}${${P9K_CONTENT:#$P9K_PYENV_PYTHON_VERSION(|/*)}:+ $P9K_PYENV_PYTHON_VERSION}'
# Custom icon.
# typeset -g POWERLEVEL9K_PYENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
@@ -1031,6 +1123,19 @@
# Custom icon.
# typeset -g POWERLEVEL9K_PHPENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
#######[ scalaenv: scala version from scalaenv (https://github.com/scalaenv/scalaenv) ]#######
# Scala color.
typeset -g POWERLEVEL9K_SCALAENV_FOREGROUND=160
# Hide scala version if it doesn't come from one of these sources.
typeset -g POWERLEVEL9K_SCALAENV_SOURCES=(shell local global)
# If set to false, hide scala version if it's the same as global:
# $(scalaenv version-name) == $(scalaenv global).
typeset -g POWERLEVEL9K_SCALAENV_PROMPT_ALWAYS_SHOW=false
# If set to false, hide scala version if it's equal to "system".
typeset -g POWERLEVEL9K_SCALAENV_SHOW_SYSTEM=true
# Custom icon.
# typeset -g POWERLEVEL9K_SCALAENV_VISUAL_IDENTIFIER_EXPANSION='⭐'
##########[ haskell_stack: haskell version from stack (https://haskellstack.org/) ]###########
# Haskell color.
typeset -g POWERLEVEL9K_HASKELL_STACK_FOREGROUND=172
@@ -1048,7 +1153,7 @@
#############[ kubecontext: current kubernetes context (https://kubernetes.io/) ]#############
# Show kubecontext only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show kubecontext.
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito'
typeset -g POWERLEVEL9K_KUBECONTEXT_SHOW_ON_COMMAND='kubectl|helm|kubens|kubectx|oc|istioctl|kogito|k9s|helmfile|flux|fluxctl|stern'
# Kubernetes context classes for the purpose of using different colors, icons and expansions with
# different contexts.
@@ -1133,6 +1238,8 @@
# typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX='%fat '
################[ terraform: terraform workspace (https://www.terraform.io) ]#################
# Don't show terraform workspace if it's literally "default".
typeset -g POWERLEVEL9K_TERRAFORM_SHOW_DEFAULT=false
# POWERLEVEL9K_TERRAFORM_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current terraform workspace gets matched.
# More specifically, it's P9K_CONTENT prior to the application of context expansion (see below)
@@ -1146,7 +1253,7 @@
# typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD
# '*test*' TEST
# '*' DEFAULT)
# '*' OTHER)
#
# If your current terraform workspace is "project_test", its class is TEST because "project_test"
# doesn't match the pattern '*prod*' but does match '*test*'.
@@ -1159,14 +1266,20 @@
typeset -g POWERLEVEL9K_TERRAFORM_CLASSES=(
# '*prod*' PROD # These values are examples that are unlikely
# '*test*' TEST # to match your needs. Customize them as needed.
'*' DEFAULT)
typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_FOREGROUND=38
# typeset -g POWERLEVEL9K_TERRAFORM_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
'*' OTHER)
typeset -g POWERLEVEL9K_TERRAFORM_OTHER_FOREGROUND=38
# typeset -g POWERLEVEL9K_TERRAFORM_OTHER_VISUAL_IDENTIFIER_EXPANSION='⭐'
#############[ terraform_version: terraform version (https://www.terraform.io) ]##############
# Terraform version color.
typeset -g POWERLEVEL9K_TERRAFORM_VERSION_FOREGROUND=38
# Custom icon.
# typeset -g POWERLEVEL9K_TERRAFORM_VERSION_VISUAL_IDENTIFIER_EXPANSION='⭐'
#[ aws: aws profile (https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-profiles.html) ]#
# Show aws only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show aws.
typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi'
typeset -g POWERLEVEL9K_AWS_SHOW_ON_COMMAND='aws|awless|terraform|pulumi|terragrunt'
# POWERLEVEL9K_AWS_CLASSES is an array with even number of elements. The first element
# in each pair defines a pattern against which the current AWS profile gets matched.
@@ -1198,6 +1311,12 @@
typeset -g POWERLEVEL9K_AWS_DEFAULT_FOREGROUND=208
# typeset -g POWERLEVEL9K_AWS_DEFAULT_VISUAL_IDENTIFIER_EXPANSION='⭐'
# AWS segment format. The following parameters are available within the expansion.
#
# - P9K_AWS_PROFILE The name of the current AWS profile.
# - P9K_AWS_REGION The region associated with the current AWS profile.
typeset -g POWERLEVEL9K_AWS_CONTENT_EXPANSION='${P9K_AWS_PROFILE//\%/%%}${P9K_AWS_REGION:+ ${P9K_AWS_REGION//\%/%%}}'
#[ aws_eb_env: aws elastic beanstalk environment (https://aws.amazon.com/elasticbeanstalk/) ]#
# AWS Elastic Beanstalk environment color.
typeset -g POWERLEVEL9K_AWS_EB_ENV_FOREGROUND=70
@@ -1207,7 +1326,7 @@
##########[ azure: azure account name (https://docs.microsoft.com/en-us/cli/azure) ]##########
# Show azure only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show azure.
typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi'
typeset -g POWERLEVEL9K_AZURE_SHOW_ON_COMMAND='az|terraform|pulumi|terragrunt'
# Azure account name color.
typeset -g POWERLEVEL9K_AZURE_FOREGROUND=32
# Custom icon.
@@ -1232,7 +1351,7 @@
# P9K_GCLOUD_PROJECT_ID | gcloud config get-value project
# P9K_GCLOUD_PROJECT_NAME | gcloud projects describe $P9K_GCLOUD_PROJECT_ID --format='value(name)'
#
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced with '%%'.
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced with '%%'.
#
# Obtaining project name requires sending a request to Google servers. This can take a long time
# and even fail. When project name is unknown, P9K_GCLOUD_PROJECT_NAME is not set and gcloud
@@ -1257,7 +1376,7 @@
#[ google_app_cred: google application credentials (https://cloud.google.com/docs/authentication/production) ]#
# Show google_app_cred only when the the command you are typing invokes one of these tools.
# Tip: Remove the next line to always show google_app_cred.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi'
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_SHOW_ON_COMMAND='terraform|pulumi|terragrunt'
# Google application credentials classes for the purpose of using different colors, icons and
# expansions with different credentials.
@@ -1305,9 +1424,19 @@
# P9K_GOOGLE_APP_CRED_PROJECT_ID | project_id
# P9K_GOOGLE_APP_CRED_CLIENT_EMAIL | client_email
#
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurences of '%' replaced by '%%'.
# Note: ${VARIABLE//\%/%%} expands to ${VARIABLE} with all occurrences of '%' replaced by '%%'.
typeset -g POWERLEVEL9K_GOOGLE_APP_CRED_DEFAULT_CONTENT_EXPANSION='${P9K_GOOGLE_APP_CRED_PROJECT_ID//\%/%%}'
##############[ toolbox: toolbox name (https://github.com/containers/toolbox) ]###############
# Toolbox color.
typeset -g POWERLEVEL9K_TOOLBOX_FOREGROUND=178
# Don't display the name of the toolbox if it matches fedora-toolbox-*.
typeset -g POWERLEVEL9K_TOOLBOX_CONTENT_EXPANSION='${P9K_TOOLBOX_NAME:#fedora-toolbox-*}'
# Custom icon.
# typeset -g POWERLEVEL9K_TOOLBOX_VISUAL_IDENTIFIER_EXPANSION='⭐'
# Custom prefix.
# typeset -g POWERLEVEL9K_TOOLBOX_PREFIX='%fin '
###############################[ public_ip: public IP address ]###############################
# Public IP color.
typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94
@@ -1322,7 +1451,7 @@
typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION=
# Regular expression for the VPN network interface. Run `ifconfig` or `ip -4 a show` while on VPN
# to see the name of the interface.
typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(wg|(.*tun))[0-9]*'
typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*'
# If set to true, show one segment per matching network interface. If set to false, show only
# one segment corresponding to the first matching network interface.
# Tip: If you set it to true, you'll probably want to unset POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION.
@@ -1336,17 +1465,19 @@
# The following parameters are accessible within the expansion:
#
# Parameter | Meaning
# ----------------------+---------------
# P9K_IP_IP | IP address
# P9K_IP_INTERFACE | network interface
# P9K_IP_RX_BYTES | total number of bytes received
# P9K_IP_TX_BYTES | total number of bytes sent
# P9K_IP_RX_RATE | receive rate (since last prompt)
# P9K_IP_TX_RATE | send rate (since last prompt)
# ----------------------+-------------------------------------------
# P9K_IP_IP | IP address
# P9K_IP_INTERFACE | network interface
# P9K_IP_RX_BYTES | total number of bytes received
# P9K_IP_TX_BYTES | total number of bytes sent
# P9K_IP_RX_BYTES_DELTA | number of bytes received since last prompt
# P9K_IP_TX_BYTES_DELTA | number of bytes sent since last prompt
# P9K_IP_RX_RATE | receive rate (since last prompt)
# P9K_IP_TX_RATE | send rate (since last prompt)
typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='$P9K_IP_IP${P9K_IP_RX_RATE:+ %70F⇣$P9K_IP_RX_RATE}${P9K_IP_TX_RATE:+ %215F⇡$P9K_IP_TX_RATE}'
# Show information for the first network interface whose name matches this regular expression.
# Run `ifconfig` or `ip -4 a show` to see the names of all network interfaces.
typeset -g POWERLEVEL9K_IP_INTERFACE='e.*'
typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*'
# Custom icon.
# typeset -g POWERLEVEL9K_IP_VISUAL_IDENTIFIER_EXPANSION='⭐'
@@ -1389,15 +1520,11 @@
# Parameter | Meaning
# ----------------------+---------------
# P9K_WIFI_SSID | service set identifier, a.k.a. network name
# P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"
# P9K_WIFI_LINK_AUTH | authentication protocol such as "wpa2-psk" or "none"; empty if unknown
# P9K_WIFI_LAST_TX_RATE | wireless transmit rate in megabits per second
# P9K_WIFI_RSSI | signal strength in dBm, from -120 to 0
# P9K_WIFI_NOISE | noise in dBm, from -120 to 0
# P9K_WIFI_BARS | signal strength in bars, from 0 to 4 (derived from P9K_WIFI_RSSI and P9K_WIFI_NOISE)
#
# All parameters except P9K_WIFI_BARS are extracted from the output of the following command:
#
# /System/Library/PrivateFrameworks/Apple80211.framework/Versions/Current/Resources/airport -I
####################################[ time: current time ]####################################
# Current time color.

View File

@@ -23,10 +23,10 @@
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
() {
emulate -L zsh
emulate -L zsh -o extended_glob
# Unset all configuration options.
unset -m 'POWERLEVEL9K_*|DEFAULT_USER'
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
autoload -Uz is-at-least && is-at-least 5.1 || return

File diff suppressed because it is too large Load Diff

View File

@@ -18,10 +18,10 @@
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
() {
emulate -L zsh
emulate -L zsh -o extended_glob
# Unset all configuration options.
unset -m 'POWERLEVEL9K_*|DEFAULT_USER'
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
autoload -Uz is-at-least && is-at-least 5.1 || return

116
font.md Normal file
View File

@@ -0,0 +1,116 @@
# Recommended font: Meslo Nerd Font patched for Powerlevel10k
Gorgeous monospace font designed by Jim Lyles for Bitstream, customized by the same for Apple,
further customized by André Berg, and finally patched by yours truly with customized scripts
originally developed by Ryan L McIntyre of Nerd Fonts. Contains all glyphs and symbols that
Powerlevel10k may need. Battle-tested in dozens of different terminals on all major operating
systems.
*FAQ*: [How was the recommended font created?](README.md#how-was-the-recommended-font-created)
## Automatic font installation
If you are using iTerm2 or Termux, `p10k configure` can install the recommended font for you.
Simply answer `Yes` when asked whether to install *Meslo Nerd Font*.
If you are using a different terminal, proceed with manual font installation. 👇
## Manual font installation
1. Download these four ttf files:
- [MesloLGS NF Regular.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Regular.ttf)
- [MesloLGS NF Bold.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold.ttf)
- [MesloLGS NF Italic.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Italic.ttf)
- [MesloLGS NF Bold Italic.ttf](
https://github.com/romkatv/powerlevel10k-media/raw/master/MesloLGS%20NF%20Bold%20Italic.ttf)
1. Double-click on each file and click "Install". This will make `MesloLGS NF` font available to all
applications on your system.
1. Configure your terminal to use this font:
- **iTerm2**: Type `p10k configure` and answer `Yes` when asked whether to install
*Meslo Nerd Font*. Alternatively, open *iTerm2 → Preferences → Profiles → Text* and set *Font* to
`MesloLGS NF`.
- **Apple Terminal**: Open *Terminal → Preferences → Profiles → Text*, click *Change* under *Font*
and select `MesloLGS NF` family.
- **Hyper**: Open *Hyper → Edit → Preferences* and change the value of `fontFamily` under
`module.exports.config` to `MesloLGS NF`.
- **Visual Studio Code**: Open *File → Preferences → Settings* (PC) or
*Code → Preferences → Settings* (Mac), enter `terminal.integrated.fontFamily` in the search box at
the top of *Settings* tab and set the value below to `MesloLGS NF`.
Consult [this screenshot](
https://raw.githubusercontent.com/romkatv/powerlevel10k-media/389133fb8c9a2347929a23702ce3039aacc46c3d/visual-studio-code-font-settings.jpg)
to see how it should look like or see [this issue](
https://github.com/romkatv/powerlevel10k/issues/671) for extra information.
- **GNOME Terminal** (the default Ubuntu terminal): Open *Terminal → Preferences* and click on the
selected profile under *Profiles*. Check *Custom font* under *Text Appearance* and select
`MesloLGS NF Regular`.
- **Konsole**: Open *Settings → Edit Current Profile → Appearance*, click *Select Font* and select
`MesloLGS NF Regular`.
- **Tilix**: Open *Tilix → Preferences* and click on the selected profile under *Profiles*. Check
*Custom font* under *Text Appearance* and select `MesloLGS NF Regular`.
- **Windows Console Host** (the old thing): Click the icon in the top left corner, then
*Properties → Font* and set *Font* to `MesloLGS NF`.
- **Windows Terminal** by Microsoft (the new thing): Open `settings.json` (<kbd>Ctrl+Shift+,</kbd>),
search for `fontFace` and set the value to `MesloLGS NF` for every profile. If you don't find
`fontFace`, add it under *profiles → defaults*. See [this settings file](
https://raw.githubusercontent.com/romkatv/dotfiles-public/aba0e6c4657d705ed6c344d700d659977385f25c/dotfiles/microsoft-terminal-settings.json)
for example.
- **IntelliJ** (and other IDEs by Jet Brains): Open *IDE → Edit → Preferences → Editor →
Color Scheme → Console Font*. Select *Use console font instead of the default* and set the font
name to `MesloLGS NF`.
- **Termux**: Type `p10k configure` and answer `Yes` when asked whether to install
*Meslo Nerd Font*.
- **Blink**: Type `config`, go to *Appearance*, tap *Add a new font*, tap *Open Gallery*, select
*MesloLGS NF.css*, tap *import* and type `exit` in the home view to reload the font.
- **Terminus**: Open *Settings → Appearance* and set *Font* to `MesloLGS NF`.
- **Terminator**: Open *Preferences* using the context menu. Under *Profiles* select the *General*
tab (should be selected already), uncheck *Use the system fixed width font* (if not already)
and select `MesloLGS NF Regular`. Exit the Preferences dialog by clicking *Close*.
- **Guake**: Right Click on an open terminal and open *Preferences*. Under *Appearance*
tab, uncheck *Use the system fixed width font* (if not already) and select `MesloLGS NF Regular`.
Exit the Preferences dialog by clicking *Close*.
- **MobaXterm**: Open *Settings**Configuration**Terminal* → (under *Terminal look and feel*)
and change *Font* to `MesloLGS NF`.
- **Asbrú Connection Manager**: Open *Preferences → Local Shell Options → Look and Feel*, enable
*Use these personal options* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
To change the font for the remote host connections, go to *Preferences → Terminal Options →
Look and Feel* and change *Font:* under *Terminal UI* to `MesloLGS NF Regular`.
- **WSLtty**: Right click on an open terminal and then on *Options*. In the *Text* section, under
*Font*, click *"Select..."* and set Font to `MesloLGS NF Regular`.
- **Yakuake**: Click ***Manage Profiles**New**Appearance*. Click *Choose* next to the
*Font* dropdown, select `MesloLGS NF` and click *OK*. Click *OK* to save the profile. Select the
new profile and click *Set as Default*.
- **Alacritty**: Create or open `~/.config/alacritty/alacritty.yml` and add the following section
to it:
```yaml
font:
normal:
family: "MesloLGS NF"
```
- **Kitty**: Create or open `~/.config/kitty/kitty.conf` and add the following line to it:
```text
font_family MesloLGS NF
```
Restart Kitty by closing all sessions and opening a new session.
- **WezTerm**: Create or open `$HOME/.config/wezterm/wezterm.lua` and add the following:
```lua
local wezterm = require 'wezterm';
return {
font = wezterm.font("MesloLGS NF"),
}
```
If the file already exists, only add the line with the font to the existing return.
Also add the first line if it is not already present.
- **urxvt**: Create or open `~/.Xresources` and add the following line to it:
```text
URxvt.font: xft:MesloLGS NF:size=11
```
You can adjust the font size to your preference. After changing the configuration use `xrdb ~/.Xresources` to reload the config.
The new config is applied for all new terminals.
1. Run `p10k configure` to generate a new `~/.p10k.zsh`. The old config may work
incorrectly with the new font.
_Using a different terminal and know how to set the font for it? Share your knowledge by sending a
PR to expand the list!_

4
gitstatus/.clang-format Normal file
View File

@@ -0,0 +1,4 @@
BasedOnStyle: Google
ColumnLimit: 100
DerivePointerAlignment: false
PointerAlignment: Left

16
gitstatus/.gitattributes vendored Normal file
View File

@@ -0,0 +1,16 @@
* text=auto
*.cc text eol=lf
*.h text eol=lf
*.info text eol=lf
*.json text eol=lf
*.md text eol=lf
*.sh text eol=lf
*.zsh text eol=lf
/.clang-format text eol=lf
/LICENSE text eol=lf
/Makefile text eol=lf
/build text eol=lf
/install text eol=lf
/mbuild text eol=lf

8
gitstatus/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
*.zwc
/core
/deps/libgit2-*.tar.gz
/locks
/logs
/obj
/usrbin/gitstatusd*
/.vscode/ipch

17
gitstatus/.vscode/c_cpp_properties.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/src"
],
"defines": [
],
"compilerPath": "/usr/bin/g++",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "gcc-x64"
}
],
"version": 4
}

72
gitstatus/.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,72 @@
{
"files.exclude": {
"*.zwc": true,
"core": true,
"locks/": true,
"logs/": true,
"obj/": true,
"usrbin/": true,
},
"files.associations": {
"array": "cpp",
"atomic": "cpp",
"*.tcc": "cpp",
"cctype": "cpp",
"chrono": "cpp",
"clocale": "cpp",
"cmath": "cpp",
"complex": "cpp",
"condition_variable": "cpp",
"cstddef": "cpp",
"cstdint": "cpp",
"cstdio": "cpp",
"cstdlib": "cpp",
"cstring": "cpp",
"ctime": "cpp",
"cwchar": "cpp",
"cwctype": "cpp",
"deque": "cpp",
"unordered_map": "cpp",
"unordered_set": "cpp",
"vector": "cpp",
"exception": "cpp",
"fstream": "cpp",
"functional": "cpp",
"future": "cpp",
"initializer_list": "cpp",
"iomanip": "cpp",
"iosfwd": "cpp",
"iostream": "cpp",
"istream": "cpp",
"limits": "cpp",
"memory": "cpp",
"mutex": "cpp",
"new": "cpp",
"numeric": "cpp",
"optional": "cpp",
"ostream": "cpp",
"ratio": "cpp",
"sstream": "cpp",
"stdexcept": "cpp",
"streambuf": "cpp",
"string_view": "cpp",
"system_error": "cpp",
"thread": "cpp",
"type_traits": "cpp",
"tuple": "cpp",
"typeinfo": "cpp",
"utility": "cpp",
"variant": "cpp",
"cstdarg": "cpp",
"charconv": "cpp",
"algorithm": "cpp",
"cinttypes": "cpp",
"iterator": "cpp",
"map": "cpp",
"memory_resource": "cpp",
"random": "cpp",
"string": "cpp",
"bit": "cpp",
"netfwd": "cpp"
}
}

46
gitstatus/Makefile Normal file
View File

@@ -0,0 +1,46 @@
APPNAME ?= gitstatusd
OBJDIR ?= obj
CXX ?= g++
ZSH := $(shell command -v zsh 2> /dev/null)
VERSION ?= $(shell . ./build.info && printf "%s" "$$gitstatus_version")
# Note: -fsized-deallocation is not used to avoid binary compatibility issues on macOS.
#
# Sized delete is implemented as __ZdlPvm in /usr/lib/libc++.1.dylib but this symbol is
# missing in macOS prior to 10.13.
CXXFLAGS += -std=c++14 -funsigned-char -O3 -DNDEBUG -DGITSTATUS_VERSION=$(VERSION) -Wall -Werror # -g -fsanitize=thread
LDFLAGS += -pthread # -fsanitize=thread
LDLIBS += -lgit2 # -lprofiler -lunwind
SRCS := $(shell find src -name "*.cc")
OBJS := $(patsubst src/%.cc, $(OBJDIR)/%.o, $(SRCS))
all: $(APPNAME)
$(APPNAME): usrbin/$(APPNAME)
usrbin/$(APPNAME): $(OBJS)
$(CXX) $(OBJS) $(LDFLAGS) $(LDLIBS) -o $@
$(OBJDIR):
mkdir -p -- $(OBJDIR)
$(OBJDIR)/%.o: src/%.cc Makefile build.info | $(OBJDIR)
$(CXX) $(CXXFLAGS) -MM -MT $@ src/$*.cc >$(OBJDIR)/$*.dep
$(CXX) $(CXXFLAGS) -Wall -c -o $@ src/$*.cc
clean:
rm -rf -- $(OBJDIR)
zwc:
$(or $(ZSH),:) -fc 'for f in *.zsh install; do zcompile -R -- $$f.zwc $$f || exit; done'
minify:
rm -rf -- .clang-format .git .gitattributes .gitignore .vscode deps docs src usrbin/.gitkeep LICENSE Makefile README.md build mbuild
pkg: zwc
GITSTATUS_DAEMON= GITSTATUS_CACHE_DIR=$(shell pwd)/usrbin ./install -f
-include $(OBJS:.o=.dep)

View File

@@ -1 +1,529 @@
This is a bundled copy of [gitstatus](https://github.com/romkatv/gitstatus) ZSH plugin.
# gitstatus
**gitstatus** is a 10x faster alternative to `git status` and `git describe`. Its primary use
case 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 and
Bash bindings for integration with shell.
## Table of Contents
1. [Using from Zsh](#using-from-zsh)
1. [Using from Bash](#using-from-bash)
2. [Using from other shells](#using-from-other-shells)
1. [How it works](#how-it-works)
1. [Benchmarks](#benchmarks)
1. [Why fast](#why-fast)
1. [Requirements](#requirements)
1. [Compiling](#compiling)
1. [License](#license)
## Using from Zsh
The easiest way to take advantage of gitstatus from Zsh is to use a theme that's already integrated
with it. For example, [Powerlevel10k](https://github.com/romkatv/powerlevel10k) is a flexible and
fast theme with first-class gitstatus integration.
![Powerlevel10k Zsh Theme](
https://raw.githubusercontent.com/romkatv/powerlevel10k-media/master/prompt-styles-high-contrast.png)
For those who wish to use gitstatus without a theme, there is
[gitstatus.prompt.zsh](gitstatus.prompt.zsh). Install it as follows:
```zsh
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.<br>
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.
```zsh
git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus
echo 'source ~/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc
```
Alternatively, if you have Homebrew installed:
```zsh
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/gitstatus`
in 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's
[over 10x faster](#benchmarks) than any alternative that can give you comparable prompt. In order
to customize it, set `PROMPT` and/or `RPROMPT` at the end of `~/.zshrc` after sourcing
`gitstatus.prompt.zsh`. Insert `${GITSTATUS_PROMPT}` where you want git status to go. For example:
```zsh
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](gitstatus.prompt.zsh) has an example of using it to truncate the current
directory.
If you'd like to change the format of git status, or want to have greater control over the
process of assembling `PROMPT`, you can copy and modify parts of
[gitstatus.prompt.zsh](gitstatus.prompt.zsh) instead of sourcing the script. Your `~/.zshrc`
might look something like this:
```zsh
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 former
defines low-level bindings that communicate with gitstatusd over pipes. The latter is a simple
script that uses these bindings to assemble git prompt.
Unlike [Powerlevel10k](https://github.com/romkatv/powerlevel10k), code based on
[gitstatus.prompt.zsh](gitstatus.prompt.zsh) is communicating with gitstatusd synchronously. This
can make your prompt slow when working in a large git repository or on a slow machine. To avoid
this problem, call `gitstatus_query` asynchronously as documented in
[gitstatus.plugin.zsh](gitstatus.plugin.zsh). This can be quite challenging.
## Using from Bash
The easiest way to take advantage of gitstatus from Bash is via
[gitstatus.prompt.sh](gitstatus.prompt.sh). Install it as follows:
```bash
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.<br>
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.
```bash
git clone --depth=1 https://gitee.com/romkatv/gitstatus.git ~/gitstatus
echo 'source ~/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc
```
Alternatively, if you have Homebrew installed:
```zsh
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/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.
![Bash Prompt with GitStatus](
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/bash-prompt.png)
In order to customize your prompt, set `PS1` at the end of `~/.bashrc` after sourcing
`gitstatus.prompt.sh`. Insert `${GITSTATUS_PROMPT}` where you want git status to go. For example:
```bash
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 the
process of assembling `PS1`, you can copy and modify parts of
[gitstatus.prompt.sh](gitstatus.prompt.sh) instead of sourcing the script. Your `~/.bashrc` might
look something like this:
```bash
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 former
defines low-level bindings that communicate with gitstatusd over pipes. The latter is a simple
script 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 in
[options.cc](src/options.cc).
## How it works
gitstatusd reads requests from stdin and prints responses to stdout. Requests contain an ID and
a 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 future
requests faster.
[Zsh bindings](gitstatus.plugin.zsh) and [Bash bindings](gitstatus.plugin.sh) start gitstatusd in
the background and communicate with it via pipes. Themes such as
[Powerlevel10k](https://github.com/romkatv/powerlevel10k) use these bindings to put git status in
`PROMPT`.
Note that gitstatus cannot be used as a drop-in replacement for `git status` command as it doesn't
produce 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 in
a clean [chromium](https://github.com/chromium/chromium) repository synced to `9394e49a`. The
repository 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](https://github.com/romkatv/libgit2) that
implements a subset of `git` functionality on top of libgit2 API; for the purposes of this
benchmark 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 was
considered cold, with the following runs considered hot. `lg2` was patched to compute results twice
in 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 inmemory
index state between invocations; in fact, this limitation is one of the primary reasons developers
use libgit2. `gitstatusd` was benchmarked similarly to `lg2` with two result computations in the
same 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 runs
are 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 the
author. Moreover, performance is sticky -- once `git status` settles around a number, it stays
there for a long time. Numbers as diverse as 295, 352, 663 and 730 had been observed on hot runs on
the same repository. The number in the table is the lowest (fastest or best) that `git status` had
shown.
### Describe
In this benchmark all tools were computing the equivalent of `git describe --tags --exact-match`
to 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 report
whether there are any, it can terminate repository scan early. It can also remember which files
were dirty on the previous run and check them first on the next run to avoid the scan entirely if
the files are still dirty. However, the benchmarks above were performed in a clean repository where
these shortcuts do not trigger. All benchmarked tools had to do the same work -- check the status
of 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 that
makes it so fast.
Most of the following comparisons are done against libgit2 rather than git because of the author's
familiarity with the former but not the with latter. libgit2 has clean, well-documented APIs and an
elegant implementation, which makes it so much easier to work with and to analyze performance
bottlenecks.
### Summary for the impatient
Under the benchmark conditions described above, the equivalent of libgit2's
`git_diff_index_to_workdir` (the most expensive part of `status` command) is 46.3 times faster in
gitstatusd. The speedup comes from the following sources.
* gitstatusd uses more efficient data structures and algorithms and employs performance-conscious
coding 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 spent
in kernel by 1.9x.
* gitstatusd can utilize multiple cores to scan index and workdir in parallel with almost perfect
scaling. 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 _index_
and _workdir_ (`git_diff_index_to_workdir` in libgit2). Index is a list of all files in the git
repository with their last modification times. This is an obvious simplification but it suffices for
this 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 files
listed 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 staged
for commit". Thus, any implementation of `status` command has to call `stat()` or one of its
variants 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 some
form of work directory traversal.
### Single-threaded scan
Let's see how `git_diff_index_to_workdir` from libgit2 accomplishes these tasks. Here's its CPU
profile from 200 hot runs over chromium repository.
![libgit2 CPU profile (hot)](
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-libgit2.png)
(The CPU profile was created with [gperftools](https://github.com/gperftools/gperftools) and
rendered with [pprof](https://github.com/google/pprof)).
We can see `__GI__lxstat` taking a lot of time. This is the `stat()` call for every file in the
index. We can also identify `__opendir`, `__readdir` and `__GI___close_nocancel` -- glibc wrappers
for reading the contents of a directory. This is for finding untracked files. Out of the total 232
seconds, 111 seconds -- or 47.7% -- was spent on these calls. The rest is computation -- comparing
strings, sorting arrays, etc.
Now let's take a look at the CPU profile of gitstatusd on the same task.
![gitstatusd CPU profile (hot)](
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-gitstatusd-hot.png)
The first impression is that this profile looks pruned. This isn't an artifact. The profile was
generated 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 can
see that gitstatusd took 62 seconds in total compared to libgit2's 232 seconds. System calls at the
core of the algorithm are cleary visible. `__GI___fxstatat` is a flavor of `stat()`, and the other
three calls -- `__libc_openat64`, `__libc_close` and `__GI___fxstat` are responsible for opening
directories and finding untracked files. Notice that there is almost nothing else in the profile
apart from these calls. The rest of the code accounts for 3.77 seconds of CPU time -- 32 times less
than in libgit2.
So, one reason gitstatusd is fast is that it has efficient diffing code -- very little time is spent
outside of kernel. However, if we look closely, we can notice that system calls in gitstatusd are
_also_ faster than in libgit2. For example, libgit2 spent 72.07 seconds in `__GI__lxstat` while
gitstatusd 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 stat
directories because index only has files. There are 25k directories in chromium repository (and 300k
files) -- that's 25k `stat()` calls that could be avoided. The second reason is that libgit2 and
gitstatusd use different flavors of `stat()`. libgit2 uses `lstat()`, which takes a path to the file
as input. Its performance is linear in the number of subdirectories in the path because it needs to
perform a lookup for every one of them and to check permissions. gitstatusd uses `fstatat()`, which
takes a file descriptor to the parent directory and a name of the file. Just a single lookup, less
CPU 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 the
immediate parent -- the most efficient way -- and the remaining 10% it opens from the repository's
root directory. The reason it's done this way is to keep the maximum number of simultaneously open
file 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 are
done by many threads simultaneously, like in gitstatusd.
There is no equivalent to `__opendir` or `__readdir` in the gitstatusd profile because it uses the
equivalent of [untracked cache](https://git-scm.com/docs/git-update-index#_untracked_cache) from
git. On the first scan of the workdir gitstatusd lists all files just like libgit2. But, unlike
libgit2, it remembers the last modification time of every directory along with the list of
untracked files under it. On the next scan, gitstatusd can skip listing files in directories whose
last 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 the
last scan, this directory has the same list of untracked files as before, which is empty (the
repository is clean).
3. `__GI___fxstatat`: Check last modification time for every file in the index that belongs to this
directory.
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)](
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-gitstatusd-cold.png)
(Some glibc functions are mislabel on this profile. `explicit_bzero` and `__nss_passwd_lookup` are
in reality `strcmp` and `memcmp`.)
This is a superset of the previous -- hot -- profile, with an extra `syscall` and string sorting for
directory listing. gitstatusd uses `getdents64` Linux system call directly, bypassing the glibc
wrapper that libgit2 uses. This is 23% faster. The details of this optimization can be found in a
[separate document](docs/listdir.md).
### Multithreading
The diffing algorithm in gitstatusd was designed from the ground up with the intention of using it
concurrently from multiple threads. With a fast SSD, `status` is CPU bound, so taking advantage of
all available CPU cores is an obvious way to yield results faster.
gitstatusd exhibits almost perfect scaling from multithreading. Engaging all cores allows it to
produce results 12.4 times faster than in single-threaded execution. This is on Intel i9-7900X with
10 cores (20 with hyperthreading) with single-core frequency of 4.3GHz and all-core frequency of
4.0GHz.
Note: `git status` also uses all available cores in some parts of its algorithm while `lg2` does
everything 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 checked
against `.gitignore` rules and a few other things.
gitstatusd uses [patched libgit2](https://github.com/romkatv/libgit2) for this step. This fork
adds several optimizations that make libgit2 faster. The patched libgit2 performs more than twice
as fast in the benchmark as the original even without changes in the user code (that is, in the
code that uses the libgit2 APIs). The fork also adds several API extensions, most notable of which
is the support for multi-threaded scans. If `lg2 status` is modified to take advantage of these
extensions, it outperforms the original libgit2 by a factor of 18. Lastly, the fork fixes a score of
bugs, 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 is
**not 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](
https://github.com/romkatv/gitstatus/releases). When using the official shell bindings
provided 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
```zsh
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.<br>
中国大陆用户可以使用 gitee.com 上的官方镜像加速下载.
```zsh
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 up
by shell bindings automatically.
When you update shell bindings, they may refuse to work with the binary you've built earlier. In
this case you'll need to rebuild.
If you are using gitstatus through [Powerlevel10k](https://github.com/romkatv/powerlevel10k), the
instructions are the same except that you don't need to clone gitstatus. Instead, change your
current directory to `/path/to/powerlevel10k/gitstatus` (`/path/to/powerlevel10k` is the directory
where you've installed Powerlevel10k) and run `./build -w -s -d docker` from there as described
above.
### Compiling for distribution
It's currently neither easy nor recommended to package and distribute gitstatus. There are no
instructions you can follow that would allow you to easily update your package when new versions of
gitstatus are released. This may change in the future but not soon.
## License
GNU General Public License v3.0. See [LICENSE](LICENSE). Contributions are covered by the same
license.

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec "${0%-android-aarch64}-linux-aarch64" "$@"

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec "${0%-6.1-x86_64}-10.0-x86_64" "$@"

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec "${0%-6.3-x86_64}-10.0-x86_64" "$@"

Binary file not shown.

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec "${0%-i586}-i386" "$@"

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec "${0%-i686}-i386" "$@"

View File

@@ -1,3 +0,0 @@
#!/bin/sh
exec "${0%-static}" "$@"

623
gitstatus/build Executable file
View File

@@ -0,0 +1,623 @@
#!/bin/sh
#
# Type `build -h` for help and see https://github.com/romkatv/gitstatus
# for full documentation.
set -ue
if [ -n "${ZSH_VERSION:-}" ]; then
emulate sh -o err_exit -o no_unset
fi
export LC_ALL=C
if [ -z "${ZSH_VERSION-}" ] && command -v zsh >/dev/null 2>&1; then
case "${BASH_VERSION-}" in
[0-3].*) exec zsh "$0" "$@";;
esac
fi
usage="$(command cat <<\END
Usage: build [-m ARCH] [-c CPU] [-d CMD] [-i IMAGE] [-s] [-w]
Options:
-m ARCH `uname -m` from the target machine; defaults to `uname -m`
from the local machine
-c CPU generate machine instructions for CPU of this type; this
value gets passed as `-march` (or `-mcpu` for ppc64le) to gcc;
inferred from ARCH if not set explicitly
-d CMD build in a Docker container and use CMD as the `docker`
command; e.g., `-d docker` or `-d podman`
-i IMAGE build in this Docker image; inferred from ARCH if not set
explicitly
-s install whatever software is necessary for build to
succeed; on some operating systems this option is not
supported; on others it can have partial effect
-w automatically download tarballs for dependencies if they
do not already exist in ./deps; dependencies are described
in ./build.info
END
)"
build="$(command cat <<\END
outdir="$(command pwd)"
if command -v mktemp >/dev/null 2>&1; then
workdir="$(command mktemp -d "${TMPDIR:-/tmp}"/gitstatus-build.XXXXXXXXXX)"
else
workdir="${TMPDIR:-/tmp}/gitstatus-build.tmp.$$"
command mkdir -- "$workdir"
fi
cd -- "$workdir"
workdir="$(command pwd)"
narg() { echo $#; }
if [ "$(narg $workdir)" != 1 -o -z "${workdir##*:*}" -o -z "${workdir##*=*}" ]; then
>&2 echo "[error] cannot build in this directory: $workdir"
exit 1
fi
appname=gitstatusd
libgit2_tmp="$outdir"/deps/"$appname".libgit2.tmp
cleanup() {
trap - INT QUIT TERM ILL PIPE
cd /
if ! command rm -rf -- "$workdir" "$outdir"/usrbin/"$appname".tmp "$libgit2_tmp"; then
command sleep 5
command rm -rf -- "$workdir" "$outdir"/usrbin/"$appname".tmp "$libgit2_tmp"
fi
}
trap cleanup INT QUIT TERM ILL PIPE
if [ -n "$gitstatus_install_tools" ]; then
case "$gitstatus_kernel" in
linux)
if command -v apk >/dev/null 2>&1; then
command apk update
command apk add binutils cmake gcc g++ git make musl-dev perl-utils
elif command -v apt-get >/dev/null 2>&1; then
apt-get update
apt-get install -y binutils cmake gcc g++ make wget
else
>&2 echo "[error] -s is not supported on this system"
exit 1
fi
;;
freebsd)
command pkg install -y cmake gmake binutils git perl5 wget
;;
openbsd)
command pkg_add install cmake gmake gcc git wget
;;
netbsd)
command pkgin -y install cmake gmake binutils git
;;
darwin)
if ! command -v make >/dev/null 2>&1 || ! command -v gcc >/dev/null 2>&1; then
>&2 echo "[error] please run 'xcode-select --install' and retry"
exit 1
fi
if command -v port >/dev/null 2>&1; 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 ls --version "$formula" &>/dev/null; then
command brew upgrade "$formula"
else
command brew install "$formula"
fi
done
else
>&2 echo "[error] please install MacPorts or Homebrew and retry"
exit 1
fi
;;
msys*|mingw*)
command pacman -Syu --noconfirm
command pacman -S --needed --noconfirm binutils cmake gcc git make perl
;;
*)
>&2 echo "[internal error] unhandled kernel: $gitstatus_kernel"
exit 1
;;
esac
fi
cpus="$(command getconf _NPROCESSORS_ONLN 2>/dev/null)" ||
cpus="$(command sysctl -n hw.ncpu 2>/dev/null)" ||
cpus=8
case "$gitstatus_cpu" in
powerpc64le) archflag="-mcpu";;
*) archflag="-march";;
esac
cflags="$archflag=$gitstatus_cpu -fno-plt -D_FORTIFY_SOURCE=2 -Wformat -Werror=format-security -fpie"
ldflags=
static_pie=
if [ -z "${CC-}" ]; then
case "$gitstatus_kernel" in
freebsd) export CC=clang;;
*) export CC=cc;;
esac
fi
printf 'int main() {}\n' >"$workdir"/cc-test.c
if 2>/dev/null "$CC" \
-ffile-prefix-map=x=y \
-Werror \
-c "$workdir"/cc-test.c \
-o "$workdir"/cc-test.o; then
cflags="$cflags -ffile-prefix-map=$workdir/="
fi
command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
if 2>/dev/null "$CC" \
-fstack-clash-protection -fcf-protection \
-Werror \
-c "$workdir"/cc-test.c \
-o "$workdir"/cc-test.o; then
cflags="$cflags -fstack-clash-protection -fcf-protection"
fi
command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
if 2>/dev/null "$CC" \
-Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now \
-Werror \
"$workdir"/cc-test.c \
-o "$workdir"/cc-test; then
ldflags="$ldflags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now"
fi
command rm -f -- "$workdir"/cc-test "$workdir"/cc-test.o
if 2>/dev/null "$CC" \
-fpie -static-pie \
-Werror \
"$workdir"/cc-test.c \
-o "$workdir"/cc-test; then
static_pie='-static-pie'
fi
if [ "$gitstatus_cpu" = x86-64 ]; then
cflags="$cflags -mtune=generic"
fi
libgit2_cmake_flags=
libgit2_cflags="${CFLAGS-} $cflags -O3 -DNDEBUG"
gitstatus_cxx=g++
gitstatus_cxxflags="${CXXFLAGS-} $cflags -I${workdir}/libgit2/include -DGITSTATUS_ZERO_NSEC -D_GNU_SOURCE -D_GLIBCXX_ASSERTIONS"
gitstatus_ldflags="${LDFLAGS-} $ldflags -L${workdir}/libgit2/build"
gitstatus_ldlibs=
gitstatus_make=make
case "$gitstatus_kernel" in
linux)
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
;;
freebsd)
gitstatus_cxx=clang++
gitstatus_make=gmake
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
;;
openbsd)
gitstatus_cxx=eg++
gitstatus_make=gmake
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
;;
netbsd)
gitstatus_make=gmake
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
;;
darwin)
command mkdir -- "$workdir"/lib
if [ -e /opt/local/lib/libiconv.a ]; then
command ln -s -- /opt/local/lib/libiconv.a "$workdir"/lib
libgit2_cflags="$libgit2_cflags -I/opt/local/include"
gitstatus_cxxflags="$gitstatus_cxxflags -I/opt/local/include"
else
brew_prefix="$(command brew --prefix)"
command ln -s -- "$brew_prefix"/opt/libiconv/lib/libiconv.a "$workdir"/lib
libgit2_cflags="$libgit2_cflags -I"$brew_prefix"/opt/libiconv/include"
gitstatus_cxxflags="$gitstatus_cxxflags -I"$brew_prefix"/opt/libiconv/include"
fi
libgit2_cmake_flags="$libgit2_cmake_flags -DUSE_ICONV=ON"
gitstatus_ldlibs="$gitstatus_ldlibs -liconv"
gitstatus_ldflags="$gitstatus_ldflags -L${workdir}/lib"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=OFF"
;;
msys*|mingw*)
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
;;
cygwin*)
gitstatus_ldflags="$gitstatus_ldflags ${static_pie:--static}"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
;;
*)
>&2 echo "[internal error] unhandled kernel: $gitstatus_kernel"
exit 1
;;
esac
for cmd in cat cmake git ld ln mkdir rm strip tar "$gitstatus_make"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
if [ -n "$gitstatus_install_tools" ]; then
>&2 echo "[internal error] $cmd not found"
exit 1
else
>&2 echo "[error] command not found: $cmd"
exit 1
fi
fi
done
. "$outdir"/build.info
if [ -z "${libgit2_version:-}" ]; then
>&2 echo "[internal error] libgit2_version not set"
exit 1
fi
if [ -z "${libgit2_sha256:-}" ]; then
>&2 echo "[internal error] libgit2_sha256 not set"
exit 1
fi
libgit2_tarball="$outdir"/deps/libgit2-"$libgit2_version".tar.gz
if [ ! -e "$libgit2_tarball" ]; then
if [ -n "$gitstatus_download_deps" ]; then
if ! command -v wget >/dev/null 2>&1; then
if [ -n "$gitstatus_install_tools" ]; then
>&2 echo "[internal error] wget not found"
exit 1
else
>&2 echo "[error] command not found: wget"
exit 1
fi
fi
libgit2_url=https://github.com/romkatv/libgit2/archive/"$libgit2_version".tar.gz
if ! >"$libgit2_tmp" command wget --no-config -qO- -- "$libgit2_url" &&
! >"$libgit2_tmp" command wget -qO- -- "$libgit2_url"; then
set -x
>&2 command which wget
>&2 command ls -lAd -- "$(command which wget)"
>&2 command ls -lAd -- "$outdir"
>&2 command ls -lA -- "$outdir"
>&2 command ls -lAd -- "$outdir"/deps
>&2 command ls -lA -- "$outdir"/deps
set +x
exit 1
fi
command mv -f -- "$libgit2_tmp" "$libgit2_tarball"
else
>&2 echo "[error] file not found: deps/libgit2-"$libgit2_version".tar.gz"
exit 1
fi
fi
libgit2_actual_sha256=
if command -v shasum >/dev/null 2>/dev/null; then
libgit2_actual_sha256="$(command shasum -b -a 256 -- "$libgit2_tarball")"
libgit2_actual_sha256="${libgit2_actual_sha256%% *}"
elif command -v sha256sum >/dev/null 2>/dev/null; then
libgit2_actual_sha256="$(command sha256sum -b -- "$libgit2_tarball")"
libgit2_actual_sha256="${libgit2_actual_sha256%% *}"
elif command -v sha256 >/dev/null 2>/dev/null; then
libgit2_actual_sha256="$(command sha256 -- "$libgit2_tarball" </dev/null)"
# Ignore sha256 output if it's from hashalot. It's incompatible.
if [ ${#libgit2_actual_sha256} -lt 64 ]; then
libgit2_actual_sha256=
else
libgit2_actual_sha256="${libgit2_actual_sha256##* }"
fi
fi
if [ -z "$libgit2_actual_sha256" ]; then
>&2 echo "[error] command not found: shasum or sha256sum"
exit 1
fi
if [ "$libgit2_actual_sha256" != "$libgit2_sha256" ]; then
>&2 echo "[error] sha256 mismatch"
>&2 echo ""
>&2 echo " file : deps/libgit2-$libgit2_version.tar.gz"
>&2 echo " expected: $libgit2_sha256"
>&2 echo " actual : $libgit2_actual_sha256"
exit 1
fi
cd -- "$workdir"
command tar -xzf "$libgit2_tarball"
command mv -- libgit2-"$libgit2_version" libgit2
command mkdir libgit2/build
cd libgit2/build
CFLAGS="$libgit2_cflags" command cmake \
-DCMAKE_BUILD_TYPE=None \
-DZERO_NSEC=ON \
-DTHREADSAFE=ON \
-DUSE_BUNDLED_ZLIB=ON \
-DREGEX_BACKEND=builtin \
-DUSE_HTTP_PARSER=builtin \
-DUSE_SSH=OFF \
-DUSE_HTTPS=OFF \
-DBUILD_CLAR=OFF \
-DUSE_GSSAPI=OFF \
-DUSE_NTLMCLIENT=OFF \
-DBUILD_SHARED_LIBS=OFF \
$libgit2_cmake_flags \
..
command make -j "$cpus" VERBOSE=1
APPNAME="$appname".tmp \
OBJDIR="$workdir"/gitstatus \
CXX="${CXX:-$gitstatus_cxx}" \
CXXFLAGS="$gitstatus_cxxflags" \
LDFLAGS="$gitstatus_ldflags" \
LDLIBS="$gitstatus_ldlibs" \
command "$gitstatus_make" -C "$outdir" -j "$cpus"
app="$outdir"/usrbin/"$appname"
command strip "$app".tmp
command mkdir -- "$workdir"/repo
printf '[init]\n defaultBranch = master\n' >"$workdir"/.gitconfig
(
cd -- "$workdir"/repo
GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git init
GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git config user.name "Your Name"
GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git config user.email "you@example.com"
GIT_CONFIG_NOSYSTEM=1 HOME="$workdir" command git commit \
--allow-empty --allow-empty-message --no-gpg-sign -m ''
)
resp="$(printf "hello\037$workdir/repo\036" | "$app".tmp)"
case "$resp" in
hello*1*/repo*master*);;
*)
>&2 echo 'error: invalid gitstatusd response for a git repo'
exit 1
;;
esac
resp="$(printf 'hello\037\036' | "$app".tmp)"
case "$resp" in
hello*0*);;
*)
>&2 echo 'error: invalid gitstatusd response for a non-repo'
exit 1
;;
esac
command mv -f -- "$app".tmp "$app"
cleanup
command cat >&2 <<-END
-------------------------------------------------
SUCCESS: created usrbin/$appname
END
END
)"
docker_image=
docker_cmd=
gitstatus_arch=
gitstatus_cpu=
gitstatus_install_tools=
gitstatus_download_deps=
while getopts ':m:c:i:d:swh' opt "$@"; do
case "$opt" in
h)
printf '%s\n' "$usage"
exit
;;
m)
if [ -n "$gitstatus_arch" ]; then
>&2 echo "[error] duplicate option: -$opt"
exit 1
fi
if [ -z "$OPTARG" ]; then
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
exit 1
fi
gitstatus_arch="$OPTARG"
;;
c)
if [ -n "$gitstatus_cpu" ]; then
>&2 echo "[error] duplicate option: -$opt"
exit 1
fi
if [ -z "$OPTARG" ]; then
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
exit 1
fi
gitstatus_cpu="$OPTARG"
;;
i)
if [ -n "$docker_image" ]; then
>&2 echo "[error] duplicate option: -$opt"
exit 1
fi
if [ -z "$OPTARG" ]; then
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
exit 1
fi
docker_image="$OPTARG"
;;
d)
if [ -n "$docker_cmd" ]; then
>&2 echo "[error] duplicate option: -$opt"
exit 1
fi
if [ -z "$OPTARG" ]; then
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
exit 1
fi
docker_cmd="$OPTARG"
;;
s)
if [ -n "$gitstatus_install_tools" ]; then
>&2 echo "[error] duplicate option: -$opt"
exit 1
fi
gitstatus_install_tools=1
;;
w)
if [ -n "$gitstatus_download_deps" ]; then
>&2 echo "[error] duplicate option: -$opt"
exit 1
fi
gitstatus_download_deps=1
;;
\?) >&2 echo "[error] invalid option: -$OPTARG" ; exit 1;;
:) >&2 echo "[error] missing required argument: -$OPTARG"; exit 1;;
*) >&2 echo "[internal error] unhandled option: -$opt" ; exit 1;;
esac
done
if [ "$OPTIND" -le $# ]; then
>&2 echo "[error] unexpected positional argument"
exit 1
fi
if [ -n "$docker_image" -a -z "$docker_cmd" ]; then
>&2 echo "[error] cannot use -i without -d"
exit 1
fi
if [ -z "$gitstatus_arch" ]; then
gitstatus_arch="$(uname -m)"
gitstatus_arch="$(printf '%s' "$gitstatus_arch" | tr '[A-Z]' '[a-z]')"
fi
if [ -z "$gitstatus_cpu" ]; then
case "$gitstatus_arch" in
armel) gitstatus_cpu=armv5;;
armv6l|armhf) gitstatus_cpu=armv6;;
armv7l) gitstatus_cpu=armv7;;
arm64|aarch64) gitstatus_cpu=armv8-a;;
ppc64le) gitstatus_cpu=powerpc64le;;
riscv64) gitstatus_cpu=rv64imafdc;;
x86_64|amd64) gitstatus_cpu=x86-64;;
x86) gitstatus_cpu=i586;;
s390x) gitstatus_cpu=z900;;
i386|i586|i686) gitstatus_cpu="$gitstatus_arch";;
*)
>&2 echo '[error] unable to infer target CPU architecture'
>&2 echo 'Please specify explicitly with `-c CPU`.'
exit 1
;;
esac
fi
gitstatus_kernel="$(uname -s)"
gitstatus_kernel="$(printf '%s' "$gitstatus_kernel" | tr '[A-Z]' '[a-z]')"
case "$gitstatus_kernel" in
linux)
if [ -n "$docker_cmd" ]; then
if [ -z "${docker_cmd##*/*}" ]; then
if [ ! -x "$docker_cmd" ]; then
>&2 echo "[error] not an executable file: $docker_cmd"
exit 1
fi
else
if ! command -v "$docker_cmd" >/dev/null 2>&1; then
>&2 echo "[error] command not found: $docker_cmd"
exit 1
fi
fi
if [ -z "$docker_image" ]; then
case "$gitstatus_arch" in
x86_64) docker_image=alpine:3.11.6;;
x86|i386|i586|i686) docker_image=i386/alpine:3.11.6;;
armv6l|armhf) docker_image=arm32v6/alpine:3.11.6;;
armv7l) docker_image=arm32v7/alpine:3.11.6;;
aarch64) docker_image=arm64v8/alpine:3.11.6;;
ppc64le) docker_image=ppc64le/alpine:3.11.6;;
s390x) docker_image=s390x/alpine:3.11.6;;
*)
>&2 echo '[error] unable to infer docker image'
>&2 echo 'Please specify explicitly with `-i IMAGE`.'
exit 1
;;
esac
fi
fi
;;
freebsd|openbsd|netbsd|darwin)
if [ -n "$docker_cmd" ]; then
>&2 echo "[error] docker (-d) is not supported on $gitstatus_kernel"
exit 1
fi
;;
msys_nt-*|mingw32_nt-*|mingw64_nt-*|cygwin_nt-*)
if ! printf '%s' "$gitstatus_kernel" | grep -Eqx '[^-]+-[0-9]+\.[0-9]+(-.*)?'; then
>&2 echo '[error] unsupported kernel, sorry!'
exit 1
fi
gitstatus_kernel="$(printf '%s' "$gitstatus_kernel" | sed 's/^\([^-]*-[0-9]*\.[0-9]*\).*/\1/')"
if [ -n "$docker_cmd" ]; then
>&2 echo '[error] docker (-d) is not supported on windows'
exit 1
fi
if [ -n "$gitstatus_install_tools" -a -z "${gitstatus_kernel##cygwin_nt-*}" ]; then
>&2 echo '[error] -s is not supported on cygwin'
exit 1
fi
;;
*)
>&2 echo '[error] unsupported kernel, sorry!'
exit 1
;;
esac
dir="$(dirname -- "$0")"
cd -- "$dir"
dir="$(pwd)"
>&2 echo "Building gitstatusd..."
>&2 echo ""
>&2 echo " kernel := $gitstatus_kernel"
>&2 echo " arch := $gitstatus_arch"
>&2 echo " cpu := $gitstatus_cpu"
[ -z "$docker_cmd" ] || >&2 echo " docker command := $docker_cmd"
[ -z "$docker_image" ] || >&2 echo " docker image := $docker_image"
if [ -n "$gitstatus_install_tools" ]; then
>&2 echo " install tools := yes"
else
>&2 echo " install tools := no"
fi
if [ -n "$gitstatus_download_deps" ]; then
>&2 echo " download deps := yes"
else
>&2 echo " download deps := no"
fi
if [ -n "$docker_cmd" ]; then
"$docker_cmd" run \
-e docker_cmd="$docker_cmd" \
-e docker_image="$docker_image" \
-e gitstatus_kernel="$gitstatus_kernel" \
-e gitstatus_arch="$gitstatus_arch" \
-e gitstatus_cpu="$gitstatus_cpu" \
-e gitstatus_install_tools="$gitstatus_install_tools" \
-e gitstatus_download_deps="$gitstatus_download_deps" \
-v "$dir":/out \
-w /out \
--rm \
-- "$docker_image" /bin/sh -uexc "$build"
else
eval "$build"
fi

22
gitstatus/build.info Normal file
View File

@@ -0,0 +1,22 @@
# This value gets embedded in gitstatusd at build time. It is
# read by ./Makefile. `gitstatusd --version` reports it back.
#
# This value is also read by shell bindings (indirectly, through
# ./install) when using GITSTATUS_DAEMON or usrbin/gitstatusd.
gitstatus_version="v1.5.3"
# libgit2 is a build time dependency of gitstatusd. The values of
# libgit2_version and libgit2_sha256 are read by ./build.
#
# If ./deps/libgit2-${libgit2_version}.tar.gz doesn't exist, build
# downloads it from the following location:
#
# https://github.com/romkatv/libgit2/archive/${libgit2_version}.tar.gz
#
# Once downloaded, the tarball is stored at the path indicated
# above so that repeated builds don't consume network bandwidth.
#
# If sha256 of ./deps/libgit2-${libgit2_version}.tar.gz doesn't match,
# build gets aborted.
libgit2_version="tag-5860a42d19bcd226cb6eff2dcbfcbf155d570c73"
libgit2_sha256="2289203eda19913a2f6d2b26a15384cc43872bffd70e87a7659f9a22da79058e"

0
gitstatus/deps/.gitkeep Normal file
View File

330
gitstatus/docs/listdir.md Normal file
View File

@@ -0,0 +1,330 @@
# Fast directory listing
In order to find untracked files in a git repository, [gitstatusd](../README.md) needs to list the
contents of every directory. gitstatusd does it 27% faster than a reasonable implementation that a
seasoned C/C++ practitioner might write. This document explains the optimizations that went into it.
As directory listing is a common operation, many other projects can benefit from applying these
optimizations.
## v1
Given a path to a directory, `ListDir()` must produce the list of files in that directory. Moreover,
the list must be sorted lexicographically to enable fast comparison with Git index.
The following C++ implementation gets the job done. For simplicity, it returns an empty list on
error.
```c++
vector<string> ListDir(const char* dirname) {
vector<string> entries;
if (DIR* dir = opendir(dirname)) {
while (struct dirent* ent = (errno = 0, readdir(dir))) {
if (!Dots(ent->d_name)) entries.push_back(ent->d_name);
}
if (errno) entries.clear();
sort(entries.begin(), entries.end());
closedir(dir);
}
return entries;
}
```
Every directory has entries `"."` and `".."`, which we aren't interested in. We filter them out with
a helper function `Dots()`.
```c++
bool Dots(const char* s) { return s[0] == '.' && (!s[1] || (s[1] == '.' && !s[2])); }
```
To check how fast `ListDir()` performs, we can run it many times on a typical directory. One million
runs on a directory with 32 files with 16-character names takes 12.7 seconds.
## v2
Experienced C++ practitioners will scoff at our implementation of `ListDir()`. If it's meant to be
efficient, returning `vector<string>` is an unaffordable convenience. To avoid heap allocations we
can use a simple arena that will allow us to reuse memory between different `ListDir()` calls.
(Changed and added lines are marked with comments.)
```c++
void ListDir(const char* dirname, string& arena, vector<char*>& entries) { // +
entries.clear(); // +
if (DIR* dir = opendir(dirname)) {
arena.clear(); // +
while (struct dirent* ent = (errno = 0, readdir(dir))) {
if (!Dots(ent->d_name)) {
entries.push_back(reinterpret_cast<char*>(arena.size())); // +
arena.append(ent->d_name, strlen(ent->d_name) + 1); // +
}
}
if (errno) entries.clear();
for (char*& p : entries) p = &arena[reinterpret_cast<size_t>(p)]; // +
sort(entries.begin(), entries.end(), // +
[](const char* a, const char* b) { return strcmp(a, b) < 0; }); // +
closedir(dir);
}
}
```
To make performance comparison easier, we can normalize them relative to the baseline. v1 will get
performance score of 100. A twice-as-fast alternative will be 200.
| version | optimization | score |
|---------|----------------------------|----------:|
| v1 | baseline | 100.0 |
| **v2** | **avoid heap allocations** | **112.7** |
Avoiding heap allocations makes `ListDir()` 12.7% faster. Not bad. As an added bonus, those casts
will fend off the occasional frontend developer who accidentally wanders into the codebase.
## v3
`opendir()` is an expensive call whose performance is linear in the number of subdirectories in the
path because it needs to perform a lookup for every one of them. We can replace it with `openat()`,
which takes a file descriptor to the parent directory and a name of the subdirectory. Just a single
lookup, less CPU time. This optimization assumes that callers already have a descriptor to the
parent directory, which is indeed the case for gitstatusd, and is often the case in other
applications that traverse filesystem.
```c++
void ListDir(int parent_fd, const char* dirname, string& arena, vector<char*>& entries) { // +
entries.clear();
int dir_fd = openat(parent_fd, dirname, O_NOATIME | O_RDONLY | O_DIRECTORY | O_CLOEXEC); // +
if (dir_fd < 0) return; // +
if (DIR* dir = fdopendir(dir_fd)) {
arena.clear();
while (struct dirent* ent = (errno = 0, readdir(dir))) {
if (!Dots(ent->d_name)) {
entries.push_back(reinterpret_cast<char*>(arena.size()));
arena.append(ent->d_name, strlen(ent->d_name) + 1);
}
}
if (errno) entries.clear();
for (char*& p : entries) p = &arena[reinterpret_cast<size_t>(p)];
sort(entries.begin(), entries.end(),
[](const char* a, const char* b) { return strcmp(a, b) < 0; });
closedir(dir);
} else { // +
close(dir_fd); // +
} // +
}
```
This is worth about 3.5% in speed.
| version | optimization | score |
|---------|--------------------------------------|----------:|
| v1 | baseline | 100.0 |
| v2 | avoid heap allocations | 112.7 |
| **v3** | **open directories with `openat()`** | **116.2** |
## v4
Copying file names to the arena isn't free but it doesn't seem like we can avoid it. Poking around
we can see that the POSIX API we are using is implemented on Linux on top of `getdents64` system
call. Its documentation isn't very encouraging:
```text
These are not the interfaces you are interested in. Look at
readdir(3) for the POSIX-conforming C library interface. This page
documents the bare kernel system call interfaces.
Note: There are no glibc wrappers for these system calls.
```
Hmm... The API looks like something we can take advantage of, so let's try it anyway.
First, we'll need a simple `Arena` class that can allocate 8KB blocks of memory.
```c++
class Arena {
public:
enum { kBlockSize = 8 << 10 };
char* Alloc() {
if (cur_ == blocks_.size()) blocks_.emplace_back(kBlockSize, 0);
return blocks_[cur_++].data();
}
void Clear() { cur_ = 0; }
private:
size_t cur_ = 0;
vector<string> blocks_;
};
```
Next, we need to define `struct dirent64_t` ourselves because there is no wrapper for the system
call we are about to use.
```c++
struct dirent64_t {
ino64_t d_ino;
off64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[];
};
```
Finally we can get to the implementation of `ListDir()`.
```c++
void ListDir(int parent_fd, Arena& arena, vector<char*>& entries) { // +
entries.clear();
int dir_fd = openat(parent_fd, dirname, O_NOATIME | O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (dir_fd < 0) return;
arena.Clear(); // +
while (true) { // +
char* buf = arena.Alloc(); // +
int n = syscall(SYS_getdents64, dir_fd, buf, Arena::kBlockSize); // +
if (n <= 0) { // +
if (n) entries.clear(); // +
break; // +
} // +
for (int pos = 0; pos < n;) { // +
auto* ent = reinterpret_cast<dirent64_t*>(buf + pos); // +
if (!Dots(ent->d_name)) entries.push_back(ent->d_name); // +
pos += ent->d_reclen; // +
} // +
} // +
sort(entries.begin(), entries.end(),
[](const char* a, const char* b) { return strcmp(a, b) < 0; });
close(dir_fd);
}
```
How are we doing with this one?
| version | optimization | score |
|---------|----------------------------------|----------:|
| v1 | baseline | 100.0 |
| v2 | avoid heap allocations | 112.7 |
| v3 | open directories with `openat()` | 116.2 |
| **v4** | **call `getdents64()` directly** | **137.8** |
Solid 20% speedup. Worth the trouble. Unfortunately, we now have just one `reinterpret_cast` instead
of two, and it's not nearly as scary-looking. Hopefully with the next iteration we can get back some
of that evil vibe of low-level code.
As a bonus, every element in `entries` has `d_type` at offset -1. This can be useful to the callers
that need to distinguish between regular files and directories (gitstatusd, in fact, needs this).
Note how `ListDir()` implements this feature at zero cost, as a lucky accident of `dirent64_t`
memory layout.
## v5
The CPU profile of `ListDir()` reveals that almost all userspace CPU time is spent in `strcmp()`.
Digging into the source code of `std::sort()` we can see that it uses Insertion Sort for short
collections. Our 32-element vector falls under the threshold. Insertion Sort makes `O(N^2)`
comparisons, hence a lot of CPU time in `strcmp()`. Switching to `qsort()` or
[Timsort](https://en.wikipedia.org/wiki/Timsort) is of no use as all good sorting algorithms fall
back to Insertion Sort.
If we cannot make fewer comparisons, perhaps we can make each of them faster? `strcmp()` compares
characters one at a time. It cannot read ahead as it can be illegal to touch memory past the first
null byte. But _we_ know that it's safe to read a few extra bytes past the end of `d_name` for every
entry except the last in the buffer. And since we own the buffer, we can overallocate it so that
reading past the end of the last entry is also safe.
Combining these ideas with the fact that file names on Linux are at most 255 bytes long, we can
invoke `getdents64()` like this:
```c++
int n = syscall(SYS_getdents64, dir_fd, buf, Arena::kBlockSize - 256);
```
And then compare entries like this:
```c++
[](const char* a, const char* b) { return memcmp(a, b, 255) < 0; }
```
This version doesn't give any speedup compared to the previous but it opens an avenue for another
optimization. The pointers we pass to `memcmp()` aren't aligned. To be more specific, their
numerical values are `N * 8 + 3` for some `N`. When given such a pointer, `memcmp()` will check the
first 5 bytes one by one, and only then switch to comparing 8 bytes at a time. If we can handle the
first 5 bytes ourselves, we can pass aligned memory to `memcmp()` and take full advantage of its
vectorized loop.
Here's the implementation:
```c++
uint64_t Read64(const void* p) { // +
uint64_t x; // +
memcpy(&x, p, sizeof(x)); // +
return x; // +
} // +
void ByteSwap64(void* p) { // +
uint64_t x = __builtin_bswap64(Read64(p)); // +
memcpy(p, &x, sizeof(x)); // +
} // +
void ListDir(int parent_fd, Arena& arena, vector<char*>& entries) {
entries.clear();
int dir_fd = openat(parent_fd, dirname, O_NOATIME | O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (dir_fd < 0) return;
arena.Clear();
while (true) {
char* buf = arena.Alloc();
int n = syscall(SYS_getdents64, dir_fd, buf, Arena::kBlockSize - 256); // +
if (n <= 0) {
if (n) entries.clear();
break;
}
for (int pos = 0; pos < n;) {
auto* ent = reinterpret_cast<dirent64_t*>(buf + pos);
if (!Dots(ent->d_name)) {
ByteSwap64(ent->d_name); // +
entries.push_back(ent->d_name);
}
pos += ent->d_reclen;
}
}
sort(entries.begin(), entries.end(), [](const char* a, const char* b) {
uint64_t x = Read64(a); // +
uint64_t y = Read64(b); // +
return x < y || (x == y && a != b && memcmp(a + 5, b + 5, 256) < 0); // +
});
for (char* p : entries) ByteSwap64(p); // +
close(dir_fd);
}
```
This is for Little Endian architecture. Big Endian doesn't need `ByteSwap64()`, so it'll be a bit
faster.
| version | optimization | score |
|---------|----------------------------------|----------:|
| v1 | baseline | 100.0 |
| v2 | avoid heap allocations | 112.7 |
| v3 | open directories with `openat()` | 116.2 |
| v4 | call `getdents64()` directly | 137.8 |
| **v5** | **hand-optimize `strcmp()`** | **143.3** |
Fast and respectably arcane.
## Conclusion
Through a series of incremental improvements we've sped up directory listing by 43.3% compared to a
naive implementation (v1) and 27.2% compared to a reasonable implementation that a seasoned C/C++
practitioner might write (v2).
However, these numbers are based on an artificial benchmark while the real judge is always the real
code. Our goal was to speed up gitstatusd. Benchmark was just a tool. Thankfully, the different
versions of `ListDir()` have the same comparative performance within gitstatusd as in the benchmark.
In truth, the directory chosen for the benchmark wasn't arbitrary. It was picked by sampling
gitstatusd when it runs on [chromium](https://github.com/chromium/chromium) git repository.
The final version of `ListDir()` spends 97% of its CPU time in the kernel. If we assume that it
makes the minimum possible number of system calls and these calls are optimal (true to the best
of my knowledge), it puts the upper bound on possible future performance improvements at just 3%.
There is almost nothing left in `ListDir()` to optimize.
![ListDir() CPU profile](
https://raw.githubusercontent.com/romkatv/gitstatus/1ac366952366d89980b3f3484f270b4fa5ae4293/cpu-profile-listdir.png)
(The CPU profile was created with [gperftools](https://github.com/gperftools/gperftools) and
rendered with [pprof](https://github.com/google/pprof)).

View File

@@ -0,0 +1,470 @@
# Bash bindings for gitstatus.
[[ $- == *i* ]] || return # non-interactive shell
# Starts gitstatusd in the background. Does nothing and succeeds if gitstatusd
# is already running.
#
# Usage: gitstatus_start [OPTION]...
#
# -t FLOAT Fail the self-check on initialization if not getting a response from
# gitstatusd for this this many seconds. Defaults to 5.
#
# -s INT Report at most this many staged changes; negative value means infinity.
# Defaults to 1.
#
# -u INT Report at most this many unstaged changes; negative value means infinity.
# Defaults to 1.
#
# -c INT Report at most this many conflicted changes; negative value means infinity.
# Defaults to 1.
#
# -d INT Report at most this many untracked files; negative value means infinity.
# Defaults to 1.
#
# -m INT Report -1 unstaged, untracked and conflicted if there are more than this many
# files in the index. Negative value means infinity. Defaults to -1.
#
# -e Count files within untracked directories like `git status --untracked-files`.
#
# -U Unless this option is specified, report zero untracked files for repositories
# with status.showUntrackedFiles = false.
#
# -W Unless this option is specified, report zero untracked files for repositories
# with bash.showUntrackedFiles = false.
#
# -D Unless this option is specified, report zero staged, unstaged and conflicted
# changes for repositories with bash.showDirtyState = false.
#
# -r INT Close git repositories that haven't been used for this many seconds. This is
# meant to release resources such as memory and file descriptors. The next request
# for a repo that's been closed is much slower than for a repo that hasn't been.
# Negative value means infinity. The default is 3600 (one hour).
function gitstatus_start() {
if [[ "$BASH_VERSION" < 4 ]]; then
>&2 printf 'gitstatus_start: need bash version >= 4.0, found %s\n' "$BASH_VERSION"
>&2 printf '\n'
>&2 printf 'To see the version of the current shell, type:\n'
>&2 printf '\n'
>&2 printf ' \033[32mecho\033[0m \033[33m"$BASH_VERSION"\033[0m\n'
>&2 printf '\n'
>&2 printf 'The output of `\033[32mbash\033[0m --version` may be different and is not relevant.\n'
return 1
fi
unset OPTIND
local opt timeout=5 max_dirty=-1 ttl=3600 extra_flags
local max_num_staged=1 max_num_unstaged=1 max_num_conflicted=1 max_num_untracked=1
local ignore_status_show_untracked_files
while getopts "t:s:u:c:d:m:r:eUWD" opt; do
case "$opt" in
t) timeout=$OPTARG;;
s) max_num_staged=$OPTARG;;
u) max_num_unstaged=$OPTARG;;
c) max_num_conflicted=$OPTARG;;
d) max_num_untracked=$OPTARG;;
m) max_dirty=$OPTARG;;
r) ttl=$OPTARG;;
e) extra_flags+='--recurse-untracked-dirs ';;
U) extra_flags+='--ignore-status-show-untracked-files ';;
W) extra_flags+='--ignore-bash-show-untracked-files ';;
D) extra_flags+='--ignore-bash-show-dirty-state ';;
*) return 1;;
esac
done
(( OPTIND == $# + 1 )) || { echo "usage: gitstatus_start [OPTION]..." >&2; return 1; }
[[ -z "${GITSTATUS_DAEMON_PID:-}" ]] || return 0 # already started
if [[ "${BASH_SOURCE[0]}" == */* ]]; then
local gitstatus_plugin_dir="${BASH_SOURCE[0]%/*}"
if [[ "$gitstatus_plugin_dir" != /* ]]; then
gitstatus_plugin_dir="$PWD"/"$gitstatus_plugin_dir"
fi
else
local gitstatus_plugin_dir="$PWD"
fi
local tmpdir req_fifo resp_fifo culprit
function gitstatus_start_impl() {
local log_level="${GITSTATUS_LOG_LEVEL:-}"
[[ -n "$log_level" || "${GITSTATUS_ENABLE_LOGGING:-0}" != 1 ]] || log_level=INFO
local uname_sm
uname_sm="$(command uname -sm)" || return
uname_sm="${uname_sm,,}"
local uname_s="${uname_sm% *}"
local uname_m="${uname_sm#* }"
if [[ "${GITSTATUS_NUM_THREADS:-0}" -gt 0 ]]; then
local threads="$GITSTATUS_NUM_THREADS"
else
local cpus
if ! command -v sysctl &>/dev/null || [[ "$uname_s" == linux ]] ||
! cpus="$(command sysctl -n hw.ncpu)"; then
if ! command -v getconf &>/dev/null || ! cpus="$(command getconf _NPROCESSORS_ONLN)"; then
cpus=8
fi
fi
local threads=$((cpus > 16 ? 32 : cpus > 0 ? 2 * cpus : 16))
fi
local daemon_args=(
--parent-pid="$$"
--num-threads="$threads"
--max-num-staged="$max_num_staged"
--max-num-unstaged="$max_num_unstaged"
--max-num-conflicted="$max_num_conflicted"
--max-num-untracked="$max_num_untracked"
--dirty-max-index-size="$max_dirty"
--repo-ttl-seconds="$ttl"
$extra_flags)
tmpdir="$(command mktemp -d "${TMPDIR:-/tmp}"/gitstatus.bash.$$.XXXXXXXXXX)" || return
if [[ -n "$log_level" ]]; then
GITSTATUS_DAEMON_LOG="$tmpdir"/daemon.log
[[ "$log_level" == INFO ]] || daemon_args+=(--log-level="$log_level")
else
GITSTATUS_DAEMON_LOG=/dev/null
fi
req_fifo="$tmpdir"/req.fifo
resp_fifo="$tmpdir"/resp.fifo
command mkfifo -- "$req_fifo" "$resp_fifo" || return
{
(
trap '' INT QUIT TSTP
[[ "$GITSTATUS_DAEMON_LOG" == /dev/null ]] || set -x
builtin cd /
(
local fd_in fd_out
exec {fd_in}<"$req_fifo" {fd_out}>>"$resp_fifo" || exit
echo "$BASHPID" >&"$fd_out"
local _gitstatus_bash_daemon _gitstatus_bash_version _gitstatus_bash_downloaded
function _gitstatus_set_daemon() {
_gitstatus_bash_daemon="$1"
_gitstatus_bash_version="$2"
_gitstatus_bash_downloaded="$3"
}
set -- -d "$gitstatus_plugin_dir" -s "$uname_s" -m "$uname_m" \
-p "printf '.\036' >&$fd_out" -e "$fd_out" -- _gitstatus_set_daemon
[[ "${GITSTATUS_AUTO_INSTALL:-1}" -ne 0 ]] || set -- -n "$@"
source "$gitstatus_plugin_dir"/install || return
[[ -n "$_gitstatus_bash_daemon" ]] || return
[[ -n "$_gitstatus_bash_version" ]] || return
[[ "$_gitstatus_bash_downloaded" == [01] ]] || return
local sig=(TERM ILL PIPE)
if (( UID == EUID )); then
local home=~
else
local user
user="$(command id -un)" || return
[[ "$user" =~ ^[a-zA-Z0-9_,.-]+$ ]] || return
eval "local home=~$user"
[[ -n "$home" ]] || return
fi
if [[ -x "$_gitstatus_bash_daemon" ]]; then
HOME="$home" "$_gitstatus_bash_daemon" \
-G "$_gitstatus_bash_version" "${daemon_args[@]}" <&"$fd_in" >&"$fd_out" &
local pid=$!
trap "trap - ${sig[*]}; kill $pid &>/dev/null" ${sig[@]}
wait "$pid"
local ret=$?
trap - ${sig[@]}
case "$ret" in
0|129|130|131|137|141|143|159)
echo -nE $'}bye\x1f0\x1e' >&"$fd_out"
exit "$ret"
;;
esac
fi
(( ! _gitstatus_bash_downloaded )) || return
[[ "${GITSTATUS_AUTO_INSTALL:-1}" -ne 0 ]] || return
[[ "$_gitstatus_bash_daemon" == \
"${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}"/* ]] || return
set -- -f "$@"
_gitstatus_bash_daemon=
_gitstatus_bash_version=
_gitstatus_bash_downloaded=
source "$gitstatus_plugin_dir"/install || return
[[ -n "$_gitstatus_bash_daemon" ]] || return
[[ -n "$_gitstatus_bash_version" ]] || return
[[ "$_gitstatus_bash_downloaded" == 1 ]] || return
HOME="$home" "$_gitstatus_bash_daemon" \
-G "$_gitstatus_bash_version" "${daemon_args[@]}" <&"$fd_in" >&"$fd_out" &
local pid=$!
trap "trap - ${sig[*]}; kill $pid &>/dev/null" ${sig[@]}
wait "$pid"
trap - ${sig[@]}
echo -nE $'}bye\x1f0\x1e' >&"$fd_out"
) & disown
) & disown
} 0</dev/null &>"$GITSTATUS_DAEMON_LOG"
exec {_GITSTATUS_REQ_FD}>>"$req_fifo" {_GITSTATUS_RESP_FD}<"$resp_fifo" || return
command rm -f -- "$req_fifo" "$resp_fifo" || return
[[ "$GITSTATUS_DAEMON_LOG" != /dev/null ]] || command rmdir -- "$tmpdir" 2>/dev/null
IFS='' read -r -u $_GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID || return
[[ "$GITSTATUS_DAEMON_PID" == [1-9]* ]] || return
local reply
echo -nE $'}hello\x1f\x1e' >&$_GITSTATUS_REQ_FD || return
local dl=
while true; do
reply=
if ! IFS='' read -rd $'\x1e' -u $_GITSTATUS_RESP_FD -t "$timeout" reply; then
culprit="$reply"
return 1
fi
[[ "$reply" == $'}hello\x1f0' ]] && break
if [[ -z "$dl" ]]; then
dl=1
if [[ -t 2 ]]; then
local spinner=('\b\033[33m-\033[0m' '\b\033[33m\\\033[0m' '\b\033[33m|\033[0m' '\b\033[33m/\033[0m')
>&2 printf '[\033[33mgitstatus\033[0m] fetching \033[32mgitstatusd\033[0m .. '
else
local spinner=('.')
>&2 printf '[gitstatus] fetching gitstatusd ..'
fi
fi
>&2 printf "${spinner[0]}"
spinner=("${spinner[@]:1}" "${spinner[0]}")
done
if [[ -n "$dl" ]]; then
if [[ -t 2 ]]; then
>&2 printf '\b[\033[32mok\033[0m]\n'
else
>&2 echo ' [ok]'
fi
fi
_GITSTATUS_DIRTY_MAX_INDEX_SIZE=$max_dirty
_GITSTATUS_CLIENT_PID="$BASHPID"
}
if ! gitstatus_start_impl; then
>&2 printf '\n'
>&2 printf '[\033[31mERROR\033[0m]: gitstatus failed to initialize.\n'
if [[ -n "${culprit-}" ]]; then
>&2 printf '\n%s\n' "$culprit"
fi
[[ -z "${req_fifo:-}" ]] || command rm -f "$req_fifo"
[[ -z "${resp_fifo:-}" ]] || command rm -f "$resp_fifo"
unset -f gitstatus_start_impl
gitstatus_stop
return 1
fi
export _GITSTATUS_CLIENT_PID _GITSTATUS_REQ_FD _GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID
unset -f gitstatus_start_impl
}
# Stops gitstatusd if it's running.
function gitstatus_stop() {
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
unset _GITSTATUS_DIRTY_MAX_INDEX_SIZE _GITSTATUS_CLIENT_PID
}
# Retrives status of a git repository from a directory under its working tree.
#
# Usage: gitstatus_query [OPTION]...
#
# -d STR Directory to query. Defaults to $PWD. Has no effect if GIT_DIR is set.
# -t FLOAT Timeout in seconds. Will block for at most this long. If no results
# are available by then, will return error.
# -p Don't compute anything that requires reading Git index. If this option is used,
# the following parameters will be 0: VCS_STATUS_INDEX_SIZE,
# VCS_STATUS_{NUM,HAS}_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED}.
#
# On success sets VCS_STATUS_RESULT to one of the following values:
#
# norepo-sync The directory doesn't belong to a git repository.
# ok-sync The directory belongs to a git repository.
#
# If VCS_STATUS_RESULT is ok-sync, additional variables are set:
#
# 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.
# VCS_STATUS_REMOTE_URL Remote URL. Can be empty.
# VCS_STATUS_ACTION Repository state, A.K.A. action. Can be empty.
# VCS_STATUS_INDEX_SIZE The number of files in the index.
# VCS_STATUS_NUM_STAGED The number of staged changes.
# VCS_STATUS_NUM_CONFLICTED The number of conflicted changes.
# VCS_STATUS_NUM_UNSTAGED The number of unstaged changes.
# VCS_STATUS_NUM_UNTRACKED The number of untracked files.
# VCS_STATUS_HAS_STAGED 1 if there are staged changes, 0 otherwise.
# VCS_STATUS_HAS_CONFLICTED 1 if there are conflicted changes, 0 otherwise.
# VCS_STATUS_HAS_UNSTAGED 1 if there are unstaged changes, 0 if there aren't, -1 if
# unknown.
# VCS_STATUS_NUM_STAGED_NEW The number of staged new files. Note that renamed files
# are reported as deleted plus new.
# VCS_STATUS_NUM_STAGED_DELETED The number of staged deleted files. Note that renamed files
# are reported as deleted plus new.
# VCS_STATUS_NUM_UNSTAGED_DELETED The number of unstaged deleted files. Note that renamed files
# are reported as deleted plus new.
# VCS_STATUS_HAS_UNTRACKED 1 if there are untracked files, 0 if there aren't, -1 if
# unknown.
# VCS_STATUS_COMMITS_AHEAD Number of commits the current branch is ahead of upstream.
# Non-negative integer.
# VCS_STATUS_COMMITS_BEHIND Number of commits the current branch is behind upstream.
# Non-negative integer.
# VCS_STATUS_STASHES Number of stashes. Non-negative integer.
# VCS_STATUS_TAG The last tag (in lexicographical order) that points to the same
# commit as HEAD.
# VCS_STATUS_PUSH_REMOTE_NAME The push remote name, e.g. "upstream" or "origin".
# VCS_STATUS_PUSH_REMOTE_URL Push remote URL. Can be empty.
# VCS_STATUS_PUSH_COMMITS_AHEAD Number of commits the current branch is ahead of push remote.
# Non-negative integer.
# VCS_STATUS_PUSH_COMMITS_BEHIND Number of commits the current branch is behind push remote.
# Non-negative integer.
# VCS_STATUS_NUM_SKIP_WORKTREE The number of files in the index with skip-worktree bit set.
# Non-negative integer.
# VCS_STATUS_NUM_ASSUME_UNCHANGED The number of files in the index with assume-unchanged bit set.
# Non-negative integer.
#
# The point of reporting -1 via VCS_STATUS_HAS_* is to allow the command to skip scanning files in
# large repos. See -m flag of gitstatus_start.
#
# gitstatus_query returns an error if gitstatus_start hasn't been called in the same
# shell or the call had failed.
function gitstatus_query() {
unset OPTIND
local opt dir timeout=() no_diff=0
while getopts "d:c:t:p" opt "$@"; do
case "$opt" in
d) dir=$OPTARG;;
t) timeout=(-t "$OPTARG");;
p) no_diff=1;;
*) return 1;;
esac
done
(( OPTIND == $# + 1 )) || { echo "usage: gitstatus_query [OPTION]..." >&2; return 1; }
[[ -n "$GITSTATUS_DAEMON_PID" ]] || return # not started
local req_id="$RANDOM.$RANDOM.$RANDOM.$RANDOM"
if [[ -z "${GIT_DIR:-}" ]]; then
[[ "$dir" == /* ]] || dir="$(pwd -P)/$dir" || return
elif [[ "$GIT_DIR" == /* ]]; then
dir=:"$GIT_DIR"
else
dir=:"$(pwd -P)/$GIT_DIR" || return
fi
echo -nE "$req_id"$'\x1f'"$dir"$'\x1f'"$no_diff"$'\x1e' >&$_GITSTATUS_REQ_FD || return
local -a resp
while true; do
IFS=$'\x1f' read -rd $'\x1e' -a resp -u $_GITSTATUS_RESP_FD "${timeout[@]}" || return
[[ "${resp[0]}" == "$req_id" ]] && break
done
if [[ "${resp[1]}" == 1 ]]; then
VCS_STATUS_RESULT=ok-sync
VCS_STATUS_WORKDIR="${resp[2]}"
VCS_STATUS_COMMIT="${resp[3]}"
VCS_STATUS_LOCAL_BRANCH="${resp[4]}"
VCS_STATUS_REMOTE_BRANCH="${resp[5]}"
VCS_STATUS_REMOTE_NAME="${resp[6]}"
VCS_STATUS_REMOTE_URL="${resp[7]}"
VCS_STATUS_ACTION="${resp[8]}"
VCS_STATUS_INDEX_SIZE="${resp[9]}"
VCS_STATUS_NUM_STAGED="${resp[10]}"
VCS_STATUS_NUM_UNSTAGED="${resp[11]}"
VCS_STATUS_NUM_CONFLICTED="${resp[12]}"
VCS_STATUS_NUM_UNTRACKED="${resp[13]}"
VCS_STATUS_COMMITS_AHEAD="${resp[14]}"
VCS_STATUS_COMMITS_BEHIND="${resp[15]}"
VCS_STATUS_STASHES="${resp[16]}"
VCS_STATUS_TAG="${resp[17]}"
VCS_STATUS_NUM_UNSTAGED_DELETED="${resp[18]}"
VCS_STATUS_NUM_STAGED_NEW="${resp[19]:-0}"
VCS_STATUS_NUM_STAGED_DELETED="${resp[20]:-0}"
VCS_STATUS_PUSH_REMOTE_NAME="${resp[21]:-}"
VCS_STATUS_PUSH_REMOTE_URL="${resp[22]:-}"
VCS_STATUS_PUSH_COMMITS_AHEAD="${resp[23]:-0}"
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
VCS_STATUS_HAS_UNSTAGED=-1
VCS_STATUS_HAS_CONFLICTED=-1
VCS_STATUS_HAS_UNTRACKED=-1
else
VCS_STATUS_HAS_UNSTAGED=$((VCS_STATUS_NUM_UNSTAGED > 0))
VCS_STATUS_HAS_CONFLICTED=$((VCS_STATUS_NUM_CONFLICTED > 0))
VCS_STATUS_HAS_UNTRACKED=$((VCS_STATUS_NUM_UNTRACKED > 0))
fi
else
VCS_STATUS_RESULT=norepo-sync
unset VCS_STATUS_WORKDIR
unset VCS_STATUS_COMMIT
unset VCS_STATUS_LOCAL_BRANCH
unset VCS_STATUS_REMOTE_BRANCH
unset VCS_STATUS_REMOTE_NAME
unset VCS_STATUS_REMOTE_URL
unset VCS_STATUS_ACTION
unset VCS_STATUS_INDEX_SIZE
unset VCS_STATUS_NUM_STAGED
unset VCS_STATUS_NUM_UNSTAGED
unset VCS_STATUS_NUM_CONFLICTED
unset VCS_STATUS_NUM_UNTRACKED
unset VCS_STATUS_HAS_STAGED
unset VCS_STATUS_HAS_UNSTAGED
unset VCS_STATUS_HAS_CONFLICTED
unset VCS_STATUS_HAS_UNTRACKED
unset VCS_STATUS_COMMITS_AHEAD
unset VCS_STATUS_COMMITS_BEHIND
unset VCS_STATUS_STASHES
unset VCS_STATUS_TAG
unset VCS_STATUS_NUM_UNSTAGED_DELETED
unset VCS_STATUS_NUM_STAGED_NEW
unset VCS_STATUS_NUM_STAGED_DELETED
unset VCS_STATUS_PUSH_REMOTE_NAME
unset VCS_STATUS_PUSH_REMOTE_URL
unset VCS_STATUS_PUSH_COMMITS_AHEAD
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
}
# Usage: gitstatus_check.
#
# Returns 0 if and only if gitstatus_start has succeeded previously.
# If it returns non-zero, gitstatus_query is guaranteed to return non-zero.
function gitstatus_check() {
[[ -n "$GITSTATUS_DAEMON_PID" ]]
}

View File

@@ -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
@@ -55,7 +57,7 @@ autoload -Uz add-zsh-hook || return
zmodload zsh/datetime zsh/system || return
zmodload -F zsh/files b:zf_rm || return
typeset -g _gitstatus_plugin_dir=${${(%):-%x}:A:h}
typeset -g _gitstatus_plugin_dir"${1:-}"="${${(%):-%x}:A:h}"
# Retrives status of a git repo from a directory under its working tree.
#
@@ -88,6 +90,8 @@ typeset -g _gitstatus_plugin_dir=${${(%):-%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.
@@ -138,9 +142,11 @@ typeset -g _gitstatus_plugin_dir=${${(%):-%x}:A:h}
#
# It's illegal to call gitstatus_query if the last asynchronous call with the same NAME hasn't
# completed yet. If you need to issue concurrent requests, use different NAME arguments.
function gitstatus_query() {
function gitstatus_query"${1:-}"() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
local fsuf=${${(%):-%N}#gitstatus_query}
unset VCS_STATUS_RESULT
local opt dir callback OPTARG
@@ -166,13 +172,13 @@ function gitstatus_query() {
done
if (( OPTIND != ARGC )); then
print -ru2 -- "gitstatus_start: exactly one positional argument is required"
print -ru2 -- "gitstatus_query: exactly one positional argument is required"
return 1
fi
local name=$*[OPTIND]
if [[ $name != [[:IDENT:]]## ]]; then
print -ru2 -- "gitstatus_start: invalid positional argument: $name"
print -ru2 -- "gitstatus_query: invalid positional argument: $name"
return 1
fi
@@ -184,6 +190,12 @@ function gitstatus_query() {
[[ $GIT_DIR == /* ]] && dir=:$GIT_DIR || dir=:${(%):-%/}/$GIT_DIR
fi
if [[ $dir != (|:)/* ]]; then
typeset -g VCS_STATUS_RESULT=norepo-sync
_gitstatus_clear$fsuf
return 0
fi
local -i req_fd=${(P)${:-_GITSTATUS_REQ_FD_$name}}
local req_id=$EPOCHREALTIME
print -rnu $req_fd -- $req_id' '$callback$'\x1f'$dir$'\x1f'$no_diff$'\x1e' || return
@@ -192,10 +204,10 @@ function gitstatus_query() {
if (( timeout == 0 )); then
typeset -g VCS_STATUS_RESULT=tout
_gitstatus_clear
_gitstatus_clear$fsuf
else
while true; do
_gitstatus_process_response $name $timeout $req_id || return
_gitstatus_process_response$fsuf $name $timeout $req_id || return
[[ $VCS_STATUS_RESULT == *-async ]] || break
done
fi
@@ -215,8 +227,11 @@ function gitstatus_query() {
#
# If a callback gets called, VCS_STATUS_* parameters are set as in gitstatus_query.
# VCS_STATUS_RESULT is either norepo-async or ok-async.
function gitstatus_process_results() {
function gitstatus_process_results"${1:-}"() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
local fsuf=${${(%):-%N}#gitstatus_process_results}
local opt OPTARG
local -i OPTIND
local -F timeout=-1
@@ -249,18 +264,18 @@ function gitstatus_process_results() {
(( _GITSTATUS_STATE_$name == 2 )) || return
while (( _GITSTATUS_NUM_INFLIGHT_$name )); do
_gitstatus_process_response $name $timeout '' || return
_gitstatus_process_response$fsuf $name $timeout '' || return
[[ $VCS_STATUS_RESULT == *-async ]] || break
done
return 0
}
function _gitstatus_clear() {
function _gitstatus_clear"${1:-}"() {
unset VCS_STATUS_{WORKDIR,COMMIT,LOCAL_BRANCH,REMOTE_BRANCH,REMOTE_NAME,REMOTE_URL,ACTION,INDEX_SIZE,NUM_STAGED,NUM_UNSTAGED,NUM_CONFLICTED,NUM_UNTRACKED,HAS_STAGED,HAS_UNSTAGED,HAS_CONFLICTED,HAS_UNTRACKED,COMMITS_AHEAD,COMMITS_BEHIND,STASHES,TAG,NUM_UNSTAGED_DELETED,NUM_STAGED_NEW,NUM_STAGED_DELETED,PUSH_REMOTE_NAME,PUSH_REMOTE_URL,PUSH_COMMITS_AHEAD,PUSH_COMMITS_BEHIND,NUM_SKIP_WORKTREE,NUM_ASSUME_UNCHANGED}
}
function _gitstatus_process_response() {
function _gitstatus_process_response"${1:-}"() {
local name=$1 timeout req_id=$3 buf
local -i resp_fd=_GITSTATUS_RESP_FD_$name
local -i dirty_max_index_size=_GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name
@@ -270,17 +285,17 @@ function _gitstatus_process_response() {
if (( $? == 4 )); then
if [[ -n $req_id ]]; then
typeset -g VCS_STATUS_RESULT=tout
_gitstatus_clear
_gitstatus_clear$fsuf
fi
return 0
else
gitstatus_stop $name
gitstatus_stop$fsuf $name
return 1
fi
}
while [[ $buf != *$'\x1e' ]]; do
if ! sysread -i $resp_fd 'buf[$#buf+1]'; then
gitstatus_stop $name
gitstatus_stop$fsuf $name
return 1
fi
done
@@ -318,7 +333,9 @@ function _gitstatus_process_response() {
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))
@@ -339,7 +356,7 @@ function _gitstatus_process_response() {
else
typeset -g VCS_STATUS_RESULT=norepo-async
fi
_gitstatus_clear
_gitstatus_clear$fsuf
fi
(( --_GITSTATUS_NUM_INFLIGHT_$name ))
[[ $VCS_STATUS_RESULT == *-async ]] && emulate zsh -c "${resp[1]#* }"
@@ -348,86 +365,89 @@ function _gitstatus_process_response() {
return 0
}
function _gitstatus_daemon() {
# Close stdin to work around bugs in Cygwin 32-bit.
exec 0<&- 2>>$daemon_log || return
function _gitstatus_daemon"${1:-}"() {
local -i pipe_fd
exec 0<&- {pipe_fd}>&1 1>>$daemon_log 2>&1 || return
local pgid=$sysparams[pid]
[[ $pgid == <1-> ]] || return
builtin cd -q / || return
{
{
trap '' PIPE
if [[ -z $GITSTATUS_DAEMON || $GITSTATUS_NUM_THREADS != <1-> ]]; then
local kernel
kernel="${(L)$(uname -s)}" || return
[[ -n $kernel ]] || return
fi
if [[ $GITSTATUS_DAEMON == /* ]]; then
local daemons=($GITSTATUS_DAEMON)
elif (( $+commands[$GITSTATUS_DAEMON] )); then
local daemons=($commands[$GITSTATUS_DAEMON])
elif [[ -n $GITSTATUS_DAEMON ]]; then
local daemons=($_gitstatus_plugin_dir/{usrbin,bin}/$GITSTATUS_DAEMON)
else
local -aU os=($kernel)
case $kernel in
linux)
local os_flavor
os_flavor="${(L)$(uname -o 2>/dev/null)}" && os+=(${(M)os_flavor:#android})
;;
cygwin_nt-*) os+=(cygwin_nt-10.0);;
msys_nt-*) os+=(msys_nt-10.0);;
mingw32_nt-*) os+=(msys_nt-10.0);;
mingw64_nt-*) os+=(msys_nt-10.0);;
esac
local arch
arch="${(L)$(uname -m)}" || return
[[ -n $arch ]] || return
local daemons=(
$_gitstatus_plugin_dir/{usrbin,bin}/gitstatusd-${^os}-$arch{,-static})
fi
local files=(${^daemons}(N:A))
daemons=(${^files}(N*))
if (( stderr_fd && $#daemons != $#files )); then
unsetopt xtrace
print -ru2 -- ''
print -ru2 -- 'ERROR: missing execute permissions on gitstatusd file(s):'
print -ru2 -- ''
print -ru2 -- ' '${(pj:\n :)${files:|daemons}}
print -ru2 -- ''
setopt xtrace
fi
(( $#daemons )) || return
local uname_sm
uname_sm="${${(L)$(command uname -sm)}//ı/i}" || return
[[ $uname_sm == [^' ']##' '[^' ']## ]] || return
local uname_s=${uname_sm% *}
local uname_m=${uname_sm#* }
if [[ $GITSTATUS_NUM_THREADS == <1-> ]]; then
args+=(-t $GITSTATUS_NUM_THREADS)
else
local cpus
if (( ! $+commands[sysctl] )) || [[ $kernel == linux ]] ||
! cpus="$(sysctl -n hw.ncpu)"; then
if (( ! $+commands[getconf] )) || ! cpus="$(getconf _NPROCESSORS_ONLN)"; then
if (( ! $+commands[sysctl] )) || [[ $uname_s == linux ]] ||
! cpus="$(command sysctl -n hw.ncpu)"; then
if (( ! $+commands[getconf] )) || ! cpus="$(command getconf _NPROCESSORS_ONLN)"; then
cpus=8
fi
fi
args+=(-t $((cpus > 16 ? 32 : cpus > 0 ? 2 * cpus : 16)))
fi
mkfifo -- $file_prefix.fifo || return
print -rn -- ${(l:20:)pgid} || return
exec <$file_prefix.fifo || return
zf_rm -- $file_prefix.fifo || return
command mkfifo -- $file_prefix.fifo || return
print -rnu $pipe_fd -- ${(l:20:)pgid} || return
exec <$file_prefix.fifo || return
zf_rm -- $file_prefix.fifo || return
local daemon
for daemon in $daemons; do
$daemon "${(@)args}"
local _gitstatus_zsh_daemon _gitstatus_zsh_version _gitstatus_zsh_downloaded
function _gitstatus_set_daemon$fsuf() {
_gitstatus_zsh_daemon="$1"
_gitstatus_zsh_version="$2"
_gitstatus_zsh_downloaded="$3"
}
local gitstatus_plugin_dir_var=_gitstatus_plugin_dir$fsuf
local gitstatus_plugin_dir=${(P)gitstatus_plugin_dir_var}
builtin set -- -d $gitstatus_plugin_dir -s $uname_s -m $uname_m \
-p "printf '\\001' >&$pipe_fd" -e $pipe_fd -- _gitstatus_set_daemon$fsuf
[[ ${GITSTATUS_AUTO_INSTALL:-1} == (|-|+)<1-> ]] || builtin set -- -n "$@"
builtin source $gitstatus_plugin_dir/install || return
[[ -n $_gitstatus_zsh_daemon ]] || return
[[ -n $_gitstatus_zsh_version ]] || return
[[ $_gitstatus_zsh_downloaded == [01] ]] || return
if (( UID == EUID )); then
local home=~
else
local user
user="$(command id -un)" || return
local home=${userdirs[$user]}
[[ -n $home ]] || return
fi
if [[ -x $_gitstatus_zsh_daemon ]]; then
HOME=$home $_gitstatus_zsh_daemon -G $_gitstatus_zsh_version "${(@)args}" >&$pipe_fd
local -i ret=$?
(( ret == 0 || ret == 10 || ret > 128 )) && return ret
done
[[ $ret == (0|129|130|131|137|141|143|159) ]] && return ret
fi
(( ! _gitstatus_zsh_downloaded )) || return
[[ ${GITSTATUS_AUTO_INSTALL:-1} == (|-|+)<1-> ]] || return
[[ $_gitstatus_zsh_daemon == \
${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}/* ]] || return
builtin set -- -f "$@"
_gitstatus_zsh_daemon=
_gitstatus_zsh_version=
_gitstatus_zsh_downloaded=
builtin source $gitstatus_plugin_dir/install || return
[[ -n $_gitstatus_zsh_daemon ]] || return
[[ -n $_gitstatus_zsh_version ]] || return
[[ $_gitstatus_zsh_downloaded == 1 ]] || return
HOME=$home $_gitstatus_zsh_daemon -G $_gitstatus_zsh_version "${(@)args}" >&$pipe_fd
} always {
local -i ret=$?
zf_rm -f -- $file_prefix.lock $file_prefix.fifo
@@ -477,10 +497,12 @@ function _gitstatus_daemon() {
#
# -D Unless this option is specified, report zero staged, unstaged and conflicted
# changes for repositories with bash.showDirtyState = false.
function gitstatus_start() {
function gitstatus_start"${1:-}"() {
emulate -L zsh -o no_aliases -o no_bg_nice -o extended_glob -o typeset_silent || return
print -rnu2 || return
local fsuf=${${(%):-%N}#gitstatus_start}
local opt OPTARG
local -i OPTIND
local -F timeout=5
@@ -506,7 +528,7 @@ function gitstatus_start() {
args+=(-$opt $OPTARG)
[[ $opt == m ]] && dirty_max_index_size=OPTARG
;;
e|U|W|D) args+=$opt;;
e|U|W|D) args+=-$opt;;
+(e|U|W|D)) args=(${(@)args:#-$opt});;
\?) print -ru2 -- "gitstatus_start: invalid option: $OPTARG" ; return 1;;
:) print -ru2 -- "gitstatus_start: missing required argument: $OPTARG"; return 1;;
@@ -526,206 +548,269 @@ function gitstatus_start() {
fi
local -i lock_fd resp_fd stderr_fd
local file_prefix xtrace=/dev/null daemon_log=/dev/null
if (( _GITSTATUS_STATE_$name )); then
(( async )) && return
(( _GITSTATUS_STATE_$name == 2 )) && return
lock_fd=_GITSTATUS_LOCK_FD_$name
resp_fd=_GITSTATUS_RESP_FD_$name
xtrace=${(P)${:-GITSTATUS_XTRACE_$name}}
daemon_log=${(P)${:-GITSTATUS_DAEMON_LOG_$name}}
file_prefix=${(P)${:-_GITSTATUS_FILE_PREFIX_$name}}
else
typeset -gi _GITSTATUS_START_COUNTER
local log_level=$GITSTATUS_LOG_LEVEL
local file_prefix=${${TMPDIR:-/tmp}:A}/gitstatus.$name.$EUID
file_prefix+=.$sysparams[pid].$EPOCHSECONDS.$((++_GITSTATUS_START_COUNTER))
(( GITSTATUS_ENABLE_LOGGING )) && : ${log_level:=INFO}
if [[ -n $log_level ]]; then
xtrace=$file_prefix.xtrace.log
daemon_log=$file_prefix.daemon.log
fi
args+=(-v ${log_level:-FATAL})
typeset -g GITSTATUS_XTRACE_$name=$xtrace
typeset -g GITSTATUS_DAEMON_LOG_$name=$daemon_log
typeset -g _GITSTATUS_FILE_PREFIX_$name=$file_prefix
typeset -gi _GITSTATUS_CLIENT_PID_$name="sysparams[pid]"
typeset -gi _GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name=dirty_max_index_size
fi
local file_prefix xtrace=/dev/null daemon_log=/dev/null culprit
() {
if [[ $xtrace != /dev/null && -o no_xtrace ]]; then
exec {stderr_fd}>&2 || return
exec 2>>$xtrace || return
setopt xtrace
{
if (( _GITSTATUS_STATE_$name )); then
(( async )) && return
(( _GITSTATUS_STATE_$name == 2 )) && return
lock_fd=_GITSTATUS_LOCK_FD_$name
resp_fd=_GITSTATUS_RESP_FD_$name
xtrace=${(P)${:-GITSTATUS_XTRACE_$name}}
daemon_log=${(P)${:-GITSTATUS_DAEMON_LOG_$name}}
file_prefix=${(P)${:-_GITSTATUS_FILE_PREFIX_$name}}
else
typeset -gi _GITSTATUS_START_COUNTER
local log_level=$GITSTATUS_LOG_LEVEL
local file_prefix=${${TMPDIR:-/tmp}:A}/gitstatus.$name.$EUID
file_prefix+=.$sysparams[pid].$EPOCHSECONDS.$((++_GITSTATUS_START_COUNTER))
(( GITSTATUS_ENABLE_LOGGING )) && : ${log_level:=INFO}
if [[ -n $log_level ]]; then
xtrace=$file_prefix.xtrace.log
daemon_log=$file_prefix.daemon.log
fi
args+=(-v ${log_level:-FATAL})
typeset -g GITSTATUS_XTRACE_$name=$xtrace
typeset -g GITSTATUS_DAEMON_LOG_$name=$daemon_log
typeset -g _GITSTATUS_FILE_PREFIX_$name=$file_prefix
typeset -gi _GITSTATUS_CLIENT_PID_$name="sysparams[pid]"
typeset -gi _GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name=dirty_max_index_size
fi
setopt monitor || return
if (( ! _GITSTATUS_STATE_$name )); then
if [[ -r /proc/version && "$(</proc/version)" == *Microsoft* ]]; then
lock_fd=-1
else
print -rn >$file_prefix.lock || return
zsystem flock -f lock_fd $file_prefix.lock || return
[[ $lock_fd == <1-> ]] || return
() {
if [[ $xtrace != /dev/null && -o no_xtrace ]]; then
exec {stderr_fd}>&2 || return
exec 2>>$xtrace || return
setopt xtrace
fi
typeset -gi _GITSTATUS_LOCK_FD_$name=lock_fd
typeset -gi GITSTATUS_DAEMON_PID_$name="${sysparams[procsubstpid]:--1}"
setopt monitor || return
if [[ -n $USERPROFILE && -d /cygdrive && -d /proc/self/fd ]]; then
# Work around bugs in Cygwin 32-bit.
#
# This hangs:
#
# emulate -L zsh
# () { exec {fd}< $1 } <(:)
# =true # hangs here
#
# This hangs:
#
# sysopen -r -u fd <(:)
local -i fd
exec {fd}< <(_gitstatus_daemon) || return
{
[[ -r /proc/self/fd/$fd ]] || return
sysopen -r -o cloexec -u resp_fd /proc/self/fd/$fd || return
} always {
exec {fd} >&- || return
}
else
sysopen -r -o cloexec -u resp_fd <(_gitstatus_daemon) || return
fi
[[ $resp_fd == <1-> ]] || return
typeset -gi _GITSTATUS_RESP_FD_$name=resp_fd
typeset -gi _GITSTATUS_STATE_$name=1
fi
if (( ! async )); then
(( _GITSTATUS_CLIENT_PID_$name == sysparams[pid] )) || return
local pgid
while (( $#pgid < 20 )); do
[[ -t $resp_fd ]]
sysread -s $((20 - $#pgid)) -t $timeout -i $resp_fd 'pgid[$#pgid+1]' || return
done
[[ $pgid == ' '#<1-> ]] || return
typeset -gi GITSTATUS_DAEMON_PID_$name=pgid
sysopen -w -o cloexec -u req_fd -- $file_prefix.fifo || return
[[ $req_fd == <1-> ]] || return
typeset -gi _GITSTATUS_REQ_FD_$name=req_fd
function _gitstatus_process_response_$name() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
local name=${${(%):-%N}#_gitstatus_process_response_}
if (( ARGC == 1 )); then
_gitstatus_process_response $name 0 ''
if (( ! _GITSTATUS_STATE_$name )); then
if [[ -r /proc/version && "$(</proc/version)" == *Microsoft* ]]; then
lock_fd=-1
else
gitstatus_stop $name
print -rn >$file_prefix.lock || return
zsystem flock -f lock_fd $file_prefix.lock || return
[[ $lock_fd == <1-> ]] || return
fi
}
if ! zle -F $resp_fd _gitstatus_process_response_$name; then
unfunction _gitstatus_process_response_$name
return 1
typeset -gi _GITSTATUS_LOCK_FD_$name=lock_fd
if [[ $OSTYPE == cygwin* && -d /proc/self/fd ]]; then
# Work around bugs in Cygwin 32-bit.
#
# This hangs:
#
# emulate -L zsh
# () { exec {fd}< $1 } <(:)
# =true # hangs here
#
# This hangs:
#
# sysopen -r -u fd <(:)
local -i fd
exec {fd}< <(_gitstatus_daemon$fsuf) || return
{
[[ -r /proc/self/fd/$fd ]] || return
sysopen -r -o cloexec -u resp_fd /proc/self/fd/$fd || return
} always {
exec {fd} >&- || return
}
else
sysopen -r -o cloexec -u resp_fd <(_gitstatus_daemon$fsuf) || return
fi
typeset -gi GITSTATUS_DAEMON_PID_$name="${sysparams[procsubstpid]:--1}"
[[ $resp_fd == <1-> ]] || return
typeset -gi _GITSTATUS_RESP_FD_$name=resp_fd
typeset -gi _GITSTATUS_STATE_$name=1
fi
function _gitstatus_cleanup_$name() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
local name=${${(%):-%N}#_gitstatus_cleanup_}
if (( ! async )); then
(( _GITSTATUS_CLIENT_PID_$name == sysparams[pid] )) || return
gitstatus_stop $name
}
if ! add-zsh-hook zshexit _gitstatus_cleanup_$name; then
unfunction _gitstatus_cleanup_$name
return 1
local pgid
while (( $#pgid < 20 )); do
[[ -t $resp_fd ]]
sysread -s $((20 - $#pgid)) -t $timeout -i $resp_fd 'pgid[$#pgid+1]' || return
done
[[ $pgid == ' '#<1-> ]] || return
typeset -gi GITSTATUS_DAEMON_PID_$name=pgid
sysopen -w -o cloexec -u req_fd -- $file_prefix.fifo || return
[[ $req_fd == <1-> ]] || return
typeset -gi _GITSTATUS_REQ_FD_$name=req_fd
print -nru $req_fd -- $'}hello\x1f\x1e' || return
local expected=$'}hello\x1f0\x1e' actual
if (( $+functions[p10k] )) && [[ ! -t 1 && ! -t 0 ]]; then
local -F deadline='EPOCHREALTIME + 4'
else
local -F deadline='1'
fi
while true; do
[[ -t $resp_fd ]]
sysread -s 1 -t $timeout -i $resp_fd actual || return
[[ $expected == $actual* ]] && break
if [[ $actual != $'\1' ]]; then
[[ -t $resp_fd ]]
while sysread -t $timeout -i $resp_fd 'actual[$#actual+1]'; do
[[ -t $resp_fd ]]
done
culprit=$actual
return 1
fi
(( EPOCHREALTIME < deadline )) && continue
if (( deadline > 0 )); then
deadline=0
if (( stderr_fd )); then
unsetopt xtrace
exec 2>&$stderr_fd {stderr_fd}>&-
stderr_fd=0
fi
if (( $+functions[p10k] )); then
p10k clear-instant-prompt || return
fi
if [[ $name == POWERLEVEL9K ]]; then
local label=powerlevel10k
else
local label=gitstatus
fi
if [[ -t 2 ]]; then
local spinner=($'\b%3F-%f' $'\b%3F\\%f' $'\b%3F|%f' $'\b%3F/%f')
print -Prnu2 -- "[%3F$label%f] fetching %2Fgitstatusd%f .. "
else
local spinner=('.')
print -rnu2 -- "[$label] fetching gitstatusd .."
fi
fi
print -Prnu2 -- $spinner[1]
spinner=($spinner[2,-1] $spinner[1])
done
if (( deadline == 0 )); then
if [[ -t 2 ]]; then
print -Pru2 -- $'\b[%2Fok%f]'
else
print -ru2 -- ' [ok]'
fi
if [[ $xtrace != /dev/null && -o no_xtrace ]]; then
exec {stderr_fd}>&2 || return
exec 2>>$xtrace || return
setopt xtrace
fi
fi
while (( $#actual < $#expected )); do
[[ -t $resp_fd ]]
sysread -s $(($#expected - $#actual)) -t $timeout -i $resp_fd 'actual[$#actual+1]' || return
done
[[ $actual == $expected ]] || return
function _gitstatus_process_response_$name-$fsuf() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
local pair=${${(%):-%N}#_gitstatus_process_response_}
local name=${pair%%-*}
local fsuf=${pair#*-}
[[ $name == POWERLEVEL9K && $fsuf == _p9k_ ]] && eval $__p9k_intro_base
if (( ARGC == 1 )); then
_gitstatus_process_response$fsuf $name 0 ''
else
gitstatus_stop$fsuf $name
fi
}
if ! zle -F $resp_fd _gitstatus_process_response_$name-$fsuf; then
unfunction _gitstatus_process_response_$name-$fsuf
return 1
fi
function _gitstatus_cleanup_$name-$fsuf() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
local pair=${${(%):-%N}#_gitstatus_cleanup_}
local name=${pair%%-*}
local fsuf=${pair#*-}
(( _GITSTATUS_CLIENT_PID_$name == sysparams[pid] )) || return
gitstatus_stop$fsuf $name
}
if ! add-zsh-hook zshexit _gitstatus_cleanup_$name-$fsuf; then
unfunction _gitstatus_cleanup_$name-$fsuf
return 1
fi
if (( lock_fd != -1 )); then
zf_rm -- $file_prefix.lock || return
zsystem flock -u $lock_fd || return
fi
unset _GITSTATUS_LOCK_FD_$name
typeset -gi _GITSTATUS_STATE_$name=2
fi
}
} always {
local -i err=$?
(( stderr_fd )) && exec 2>&$stderr_fd {stderr_fd}>&-
(( err == 0 )) && return
print -nru $req_fd -- $'hello\x1f\x1e' || return
local expected=$'hello\x1f0\x1e' actual
while (( $#actual < $#expected )); do
[[ -t $resp_fd ]]
sysread -s $(($#expected - $#actual)) -t $timeout -i $resp_fd 'actual[$#actual+1]' || return
done
[[ $actual == $expected ]] || return
gitstatus_stop$fsuf $name
if (( lock_fd != -1 )); then
zf_rm -- $file_prefix.lock || return
zsystem flock -u $lock_fd || return
fi
unset _GITSTATUS_LOCK_FD_$name
typeset -gi _GITSTATUS_STATE_$name=2
setopt prompt_percent no_prompt_subst no_prompt_bang
(( $+functions[p10k] )) && p10k clear-instant-prompt
print -ru2 -- ''
print -Pru2 -- '[%F{red}ERROR%f]: gitstatus failed to initialize.'
print -ru2 -- ''
if [[ -n $culprit ]]; then
print -ru2 -- $culprit
return err
fi
if [[ -s $xtrace ]]; then
print -ru2 -- ''
print -Pru2 -- " Zsh log (%U${xtrace//\%/%%}%u):"
print -Pru2 -- '%F{yellow}'
print -lru2 -- "${(@)${(@f)$(<$xtrace)}/#/ }"
print -Pnru2 -- '%f'
fi
if [[ -s $daemon_log ]]; then
print -ru2 -- ''
print -Pru2 -- " Daemon log (%U${daemon_log//\%/%%}%u):"
print -Pru2 -- '%F{yellow}'
print -lru2 -- "${(@)${(@f)$(<$daemon_log)}/#/ }"
print -Pnru2 -- '%f'
fi
if [[ $GITSTATUS_LOG_LEVEL == DEBUG ]]; then
print -ru2 -- ''
print -ru2 -- ' System information:'
print -Pru2 -- '%F{yellow}'
print -ru2 -- " zsh: $ZSH_VERSION"
print -ru2 -- " uname -a: $(command uname -a)"
print -Pru2 -- '%f'
print -ru2 -- ' If you need help, open an issue and attach this whole error message to it:'
print -ru2 -- ''
print -Pru2 -- ' %Uhttps://github.com/romkatv/gitstatus/issues/new%u'
else
print -ru2 -- ''
local home=~
local zshrc=${${${(q)${ZDOTDIR:-~}}/#${(q)home}/'~'}//\%/%%}/.zshrc
print -Pru2 -- " Add the following parameter to %U$zshrc%u for extra diagnostics on error:"
print -ru2 -- ''
print -Pru2 -- ' %BGITSTATUS_LOG_LEVEL=DEBUG%b'
print -ru2 -- ''
print -ru2 -- ' Restart Zsh to retry gitstatus initialization:'
print -ru2 -- ''
print -Pru2 -- ' %F{green}%Uexec%u zsh%f'
fi
}
local -i err=$?
(( stderr_fd )) && exec 2>&$stderr_fd
(( err == 0 )) && return
gitstatus_stop $name
setopt prompt_percent no_prompt_subst no_prompt_bang
print -Pru2 -- '[%F{red}ERROR%f]: gitstatus failed to initialize.'
print -ru2 -- ''
print -ru2 -- ' Your Git prompt may disappear or become slow.'
if [[ -s $xtrace ]]; then
print -ru2 -- ''
print -ru2 -- " The content of ${(q-)xtrace} (gitstatus_start xtrace):"
print -Pru2 -- '%F{yellow}'
>&2 awk '{print " " $0}' <$xtrace
print -Pru2 -- "%F{red} ^ this command failed ($err)%f"
fi
if [[ -s $daemon_log ]]; then
print -ru2 -- ''
print -ru2 -- " The content of ${(q-)daemon_log} (gitstatus daemon log):"
print -Pru2 -- '%F{yellow}'
>&2 awk '{print " " $0}' <$daemon_log
print -Pnru2 -- '%f'
fi
if [[ $GITSTATUS_LOG_LEVEL == DEBUG ]]; then
print -ru2 -- ''
print -ru2 -- ' Your system information:'
print -Pru2 -- '%F{yellow}'
print -ru2 -- " zsh: $ZSH_VERSION"
print -ru2 -- " uname -a: $(uname -a)"
print -Pru2 -- '%f'
print -ru2 -- ' If you need help, open an issue and attach this whole error message to it:'
print -ru2 -- ''
print -Pru2 -- ' %F{green}https://github.com/romkatv/gitstatus/issues/new%f'
else
print -ru2 -- ''
print -ru2 -- ' Run the following command to retry with extra diagnostics:'
print -Pru2 -- '%F{green}'
local env="GITSTATUS_LOG_LEVEL=DEBUG"
if [[ -n $GITSTATUS_NUM_THREADS ]]; then
env+=" GITSTATUS_NUM_THREADS=${(q)GITSTATUS_NUM_THREADS}"
fi
if [[ -n $GITSTATUS_DAEMON ]]; then
env+=" GITSTATUS_DAEMON=${(q)GITSTATUS_DAEMON}"
fi
print -nru2 -- " ${env} gitstatus_start ${(@q-)*}"
print -Pru2 -- '%f'
print -ru2 -- ''
local zshrc=${(D)ZDOTDIR:-~}/.zshrc
print -ru2 -- " If this command produces no output, add the following parameter to $zshrc:"
print -ru2 -- ''
print -Pru2 -- '%F{green} GITSTATUS_LOG_LEVEL=DEBUG%f'
print -ru2 -- ''
print -ru2 -- ' With this parameter gitstatus will print additional information on error.'
fi
return err
}
# Stops gitstatusd if it's running.
#
# Usage: gitstatus_stop NAME.
function gitstatus_stop() {
function gitstatus_stop"${1:-}"() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
local fsuf=${${(%):-%N}#gitstatus_stop}
if (( ARGC != 1 )); then
print -ru2 -- "gitstatus_stop: exactly one positional argument is required"
return 1
@@ -753,8 +838,8 @@ function gitstatus_stop() {
local daemon_pid=${(P)daemon_pid_var}
local file_prefix=${(P)file_prefix_var}
local cleanup=_gitstatus_cleanup_$name
local process=_gitstatus_process_response_$name
local cleanup=_gitstatus_cleanup_$name-$fsuf
local process=_gitstatus_process_response_$name-$fsuf
if (( $+functions[$cleanup] )); then
add-zsh-hook -d zshexit $cleanup
@@ -776,16 +861,18 @@ function gitstatus_stop() {
unset $inflight_var $file_prefix_var $dirty_max_index_size_var
unset VCS_STATUS_RESULT
_gitstatus_clear
_gitstatus_clear$fsuf
}
# Usage: gitstatus_check NAME.
#
# Returns 0 if and only if `gitstatus_start NAME` has succeeded previously.
# If it returns non-zero, gitstatus_query NAME is guaranteed to return non-zero.
function gitstatus_check() {
function gitstatus_check"${1:-}"() {
emulate -L zsh -o no_aliases -o extended_glob -o typeset_silent
local fsuf=${${(%):-%N}#gitstatus_check}
if (( ARGC != 1 )); then
print -ru2 -- "gitstatus_check: exactly one positional argument is required"
return 1

View File

@@ -0,0 +1,104 @@
# Simple Bash prompt with Git status.
# Source gitstatus.plugin.sh from $GITSTATUS_DIR or from the same directory
# in which the current script resides if the variable isn't set.
if [[ -n "${GITSTATUS_DIR:-}" ]]; then
source "$GITSTATUS_DIR" || return
elif [[ "${BASH_SOURCE[0]}" == */* ]]; then
source "${BASH_SOURCE[0]%/*}/gitstatus.plugin.sh" || return
else
source gitstatus.plugin.sh || return
fi
# Sets GITSTATUS_PROMPT to reflect the state of the current git repository.
# The value is empty if not in a git repository. Forwards all arguments to
# gitstatus_query.
#
# Example value of GITSTATUS_PROMPT: master ⇣42⇡42 ⇠42⇢42 *42 merge ~42 +42 !42 ?42
#
# master current branch
# ⇣42 local branch is 42 commits behind the remote
# ⇡42 local branch is 42 commits ahead of the remote
# ⇠42 local branch is 42 commits behind the push remote
# ⇢42 local branch is 42 commits ahead of the push remote
# *42 42 stashes
# merge merge in progress
# ~42 42 merge conflicts
# +42 42 staged changes
# !42 42 unstaged changes
# ?42 42 untracked files
function gitstatus_prompt_update() {
GITSTATUS_PROMPT=""
gitstatus_query "$@" || return 1 # error
[[ "$VCS_STATUS_RESULT" == ok-sync ]] || return 0 # not a git repo
local reset=$'\001\e[0m\002' # no color
local clean=$'\001\e[38;5;076m\002' # green foreground
local untracked=$'\001\e[38;5;014m\002' # teal foreground
local modified=$'\001\e[38;5;011m\002' # yellow foreground
local conflicted=$'\001\e[38;5;196m\002' # red foreground
local p
local where # branch name, tag or commit
if [[ -n "$VCS_STATUS_LOCAL_BRANCH" ]]; then
where="$VCS_STATUS_LOCAL_BRANCH"
elif [[ -n "$VCS_STATUS_TAG" ]]; then
p+="${reset}#"
where="$VCS_STATUS_TAG"
else
p+="${reset}@"
where="${VCS_STATUS_COMMIT:0:8}"
fi
(( ${#where} > 32 )) && where="${where:0:12}${where: -12}" # truncate long branch names and tags
p+="${clean}${where}"
# ⇣42 if behind the remote.
(( VCS_STATUS_COMMITS_BEHIND )) && p+=" ${clean}${VCS_STATUS_COMMITS_BEHIND}"
# ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42.
(( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && p+=" "
(( VCS_STATUS_COMMITS_AHEAD )) && p+="${clean}${VCS_STATUS_COMMITS_AHEAD}"
# ⇠42 if behind the push remote.
(( VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" ${clean}${VCS_STATUS_PUSH_COMMITS_BEHIND}"
(( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" "
# ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42.
(( VCS_STATUS_PUSH_COMMITS_AHEAD )) && p+="${clean}${VCS_STATUS_PUSH_COMMITS_AHEAD}"
# *42 if have stashes.
(( VCS_STATUS_STASHES )) && p+=" ${clean}*${VCS_STATUS_STASHES}"
# 'merge' if the repo is in an unusual state.
[[ -n "$VCS_STATUS_ACTION" ]] && p+=" ${conflicted}${VCS_STATUS_ACTION}"
# ~42 if have merge conflicts.
(( VCS_STATUS_NUM_CONFLICTED )) && p+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
# +42 if have staged changes.
(( VCS_STATUS_NUM_STAGED )) && p+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
# !42 if have unstaged changes.
(( VCS_STATUS_NUM_UNSTAGED )) && p+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
# ?42 if have untracked files. It's really a question mark, your font isn't broken.
(( VCS_STATUS_NUM_UNTRACKED )) && p+=" ${untracked}?${VCS_STATUS_NUM_UNTRACKED}"
GITSTATUS_PROMPT="${p}${reset}"
}
# Start gitstatusd in the background.
gitstatus_stop && gitstatus_start -s -1 -u -1 -c -1 -d -1
# On every prompt, fetch git status and set GITSTATUS_PROMPT.
PROMPT_COMMAND=gitstatus_prompt_update
PROMPT_DIRTRIM=3
# Enable promptvars so that ${GITSTATUS_PROMPT} in PS1 is expanded.
shopt -s promptvars
# Customize prompt. Put $GITSTATUS_PROMPT in it reflect git status.
#
# Example:
#
# user@host ~/projects/skynet master ⇡42
# $ █
PS1='\[\033[01;32m\]\u@\h\[\033[00m\] ' # green user@host
PS1+='\[\033[01;34m\]\w\[\033[00m\]' # blue current working directory
PS1+='${GITSTATUS_PROMPT:+ $GITSTATUS_PROMPT}' # git status (requires promptvars option)
PS1+='\n\[\033[01;$((31+!$?))m\]\$\[\033[00m\] ' # green/red (success/error) $/# (normal/root)
PS1+='\[\e]0;\u@\h: \w\a\]' # terminal title: user@host: dir

View File

@@ -0,0 +1,111 @@
# Simple Zsh prompt with Git status.
# Source gitstatus.plugin.zsh from $GITSTATUS_DIR or from the same directory
# in which the current script resides if the variable isn't set.
source "${GITSTATUS_DIR:-${${(%):-%x}:h}}/gitstatus.plugin.zsh" || return
# Sets GITSTATUS_PROMPT to reflect the state of the current git repository. Empty if not
# in a git repository. In addition, sets GITSTATUS_PROMPT_LEN to the number of columns
# $GITSTATUS_PROMPT will occupy when printed.
#
# Example:
#
# GITSTATUS_PROMPT='master ⇣42⇡42 ⇠42⇢42 *42 merge ~42 +42 !42 ?42'
# GITSTATUS_PROMPT_LEN=39
#
# master current branch
# ⇣42 local branch is 42 commits behind the remote
# ⇡42 local branch is 42 commits ahead of the remote
# ⇠42 local branch is 42 commits behind the push remote
# ⇢42 local branch is 42 commits ahead of the push remote
# *42 42 stashes
# merge merge in progress
# ~42 42 merge conflicts
# +42 42 staged changes
# !42 42 unstaged changes
# ?42 42 untracked files
function gitstatus_prompt_update() {
emulate -L zsh
typeset -g GITSTATUS_PROMPT=''
typeset -gi GITSTATUS_PROMPT_LEN=0
# Call gitstatus_query synchronously. Note that gitstatus_query can also be called
# asynchronously; see documentation in gitstatus.plugin.zsh.
gitstatus_query 'MY' || return 1 # error
[[ $VCS_STATUS_RESULT == 'ok-sync' ]] || return 0 # not a git repo
local clean='%76F' # green foreground
local modified='%178F' # yellow foreground
local untracked='%39F' # blue foreground
local conflicted='%196F' # red foreground
local p
local where # branch name, tag or commit
if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then
where=$VCS_STATUS_LOCAL_BRANCH
elif [[ -n $VCS_STATUS_TAG ]]; then
p+='%f#'
where=$VCS_STATUS_TAG
else
p+='%f@'
where=${VCS_STATUS_COMMIT[1,8]}
fi
(( $#where > 32 )) && where[13,-13]="…" # truncate long branch names and tags
p+="${clean}${where//\%/%%}" # escape %
# ⇣42 if behind the remote.
(( VCS_STATUS_COMMITS_BEHIND )) && p+=" ${clean}${VCS_STATUS_COMMITS_BEHIND}"
# ⇡42 if ahead of the remote; no leading space if also behind the remote: ⇣42⇡42.
(( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && p+=" "
(( VCS_STATUS_COMMITS_AHEAD )) && p+="${clean}${VCS_STATUS_COMMITS_AHEAD}"
# ⇠42 if behind the push remote.
(( VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" ${clean}${VCS_STATUS_PUSH_COMMITS_BEHIND}"
(( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && p+=" "
# ⇢42 if ahead of the push remote; no leading space if also behind: ⇠42⇢42.
(( VCS_STATUS_PUSH_COMMITS_AHEAD )) && p+="${clean}${VCS_STATUS_PUSH_COMMITS_AHEAD}"
# *42 if have stashes.
(( VCS_STATUS_STASHES )) && p+=" ${clean}*${VCS_STATUS_STASHES}"
# 'merge' if the repo is in an unusual state.
[[ -n $VCS_STATUS_ACTION ]] && p+=" ${conflicted}${VCS_STATUS_ACTION}"
# ~42 if have merge conflicts.
(( VCS_STATUS_NUM_CONFLICTED )) && p+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}"
# +42 if have staged changes.
(( VCS_STATUS_NUM_STAGED )) && p+=" ${modified}+${VCS_STATUS_NUM_STAGED}"
# !42 if have unstaged changes.
(( VCS_STATUS_NUM_UNSTAGED )) && p+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}"
# ?42 if have untracked files. It's really a question mark, your font isn't broken.
(( VCS_STATUS_NUM_UNTRACKED )) && p+=" ${untracked}?${VCS_STATUS_NUM_UNTRACKED}"
GITSTATUS_PROMPT="${p}%f"
# The length of GITSTATUS_PROMPT after removing %f and %F.
GITSTATUS_PROMPT_LEN="${(m)#${${GITSTATUS_PROMPT//\%\%/x}//\%(f|<->F)}}"
}
# Start gitstatusd instance with name "MY". The same name is passed to
# gitstatus_query in gitstatus_prompt_update. The flags with -1 as values
# enable staged, unstaged, conflicted and untracked counters.
gitstatus_stop 'MY' && gitstatus_start -s -1 -u -1 -c -1 -d -1 'MY'
# On every prompt, fetch git status and set GITSTATUS_PROMPT.
autoload -Uz add-zsh-hook
add-zsh-hook precmd gitstatus_prompt_update
# Enable/disable the right prompt options.
setopt no_prompt_bang prompt_percent prompt_subst
# Customize prompt. Put $GITSTATUS_PROMPT in it to reflect git status.
#
# Example:
#
# user@host ~/projects/skynet master ⇡42
# % █
#
# The current directory gets truncated from the left if the whole prompt doesn't fit on the line.
PROMPT='%70F%n@%m%f ' # green user@host
PROMPT+='%39F%$((-GITSTATUS_PROMPT_LEN-1))<…<%~%<<%f' # blue current working directory
PROMPT+='${GITSTATUS_PROMPT:+ $GITSTATUS_PROMPT}' # git status
PROMPT+=$'\n' # new line
PROMPT+='%F{%(?.76.196)}%#%f ' # %/# (normal/root); green/red (ok/error)

471
gitstatus/install Executable file
View File

@@ -0,0 +1,471 @@
#!/bin/sh
#
# This script does not have a stable API.
_gitstatus_install_daemon_found() {
local installed="$1"
shift
[ $# = 0 ] || "$@" "$daemon" "$version" "$installed"
}
_gitstatus_install_main() {
if [ -n "${ZSH_VERSION:-}" ]; then
emulate -L sh -o no_unset
else
set -u
fi
local argv1="$1"
shift
local no_check= no_install= uname_s= uname_m= gitstatus_dir= dl_status= e=
local opt= OPTARG= OPTIND=1
while getopts ':s:m:d:p:e:fnh' opt "$@"; do
case "$opt" in
h)
command cat <<\END
Usage: install [-s KERNEL] [-m ARCH] [-d DIR] [-p CMD] [-e ERRFD] [-f|-n] [-- CMD [ARG]...]
If positional arguments are specified, call this on success:
CMD [ARG]... DAEMON VERSION INSTALLED
DAEMON is path to gitstatusd. VERSION is a glob pattern for the
version this daemon should support; it's supposed to be passed as
-G to gitstatusd. INSTALLED is 1 if gitstatusd has just been
downloaded and 0 otherwise.
Options:
-s KERNEL use this instead of lowercase `uname -s`
-m ARCH use this instead of lowercase `uname -m`
-d DIR use this instead of `dirname "$0"`
-p CMD eval this every second while downloading gitstatusd
-e ERRFD write error messages to this file descriptor
-f download gitstatusd even if there is one locally
-n do not download gitstatusd (fail instead)
END
return
;;
n)
if [ -n "$no_install" ]; then
>&2 echo "[gitstatus] error: duplicate option: -$opt"
return 1
fi
no_install=1
;;
f)
if [ -n "$no_check" ]; then
>&2 echo "[gitstatus] error: duplicate option: -$opt"
return 1
fi
no_check=1
;;
d)
if [ -n "$gitstatus_dir" ]; then
>&2 echo "[gitstatus] error: duplicate option: -$opt"
return 1
fi
if [ -z "$OPTARG" ]; then
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
return 1
fi
gitstatus_dir="$OPTARG"
;;
p)
if [ -n "$dl_status" ]; then
>&2 echo "[gitstatus] error: duplicate option: -$opt"
return 1
fi
if [ -z "$OPTARG" ]; then
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
return 1
fi
dl_status="$OPTARG"
;;
e)
if [ -n "$e" ]; then
>&2 echo "[gitstatus] error: duplicate option: -$opt"
return 1
fi
if [ -z "$OPTARG" ]; then
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
return 1
fi
e="$OPTARG"
;;
m)
if [ -n "$uname_m" ]; then
>&2 echo "[gitstatus] error: duplicate option: -$opt"
return 1
fi
if [ -z "$OPTARG" ]; then
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
return 1
fi
uname_m="$OPTARG"
;;
s)
if [ -n "$uname_s" ]; then
>&2 echo "[gitstatus] error: duplicate option: -$opt"
return 1
fi
if [ -z "$OPTARG" ]; then
>&2 echo "[error] incorrect value of -$opt: $OPTARG"
return 1
fi
uname_s="$OPTARG"
;;
\?) >&2 echo "[gitstatus] error: invalid option: -$OPTARG" ; return 1;;
:) >&2 echo "[gitstatus] error: missing required argument: -$OPTARG"; return 1;;
*) >&2 echo "[gitstatus] internal error: unhandled option: -$opt" ; return 1;;
esac
done
shift "$((OPTIND - 1))"
: "${e:=2}"
: "${gitstatus_dir:=$argv1}"
if [ -n "$no_check" -a -n "$no_install" ]; then
>&2 echo "[gitstatus] error: incompatible options: -f, -n"
return 1
fi
if [ -z "$uname_s" ]; then
uname_s="$(command uname -s)" || return
uname_s="$(printf '%s' "$uname_s" | command tr '[A-Z]' '[a-z]')" || return
fi
if [ -z "$uname_m" ]; then
uname_m="$(command uname -m)" || return
uname_m="$(printf '%s' "$uname_m" | command tr '[A-Z]' '[a-z]')" || return
fi
local daemon="${GITSTATUS_DAEMON:-}"
local cache_dir="${GITSTATUS_CACHE_DIR:-${XDG_CACHE_HOME:-$HOME/.cache}/gitstatus}"
if [ -z "$no_check" ]; then
if [ -n "${daemon##/*}" ]; then
>&2 echo "[gitstatus] error: GITSTATUS_DAEMON is not absolute path: $daemon"
return 1
fi
if [ -z "$daemon" -a -e "$gitstatus_dir"/usrbin/gitstatusd ]; then
daemon="$gitstatus_dir"/usrbin/gitstatusd
fi
if [ -n "$daemon" ]; then
local gitstatus_version= libgit2_version=
if ! . "$gitstatus_dir"/build.info; then
>&2 echo "[gitstatus] internal error: failed to source build.info"
return 1
fi
if [ -z "$gitstatus_version" ]; then
>&2 echo "[gitstatus] internal error: empty gitstatus_version in build.info"
return 1
fi
local version="$gitstatus_version"
_gitstatus_install_daemon_found 0 "$@"
return
fi
fi
while IFS= read -r line; do
line="${line###*}"
[ -n "$line" ] || continue
local uname_s_glob= uname_m_glob= file= version= sha256=
eval "$line" || return
if [ -z "$uname_s_glob" -o \
-z "$uname_m_glob" -o \
-z "$file" -o \
-z "$version" -o \
-z "$sha256" ]; then
>&2 echo "[gitstatus] internal error: invalid install.info line: $line"
return 1
fi
case "$uname_s" in
$uname_s_glob) ;;
*) continue;;
esac
case "$uname_m" in
$uname_m_glob) ;;
*) continue;;
esac
# Found a match. The while loop will terminate during this iteration.
if [ -z "$no_check" ]; then
# Check if a suitable gitstatusd already exists.
local daemon="$gitstatus_dir"/usrbin/"$file"
if [ ! -e "$daemon" ]; then
daemon="$cache_dir"/"$file"
[ -e "$daemon" ] || daemon=
fi
if [ -n "$daemon" ]; then
_gitstatus_install_daemon_found 0 "$@"
return
fi
fi
# No suitable gitstatusd exists. Need to download.
if [ -n "$no_install" ]; then
>&2 echo "[gitstatus] error: no gitstatusd found and installation is disabled"
return 1
fi
local daemon="$cache_dir"/"$file"
if [ -n "${cache_dir##/*}" ]; then
>&2 echo "[gitstatus] error: GITSTATUS_CACHE_DIR is not absolute: $cache_dir"
return 1
fi
if [ ! -d "$cache_dir" ] && ! mkdir -p -- "$cache_dir" || [ ! -w "$cache_dir" ]; then
local dir="$cache_dir"
while true; do
if [ -e "$dir" ]; then
if [ ! -d "$dir" ]; then
>&"$e" printf 'Not a directory: \033[4;31m%s\033[0m\n' "$dir"
>&"$e" printf '\n'
>&"$e" printf 'Delete it, then restart your shell.\n'
elif [ ! -w "$dir" ]; then
>&"$e" printf 'Directory is not writable: \033[4;31m%s\033[0m\n' "$dir"
>&"$e" printf '\n'
>&"$e" printf 'Make it writable, then restart your shell.\n'
fi
break
fi
if [ "$dir" = / ] || [ "$dir" = . ]; then
break
fi
dir="$(dirname -- "$dir")"
done
return 1
fi
local tmpdir
if ! command -v mktemp >/dev/null 2>&1 ||
! tmpdir="$(command mktemp -d "${TMPDIR:-/tmp}"/gitstatus-install.XXXXXXXXXX)"; then
tmpdir="${TMPDIR:-/tmp}/gitstatus-install.tmp.$$"
if ! mkdir -p -- "$tmpdir"; then
local dir="${TMPDIR:-/tmp}"
if [ -z "${TMPDIR:-}" ]; then
local label='directory'
else
local label='directory (\033[1mTMPDIR\033[m)'
fi
if [ ! -e "$dir" ]; then
>&"$e" printf 'Temporary '"$label"' does not exist: \033[4;31m%s\033[0m\n' "$dir"
>&"$e" printf '\n'
>&"$e" printf 'Create it, then restart your shell.\n'
elif [ ! -d "$dir" ]; then
>&"$e" printf 'Not a '"$label"': \033[4;31m%s\033[0m\n' "$dir"
>&"$e" printf '\n'
>&"$e" printf 'Make it a directory, then restart your shell.\n'
elif [ ! -w "$dir" ]; then
>&"$e" printf 'Temporary '"$label"' is not writable: \033[4;31m%s\033[0m\n' "$dir"
>&"$e" printf '\n'
>&"$e" printf 'Make it writable, then restart your shell.\n'
fi
return 1
fi
fi
if ! command -v curl >/dev/null 2>&1 && ! command -v wget >/dev/null 2>&1; then
>&"$e" printf 'Please install \033[32mcurl\033[0m or \033[32mwget\033[0m, then restart your shell.\n'
return 1
fi
(
run_cmd() {
command -v "$1" >/dev/null 2>/dev/null || return 127
local trapped= pid die ret
trap 'trapped=1' $sig
# The only reason for suppressing stderr is that `curl -f` cannot be silenced:
# `-s` doesn't work despite what the docs say.
command "$@" 2>/dev/null &
ret="$?"
if [ "$ret" = 0 ]; then
pid="$!"
die="trap - $sig; kill -- $pid 2>/dev/null; wait -- $pid 2>/dev/null; exit 1"
trap "$die" $sig
[ -z "$trapped" ] || eval "$die"
wait -- "$pid" 2>/dev/null
ret="$?"
fi
trap - $sig
[ -z "$trapped" ] || exit
return "$ret"
}
check_sha256() {
local data_file="$tmpdir"/"$1".tar.gz
local hash_file="$tmpdir"/"$1".tar.gz.sha256
local hash=
if command -v shasum >/dev/null 2>/dev/null; then
if run_cmd shasum -b -a 256 -- "$data_file" >"$hash_file"; then
IFS= read -r hash <"$hash_file" || hash=
hash="${hash%% *}"
fi
elif command -v sha256sum >/dev/null 2>/dev/null; then
if run_cmd sha256sum -b -- "$data_file" >"$hash_file"; then
IFS= read -r hash <"$hash_file" || hash=
hash="${hash%% *}"
fi
elif command -v sha256 >/dev/null 2>/dev/null; then
if run_cmd sha256 -- "$data_file" </dev/null >"$hash_file"; then
IFS= read -r hash <"$hash_file" || hash=
# Ignore sha256 output if it's from hashalot. It's incompatible.
if [ ${#hash} -lt 64 ]; then
hash=
else
hash="${hash##* }"
fi
fi
fi
[ "$1" = 1 -a -z "$hash" -o "$hash" = "$sha256" ]
}
local url1="https://github.com/romkatv/gitstatus/releases/download/$version/$file.tar.gz"
local url2="https://gitee.com/romkatv/gitstatus/raw/release-$version/release/$file.tar.gz"
local sig='INT QUIT TERM ILL PIPE'
fetch() {
if [ "$1" != 1 ] && command -v sleep >/dev/null 2>/dev/null; then
if ! run_cmd sleep "$1"; then
echo -n >"$tmpdir"/"$1".status
return 1
fi
fi
local cmd part url ret
for cmd in 'curl -q -kfsSL' 'wget --no-config -qO-' 'wget -qO-' 'curl -kfsSL'; do
part=0
while true; do
if [ "$part" = 2 ]; then
ret=1
break
elif [ "$part" = 0 ]; then
url="$2"
else
url="$2"."$part"
fi
run_cmd $cmd -- "$url" >>"$tmpdir"/"$1".tar.gz
ret="$?"
[ "$ret" = 0 ] || break
check_sha256 "$1" && break
part=$((part+1))
done
[ "$ret" = 0 ] && break
run_cmd rm -f -- "$tmpdir"/"$1".tar.gz && continue
ret="$?"
break
done
echo -n >"$tmpdir"/"$1".status
return "$ret"
}
local trapped=
trap 'trapped=1' $sig
fetch 1 "$url1" &
local pid1="$!"
fetch 2 "$url2" &
local pid2="$!"
local die="trap - $sig; kill -- $pid1 $pid2 2>/dev/null; wait -- $pid1 $pid2 2>/dev/null; exit 1"
trap "$die" $sig
[ -z "$trapped" ] || eval "$die"
local n=
while true; do
[ -z "$dl_status" ] || eval "$dl_status" || eval "$die"
if command -v sleep >/dev/null 2>/dev/null; then
command sleep 1
elif command -v true >/dev/null 2>/dev/null; then
command true
fi
if [ -n "$pid1" -a -e "$tmpdir"/1.status ]; then
wait -- "$pid1" 2>/dev/null
local ret="$?"
pid1=
if [ "$ret" = 0 ]; then
if [ -n "$pid2" ]; then
kill -- "$pid2" 2>/dev/null
wait -- "$pid2" 2>/dev/null
fi
n=1
break
elif [ -z "$pid2" ]; then
break
else
die="trap - $sig; kill -- $pid2 2>/dev/null; wait -- $pid2 2>/dev/null; exit 1"
trap "$die" $sig
fi
elif [ -n "$pid2" -a -e "$tmpdir"/2.status ]; then
wait -- "$pid2" 2>/dev/null
local ret="$?"
pid2=
if [ "$ret" = 0 ]; then
if [ -n "$pid1" ]; then
kill -- "$pid1" 2>/dev/null
wait -- "$pid1" 2>/dev/null
fi
n=2
break
elif [ -z "$pid1" ]; then
break
else
die="trap - $sig; kill -- $pid1 2>/dev/null; wait -- $pid1 2>/dev/null; exit 1"
trap "$die" $sig
fi
fi
done
trap - $sig
if [ -z "$n" ]; then
>&"$e" printf 'Failed to download \033[32m%s\033[0m from any mirror:\n' "$file"
>&"$e" printf '\n'
>&"$e" printf ' 1. \033[4m%s\033[0m\n' "$url1"
>&"$e" printf ' 2. \033[4m%s\033[0m\n' "$url2"
>&"$e" printf '\n'
>&"$e" printf 'Check your internet connection, then restart your shell.\n'
exit 1
fi
command tar -C "$tmpdir" -xzf "$tmpdir"/"$n".tar.gz || exit
local tmpfile
if ! command -v mktemp >/dev/null 2>&1 ||
! tmpfile="$(command mktemp "$cache_dir"/gitstatusd.XXXXXXXXXX)"; then
tmpfile="$cache_dir"/gitstatusd.tmp.$$
fi
command mv -f -- "$tmpdir"/"$file" "$tmpfile" || exit
command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit
command rm -f -- "$cache_dir"/"$file"
command mv -f -- "$tmpfile" "$cache_dir"/"$file" && exit
command rm -f -- "$tmpfile"
exit 1
)
local ret="$?"
command rm -rf -- "$tmpdir"
[ "$ret" = 0 ] || return
_gitstatus_install_daemon_found 1 "$@"
return
done <"$gitstatus_dir"/install.info
>&"$e" printf 'There is no prebuilt \033[32mgitstatusd\033[0m for \033[1m%s\033[0m.\n' "$uname_s $uname_m"
>&"$e" printf '\n'
>&"$e" printf 'See: \033[4mhttps://github.com/romkatv/gitstatus#compiling\033[0m\n'
return 1
}
if [ -z "${0##*/*}" ]; then
_gitstatus_install_main "${0%/*}" "$@"
else
_gitstatus_install_main . "$@"
fi

34
gitstatus/install.info Normal file
View File

@@ -0,0 +1,34 @@
# 2
#
# This file is used by ./install and indirectly by shell bindings.
#
# The first line is read by powerlevel10k instant prompt. It must
# be updated whenever the content of this file changes. The actual
# value doesn't matter as long as it's unique. Consecutive integers
# work fine.
# Official gitstatusd binaries.
uname_s_glob="cygwin_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="fe132c412c460c2889d731012d280207fe2b4a3c94d077fb4f1c06ed5d319a42";
uname_s_glob="cygwin_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="c84cade0d6b86e04c27a6055f45851f6b46d6b88ba58772f7ca8ef4d295c800f";
uname_s_glob="darwin"; uname_m_glob="arm64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="ad973948cca4bdcf83b7fcdda70c489a404488ea7304712721f1100b73ec7cbe";
uname_s_glob="darwin"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="b13455d56cf7b6f07efb7da088057bbc1212847c88b59493918d6f9c0c157160";
uname_s_glob="freebsd"; uname_m_glob="amd64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.3"; sha256="73b5622ec9737e93f3fafe117b47ce8de33037be3e2bff283f36668f5852668a";
uname_s_glob="linux"; uname_m_glob="aarch64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="89b87181b2005527a558cdbc32b12b0f15a1a12bb69865ec216ca5a0266a6c4f";
uname_s_glob="linux"; uname_m_glob="armv6l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="4bf5a0d0a082f544a48536ad3675930d5d2cc6a8cf906710045e0788f51192b3";
uname_s_glob="linux"; uname_m_glob="armv7l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="2b9deb29f86c8209114b71b94fc2e1ed936a1658808a1bee46f4a82fd6a1f8cc";
uname_s_glob="linux"; uname_m_glob="armv8l"; file="gitstatusd-${uname_s}-aarch64"; version="v1.5.1"; sha256="89b87181b2005527a558cdbc32b12b0f15a1a12bb69865ec216ca5a0266a6c4f";
uname_s_glob="linux"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="4998bf7889f625df71f1da5757915b678e04039cc8cba00ae10950352c7329f9";
uname_s_glob="linux"; uname_m_glob="ppc64le"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="e5336dc8e23406c649bafeea83ff17df1726b05ee490f67bae549e55a9a7a7c4";
uname_s_glob="linux"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="0e8bbc46c17f5cd6e0db98b74c48f4b68f464f98550c8254f6cfcfd936ad1fcf";
uname_s_glob="msys_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
uname_s_glob="msys_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
# Fallbacks to official gitstatusd binaries.
uname_s_glob="cygwin_nt-*"; uname_m_glob="i686"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="fe132c412c460c2889d731012d280207fe2b4a3c94d077fb4f1c06ed5d319a42";
uname_s_glob="cygwin_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="c84cade0d6b86e04c27a6055f45851f6b46d6b88ba58772f7ca8ef4d295c800f";
uname_s_glob="mingw32_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
uname_s_glob="mingw32_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
uname_s_glob="mingw64_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
uname_s_glob="mingw64_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";
uname_s_glob="msys_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="7f9b849fc52e7a95b9b933e25121ad5ae990a1871aad6616922ad7bcf1eebf20";
uname_s_glob="msys_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.5.1"; sha256="5d3c626b5ee564dbc13ddba89752dc58b0efe925b26dbd8b2304849d9ba01732";

368
gitstatus/mbuild Executable file
View File

@@ -0,0 +1,368 @@
#!/usr/bin/env zsh
#
# This script does not have a stable API.
#
# Usage: mbuild [-b git-ref] [kernel-arch]...
#
# Builds a bunch of gitstatusd-* binaries. Without arguments builds binaries
# for all platforms. git-ref defaults to master.
#
# Before using this script you need to set up build servers and list them
# in ~/.ssh/config. There should be a Host entry for every value of `assets`
# association defined below. VMs and cloud instances work as well as physical
# machines, including localhost. As long as the machine has been set up as
# described below and you can SSH to it without password, it should work.
#
# ===[ Build Server Setup ]===
#
# Linux
#
# - Install docker.
# $ apt install docker.io # adjust appropriately if there is no `apt`
# $ usermod -aG docker $USER # not needed if going to build as root
# - Install git.
# $ apt install git # adjust appropriately if there is no `apt`
#
# macOS
#
# - Install compiler tools:
# $ xcode-select --install
# - Install homebrew: https://brew.sh/.
# $ bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install.sh)"
#
# FreeBSD
#
# - Install git.
# $ pkg install git
#
# Windows
#
# - Disable Windows Defender (optional).
# ps> Set-MpPreference -DisableRealtimeMonitoring $true
# - Install 64-bit and 32-bit msys2: https://www.msys2.org/wiki/MSYS2-installation/.
# - Open each of them after installation, type `pacman -Syu --noconfirm` and close the window.
# - Then run in powershell while having no msys2 or cygwin windows open:
# ps> C:\msys32\autorebase.bat
# ps> C:\msys64\autorebase.bat
# - Install 64-bit and 32-bit cygwin: https://cygwin.com/install.html.
# - Choose to install 32-bit to c:/cygwin32 instead of the default c:/cygwin.
# - Select these packages: binutils, cmake, gcc-core, gcc-g++, git, make, perl, wget.
#
# IMPORTANT: Install msys2 and cygwin one at a time.
#
# IMPORTANT: msys2 builder can reboot the build machine.
#
# Option 1: OpenSSH for Windows
#
# - Install OpenSSH: https://docs.microsoft.com/en-us/windows-server/administration/openssh/openssh_install_firstuse.
# ps> Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
# ps> Start-Service sshd
# ps> Set-Service -Name sshd -StartupType 'Automatic'
# - Enable publickey authentication: https://stackoverflow.com/a/50502015/1095235.
# ps> cd $env:USERPROFILE
# ps> mkdir .ssh
# ps> notepad.exe .ssh/authorized_keys
# - Paste your public key, save, close.
# ps> icacls .ssh/authorized_keys /inheritance:r
# ps> notepad.exe C:\ProgramData\ssh\sshd_config
# - Comment out these two lines, save, close:
# # Match Group administrators
# # AuthorizedKeysFile __PROGRAMDATA__/ssh/administrators_authorized_keys
# ps> Restart-Service sshd
#
# Option 2: OpenSSH from WSL
#
# - Install WSL.
# - Install Ubuntu.
# - Install sshd.
# $ apt install openssh-server
# $ dpkg-reconfigure openssh-server
# $ cat >/etc/ssh/sshd_config <<\END
# ClientAliveInterval 60
# AcceptEnv TERM LANG LC_*
# PermitRootLogin no
# AllowTcpForwarding no
# AllowAgentForwarding no
# AllowStreamLocalForwarding no
# AuthenticationMethods publickey
# END
# service ssh --full-restart
# - Add your public ssh key to ~/.ssh/authorized_keys.
# - Make `sshd` start when Windows boots.
'emulate' '-L' 'zsh' '-o' 'no_aliases' '-o' 'err_return'
setopt no_unset extended_glob pipe_fail prompt_percent typeset_silent \
no_prompt_subst no_prompt_bang pushd_silent warn_create_global
autoload -Uz is-at-least
if ! is-at-least 5.1 || [[ $ZSH_VERSION == 5.4.* ]]; then
print -ru2 -- "[error] unsupported zsh version: $ZSH_VERSION"
return 1
fi
zmodload zsh/system
local -r git_url='https://github.com/romkatv/gitstatus.git'
local -rA assets=(
# target kernel-arch hostname of the build machine
cygwin_nt-10.0-i686 build-windows-x86_64
cygwin_nt-10.0-x86_64 build-windows-x86_64
msys_nt-10.0-i686 build-windows-x86_64
msys_nt-10.0-x86_64 build-windows-x86_64
darwin-arm64 build-macos-arm64
darwin-x86_64 build-macos-x86_64
freebsd-amd64 build-freebsd-amd64
linux-aarch64 build-linux-aarch64
linux-armv6l build-linux-armv7l
linux-armv7l build-linux-armv7l
linux-i686 build-linux-x86_64
linux-ppc64le build-linux-ppc64le
linux-x86_64 build-linux-x86_64
)
local -rA protocol=(
'cygwin_nt-10.0-*' windows
'msys_nt-10.0-*' windows
'darwin-*' unix
'freebsd-*' unix
'linux-*' unix
)
local -r rootdir=${ZSH_SCRIPT:h}
local -r logs=$rootdir/logs
local -r locks=$rootdir/locks
local -r binaries=$rootdir/usrbin
function usage() {
print -r -- 'usage: mbuild [-b REF] [KERNEL-ARCH]...'
}
local OPTARG opt git_ref=master
local -i OPTIND
while getopts ":b:h" opt; do
case $opt in
h) usage; return 0;;
b) [[ -n $OPTARG ]]; git_ref=$OPTARG;;
\?) print -ru2 -- "mbuild: invalid option: -$OPTARG" ; return 1;;
:) print -ru2 -- "mbuild: missing required argument: -$OPTARG"; return 1;;
*) print -ru2 -- "mbuild: invalid option: -$opt" ; return 1;;
esac
done
shift $((OPTIND - 1))
(( $# )) || set -- ${(ko)assets}
set -- ${(u)@}
local platform
for platform; do
if (( ! $+assets[$platform] )); then
print -ru2 -- "mbuild: invalid platform: $platform"
return 1
fi
done
local build='
rm -rf gitstatus
git clone --recursive --shallow-submodules --depth=1 -b '$git_ref' '$git_url'
cd gitstatus
if command -v zsh >/dev/null 2>&1; then
sh=zsh
elif command -v dash >/dev/null 2>&1; then
sh=dash
elif command -v ash >/dev/null 2>&1; then
sh=ash
else
sh=sh
fi
$sh -x ./build -m '
function build-unix() {
local intro flags=(-sw)
case $2 in
linux-ppc64le) ;;
linux-*) flags+=(-d docker);;
darwin-arm64) intro='PATH="/opt/local/bin:$PATH"';;
darwin-*) intro='PATH="/usr/local/bin:$PATH"';;
esac
ssh $1 -- /bin/sh -uex <<<"
$intro
cd /tmp
$build ${2##*-} ${(j: :)${(@q)flags}}"
scp $1:/tmp/gitstatus/usrbin/gitstatusd $binaries/gitstatusd-$2
}
function build-windows() {
local shell=$(ssh $1 'echo $0')
if [[ $shell == '$0'* ]]; then
local c='c:'
else
local c='/mnt/c'
fi
local tmp env bin intro flags=(-w)
case $2 in
cygwin_nt-10.0-i686) bin='cygwin32/bin' ;|
cygwin_nt-10.0-x86_64) bin='cygwin64/bin' ;|
msys_nt-10.0-i686) bin='msys32/usr/bin';|
msys_nt-10.0-x86_64) bin='msys64/usr/bin';|
cygwin_nt-10.0-*)
tmp='/cygdrive/c/tmp'
;|
msys_nt-10.0-*)
tmp='/c/tmp'
env='MSYSTEM=MSYS'
# TODO: fix this (some errors about PGP keys).
# flags+=(-s)
# intro='pacman -S --needed --noconfirm git; '
intro+='PATH="$PATH:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl"'
while true; do
# TODO: run autorebase only when getting an error that can be fixed by autorebasing.
break
local out
out="$(ssh $1 cmd.exe "$c/${bin%%/*}/autorebase.bat" 2>&1)"
[[ $out == *"The following DLLs couldn't be rebased"* ]] || break
# Reboot to get rid of whatever is using those DLLs.
ssh $1 powershell.exe <<<'Restart-Computer -Force' || true
sleep 30
while ! ssh $1 <<<''; do sleep 5; done
done
() {
while true; do
# TODO: fix this (some errors about PGP keys).
break
local -i fd
exec {fd}< <(
ssh $1 $c/$bin/env.exe $env c:/$bin/bash.exe -l 2>&1 <<<"
pacman -Syu --noconfirm
exit")
{
local line
while true; do
IFS= read -u $fd -r line || return 0
if [[ $line == *"warning: terminate MSYS2"* ]]; then
# At this point the machine is hosed. A rogue process with a corrupted name
# is eating all CPU. The top SSH connection won't terminate on its own.
ssh $1 powershell.exe <<<'Restart-Computer -Force' || true
sleep 30
while ! ssh $1 <<<''; do sleep 5; done
break
fi
done
} always {
exec {fd}<&-
kill -- -$sysparams[procsubstpid] 2>/dev/null || true
}
done
} "$@"
;|
esac
ssh $1 $c/$bin/env.exe $env c:/$bin/bash.exe -l <<<"
set -uex
$intro
mkdir -p -- $tmp
cd -- $tmp
$build ${2##*-} ${(j: :)${(@q)flags}}
exit"
scp $1:$c/tmp/gitstatus/usrbin/gitstatusd $binaries/gitstatusd-$2
chmod +x $binaries/gitstatusd-$2
}
function build() (
setopt xtrace
local platform=$1
local machine=$assets[$platform]
print -n >>$locks/$machine
zsystem flock $locks/$machine
build-${protocol[(k)$platform]} $machine $platform
local tmp=gitstatusd-$platform.tmp.$$.tar.gz
( cd -q -- $binaries; tar --owner=0 --group=0 -I 'gzip -9' -cf $tmp gitstatusd-$platform )
mv -f -- $binaries/$tmp $binaries/gitstatusd-$platform.tar.gz
)
function mbuild() {
local platform pid pids=()
for platform; do
build $platform &>$logs/$platform &
print -r -- "starting build for $platform on $assets[$platform] (pid $!)"
pids+=($platform $!)
done
local failed=()
for platform pid in $pids; do
print -rn -- "$platform => "
if wait $pid; then
print -r -- "ok"
else
print -r -- "error"
failed+=$platform
fi
done
(( $#failed )) || return 0
print
print -r -- "Error logs:"
print
for platform in $failed; do
print -r -- " $platform => $logs/$platform"
done
return 1
}
# Copied from https://github.com/romkatv/run-process-tree.
function run-process-tree() {
zmodload zsh/parameter zsh/param/private || return
local -P opt=(${(kv)options[@]}) || return
local -P pat=(${patchars[@]}) || return
local -P dis_pat=(${dis_patchars[@]}) || return
emulate -L zsh -o err_return || return
setopt monitor traps_async pipe_fail no_unset
zmodload zsh/system
if (( $# == 0 )); then
print -ru2 -- 'usage: run-process-tree command [arg]...'
return 1
fi
local -P stdout REPLY
exec {stdout}>&1
{
{
local -Pi pipe
local -P gid=$sysparams[pid]
local -P sig=(ABRT EXIT HUP ILL INT PIPE QUIT TERM ZERR)
local -P trap=(trap "trap - $sig; kill -- -$sysparams[pid]" $sig)
exec {pipe}>&1 1>&$stdout
$trap
{
$trap
while sleep 1 && print -u $pipe .; do; done
} 2>/dev/null &
local -Pi watchdog=$!
{
trap - ZERR
exec {pipe}>&-
enable -p -- $pat
disable -p -- $dis_pat
options=($opt zle off monitor off)
"$@"
} &
local -Pi ret
wait $! || ret=$?
trap "exit $ret" TERM
kill $watchdog
wait $watchdog
return ret
} | while read; do; done || return
} always {
exec {stdout}>&-
}
}
mkdir -p -- $logs $locks $binaries
run-process-tree mbuild $@

View File

@@ -1,28 +0,0 @@
#!/usr/bin/zsh
emulate -L zsh
setopt err_exit no_unset pipe_fail extended_glob xtrace
: ${GITSTATUS_DIR:=${${(%):-%x}:A:h}}
: ${GITSTATUS_URL:=https://github.com/romkatv/gitstatus.git}
readonly GITSTATUS_DIR GITSTATUS_URL
readonly -a IGNORE=(pull-upstream.zsh README.md)
() {
local repo && repo="$(mktemp -d ${TMPDIR:-/tmp}/gitstatus-pull-upstream.XXXXXXXXXX)"
trap "rm -rf ${(q)repo}" EXIT
git clone --depth 1 $GITSTATUS_URL $repo
local dst
for dst in $GITSTATUS_DIR/**/*(.,@); do
local f=${dst#$GITSTATUS_DIR/}
(( ! ${IGNORE[(I)$f]} )) || continue
local src=$repo/$f
[[ -f $src ]] && {
mkdir -p ${dst:h} && cp -f $src $dst || return
} || {
rm -f $dst
}
done
}

37
gitstatus/src/algorithm.h Normal file
View File

@@ -0,0 +1,37 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_ALGORITHM_H_
#define ROMKATV_GITSTATUS_ALGORITHM_H_
#include <algorithm>
namespace gitstatus {
// Requires: Iter is a BidirectionalIterator.
//
// Returns iterator pointing to the last value in [begin, end) that compares equal to the value, or
// begin if none compare equal.
template <class Iter, class T>
Iter FindLast(Iter begin, Iter end, const T& val) {
while (begin != end && !(*--end == val)) {}
return end;
}
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_ALGORITHM_H_

118
gitstatus/src/arena.cc Normal file
View File

@@ -0,0 +1,118 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "arena.h"
#include <algorithm>
#include <type_traits>
#include "bits.h"
#include "check.h"
namespace gitstatus {
namespace {
size_t Clamp(size_t min, size_t val, size_t max) { return std::min(max, std::max(min, val)); }
static const uintptr_t kSingularity = reinterpret_cast<uintptr_t>(&kSingularity);
} // namespace
// Triple singularity. We are all fucked.
Arena::Block Arena::g_empty_block = {kSingularity, kSingularity, kSingularity};
Arena::Arena(Arena::Options opt) : opt_(std::move(opt)), top_(&g_empty_block) {
CHECK(opt_.min_block_size <= opt_.max_block_size);
}
Arena::Arena(Arena&& other) : Arena() { *this = std::move(other); }
Arena::~Arena() {
// See comments in Makefile for the reason sized deallocation is not used.
for (const Block& b : blocks_) ::operator delete(reinterpret_cast<void*>(b.start));
}
Arena& Arena::operator=(Arena&& other) {
if (this != &other) {
// In case std::vector ever gets small object optimization.
size_t idx = other.reusable_ ? other.top_ - other.blocks_.data() : 0;
opt_ = other.opt_;
blocks_ = std::move(other.blocks_);
reusable_ = other.reusable_;
top_ = reusable_ ? blocks_.data() + idx : &g_empty_block;
other.blocks_.clear();
other.reusable_ = 0;
other.top_ = &g_empty_block;
}
return *this;
}
void Arena::Reuse(size_t num_blocks) {
reusable_ = std::min(reusable_, num_blocks);
for (size_t i = reusable_; i != blocks_.size(); ++i) {
const Block& b = blocks_[i];
// See comments in Makefile for the reason sized deallocation is not used.
::operator delete(reinterpret_cast<void*>(b.start));
}
blocks_.resize(reusable_);
if (reusable_) {
top_ = blocks_.data();
top_->tip = top_->start;
} else {
top_ = &g_empty_block;
}
}
void Arena::AddBlock(size_t size, size_t alignment) {
if (alignment > alignof(std::max_align_t)) {
size += alignment - 1;
} else {
size = std::max(size, alignment);
}
if (size <= top_->size() && top_ < blocks_.data() + reusable_ - 1) {
assert(blocks_.front().size() == top_->size());
++top_;
top_->tip = top_->start;
return;
}
if (size <= opt_.max_alloc_threshold) {
size =
std::max(size, Clamp(opt_.min_block_size, NextPow2(top_->size() + 1), opt_.max_block_size));
}
auto p = reinterpret_cast<uintptr_t>(::operator new(size));
blocks_.push_back(Block{p, p, p + size});
if (reusable_) {
if (size < blocks_.front().size()) {
top_ = &blocks_.back();
return;
}
if (size > blocks_.front().size()) reusable_ = 0;
}
std::swap(blocks_.back(), blocks_[reusable_]);
top_ = &blocks_[reusable_++];
}
void* Arena::AllocateSlow(size_t size, size_t alignment) {
assert(alignment && !(alignment & (alignment - 1)));
AddBlock(size, alignment);
assert(Align(top_->tip, alignment) + size <= top_->end);
return Allocate(size, alignment);
}
} // namespace gitstatus

273
gitstatus/src/arena.h Normal file
View File

@@ -0,0 +1,273 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_ARENA_H_
#define ROMKATV_GITSTATUS_ARENA_H_
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <new>
#include <type_traits>
#include <vector>
#include "string_view.h"
namespace gitstatus {
// Thread-compatible. Very fast and very flexible w.r.t. allocation size and alignment.
//
// Natural API extensions:
//
// // Donates a block to the arena. When the time comes, it'll be freed with
// // free(p, size, userdata).
// void Donate(void* p, size_t size, void* userdata, void(*free)(void*, size_t, void*));
class Arena {
public:
struct Options {
// The first call to Allocate() will allocate a block of this size. There is one exception when
// the first requested allocation size is larger than this limit. Subsequent blocks will be
// twice as large as the last until they saturate at max_block_size.
size_t min_block_size = 64;
// Allocate blocks at most this large. There is one exception when the requested allocation
// size is larger than this limit.
size_t max_block_size = 8 << 10;
// When the size of the first allocation in a block is larger than this threshold, the block
// size will be equal to the allocation size. This is meant to reduce memory waste when making
// many allocations with sizes slightly over max_block_size / 2. With max_alloc_threshold equal
// to max_block_size / N, the upper bound on wasted memory when making many equally-sized
// allocations is 100.0 / (N + 1) percent. When making allocations of different sizes, the upper
// bound on wasted memory is 50%.
size_t max_alloc_threshold = 1 << 10;
// Natural extensions:
//
// void* userdata;
// void (*alloc)(size_t size, size_t alignment, void* userdata);
// void (*free)(void* p, size_t size, void* userdata);
};
// Requires: opt.min_block_size <= opt.max_block_size.
//
// Doesn't allocate any memory.
Arena(Options opt);
Arena() : Arena(Options()) {}
Arena(Arena&&);
~Arena();
Arena& operator=(Arena&& other);
// Requires: alignment is a power of 2.
//
// Result is never null and always aligned. If size is zero, the result may be equal to the last.
// Alignment above alignof(std::max_align_t) is supported. There is no requirement for alignment
// to be less than size or to divide it.
inline void* Allocate(size_t size, size_t alignment) {
assert(alignment && !(alignment & (alignment - 1)));
uintptr_t p = Align(top_->tip, alignment);
uintptr_t e = p + size;
if (e <= top_->end) {
top_->tip = e;
return reinterpret_cast<void*>(p);
}
return AllocateSlow(size, alignment);
}
template <class T>
inline T* Allocate(size_t n) {
static_assert(!std::is_reference<T>(), "");
return static_cast<T*>(Allocate(n * sizeof(T), alignof(T)));
}
template <class T>
inline T* Allocate() {
return Allocate<T>(1);
}
inline char* MemDup(const char* p, size_t len) {
char* res = Allocate<char>(len);
std::memcpy(res, p, len);
return res;
}
// Copies the null-terminated string (including the trailing null character) to the arena and
// returns a pointer to the copy.
inline char* StrDup(const char* s) {
size_t len = std::strlen(s);
return MemDup(s, len + 1);
}
// Guarantees: !StrDup(p, len)[len].
inline char* StrDup(const char* p, size_t len) {
char* res = Allocate<char>(len + 1);
std::memcpy(res, p, len);
res[len] = 0;
return res;
}
// Guarantees: !StrDup(s)[s.len].
inline char* StrDup(StringView s) {
return StrDup(s.ptr, s.len);
}
template <class... Ts>
inline char* StrCat(const Ts&... ts) {
return [&](std::initializer_list<StringView> ss) {
size_t len = 0;
for (StringView s : ss) len += s.len;
char* p = Allocate<char>(len + 1);
for (StringView s : ss) {
std::memcpy(p, s.ptr, s.len);
p += s.len;
}
*p = 0;
return p - len;
}({ts...});
}
// Copies/moves `val` to the arena and returns a pointer to it.
template <class T>
inline std::remove_const_t<std::remove_reference_t<T>>* Dup(T&& val) {
return DirectInit<std::remove_const_t<std::remove_reference_t<T>>>(std::forward<T>(val));
}
// The same as `new T{args...}` but on the arena.
template <class T, class... Args>
inline T* DirectInit(Args&&... args) {
T* res = Allocate<T>();
::new (const_cast<void*>(static_cast<const void*>(res))) T(std::forward<Args>(args)...);
return res;
}
// The same as `new T(args...)` but on the arena.
template <class T, class... Args>
inline T* BraceInit(Args&&... args) {
T* res = Allocate<T>();
::new (const_cast<void*>(static_cast<const void*>(res))) T{std::forward<Args>(args)...};
return res;
}
// Tip() and TipSize() allow you to allocate the remainder of the current block. They can be
// useful if you are flexible w.r.t. the allocation size.
//
// Invariant:
//
// const void* tip = Tip();
// void* p = Allocate(TipSize(), 1); // grab the remainder of the current block
// assert(p == tip);
const void* Tip() const { return reinterpret_cast<const void*>(top_->tip); }
size_t TipSize() const { return top_->end - top_->tip; }
// Invalidates all allocations (without running destructors of allocated objects) and frees all
// blocks except at most the specified number of blocks. The retained blocks will be used to
// fulfil future allocation requests.
void Reuse(size_t num_blocks = std::numeric_limits<size_t>::max());
private:
struct Block {
size_t size() const { return end - start; }
uintptr_t start;
uintptr_t tip;
uintptr_t end;
};
inline static size_t Align(size_t n, size_t m) { return (n + m - 1) & ~(m - 1); };
void AddBlock(size_t size, size_t alignment);
bool ReuseBlock(size_t size, size_t alignment);
__attribute__((noinline)) void* AllocateSlow(size_t size, size_t alignment);
Options opt_;
std::vector<Block> blocks_;
// Invariant: !blocks_.empty() <= reusable_ && reusable_ <= blocks_.size().
size_t reusable_ = 0;
// Invariant: (top_ == &g_empty_block) == blocks_.empty().
// Invariant: blocks_.empty() || top_ == &blocks_.back() || top_ < blocks_.data() + reusable_.
Block* top_;
static Block g_empty_block;
};
// Copies of ArenaAllocator use the same thread-compatible Arena without synchronization.
template <class T>
class ArenaAllocator {
public:
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = size_t;
using difference_type = ptrdiff_t;
using propagate_on_container_move_assignment = std::true_type;
template <class U>
struct rebind {
using other = ArenaAllocator<U>;
};
using is_always_equal = std::false_type;
ArenaAllocator(Arena* arena = nullptr) : arena_(*arena) {}
Arena& arena() const { return arena_; }
pointer address(reference x) const { return &x; }
const_pointer address(const_reference x) const { return &x; }
pointer allocate(size_type n, const void* hint = nullptr) { return arena_.Allocate<T>(n); }
void deallocate(T* p, std::size_t n) {}
size_type max_size() const { return std::numeric_limits<size_type>::max() / sizeof(value_type); }
template <class U, class... Args>
void construct(U* p, Args&&... args) {
::new (const_cast<void*>(static_cast<const void*>(p))) U(std::forward<Args>(args)...);
}
template <class U>
void destroy(U* p) {
p->~U();
}
bool operator==(const ArenaAllocator& other) const { return &arena_ == &other.arena_; }
bool operator!=(const ArenaAllocator& other) const { return &arena_ != &other.arena_; }
private:
Arena& arena_;
};
template <class C>
struct LazyWithArena;
template <template <class, class> class C, class T1, class A>
struct LazyWithArena<C<T1, A>> {
using type = C<T1, ArenaAllocator<typename C<T1, A>::value_type>>;
};
template <template <class, class, class> class C, class T1, class T2, class A>
struct LazyWithArena<C<T1, T2, A>> {
using type = C<T1, T2, ArenaAllocator<typename C<T1, T2, A>::value_type>>;
};
template <class C>
using WithArena = typename LazyWithArena<C>::type;
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_DIR_H_

29
gitstatus/src/bits.h Normal file
View File

@@ -0,0 +1,29 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_BITS_H_
#define ROMKATV_GITSTATUS_BITS_H_
#include <cstddef>
namespace gitstatus {
inline size_t NextPow2(size_t n) { return n < 2 ? 1 : (~size_t{0} >> __builtin_clzll(n - 1)) + 1; }
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_BITS_H_

61
gitstatus/src/check.h Normal file
View File

@@ -0,0 +1,61 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_CHECK_H_
#define ROMKATV_GITSTATUS_CHECK_H_
#include "logging.h"
#include <stdexcept>
// The argument must be an expression convertible to bool.
// Does nothing if the expression evalutes to true. Otherwise
// it's equivalent to LOG(FATAL).
#define CHECK(cond...) \
static_cast<void>(0), (!!(cond)) ? static_cast<void>(0) : LOG(FATAL) << #cond << ": "
#define VERIFY(cond...) \
static_cast<void>(0), ::gitstatus::internal_check::Thrower(!(cond)) \
? static_cast<void>(0) \
: LOG(ERROR) << #cond << ": "
namespace gitstatus {
struct Exception : std::exception {
const char* what() const noexcept override { return "Exception"; }
};
namespace internal_check {
class Thrower {
public:
Thrower(bool should_throw) : throw_(should_throw) {}
Thrower(Thrower&&) = delete;
explicit operator bool() const { return !throw_; }
~Thrower() noexcept(false) {
if (throw_) throw Exception();
}
private:
bool throw_;
};
} // namespace internal_check
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_CHECK_H_

View File

@@ -0,0 +1,157 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "check_dir_mtime.h"
#include <fcntl.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include <cerrno>
#include <cstring>
#include <ctime>
#include <string>
#include <vector>
#include "check.h"
#include "dir.h"
#include "logging.h"
#include "print.h"
#include "scope_guard.h"
#include "stat.h"
namespace gitstatus {
namespace {
constexpr char kDirPrefix[] = ".gitstatus.";
void Touch(const char* path) {
int fd = creat(path, 0444);
VERIFY(fd >= 0) << Errno();
CHECK(!close(fd)) << Errno();
}
bool StatChanged(const char* path, const struct stat& prev) {
struct stat cur;
VERIFY(!lstat(path, &cur)) << Errno();
return !StatEq(prev, cur);
}
void RemoveStaleDirs(const char* root_dir) {
int dir_fd = open(root_dir, O_DIRECTORY | O_CLOEXEC);
if (dir_fd < 0) return;
ON_SCOPE_EXIT(&) { CHECK(!close(dir_fd)) << Errno(); };
Arena arena;
std::vector<char*> entries;
const std::time_t now = std::time(nullptr);
if (!ListDir(dir_fd, arena, entries,
/* precompose_unicode = */ false,
/* case_sensitive = */ true)) {
return;
}
std::string path = root_dir;
const size_t root_dir_len = path.size();
for (const char* entry : entries) {
if (std::strlen(entry) < std::strlen(kDirPrefix)) continue;
if (std::memcmp(entry, kDirPrefix, std::strlen(kDirPrefix))) continue;
struct stat st;
if (fstatat(dir_fd, entry, &st, AT_SYMLINK_NOFOLLOW)) {
LOG(WARN) << "Cannot stat " << Print(entry) << " in " << Print(root_dir) << ": " << Errno();
continue;
}
if (MTim(st).tv_sec + 10 > now) continue;
path.resize(root_dir_len);
path += entry;
size_t dir_len = path.size();
path += "/b/1";
if (unlink(path.c_str()) && errno != ENOENT) {
LOG(WARN) << "Cannot unlink " << Print(path) << ": " << Errno();
continue;
}
for (const char* d : {"/a/1", "/a", "/b", ""}) {
path.resize(dir_len);
path += d;
if (rmdir(path.c_str()) && errno != ENOENT) {
LOG(WARN) << "Cannot remove " << Print(path) << ": " << Errno();
break;
}
}
}
}
} // namespace
bool CheckDirMtime(const char* root_dir) {
try {
RemoveStaleDirs(root_dir);
std::string tmp = std::string() + root_dir + kDirPrefix + "XXXXXX";
VERIFY(mkdtemp(&tmp[0])) << Errno();
ON_SCOPE_EXIT(&) { rmdir(tmp.c_str()); };
std::string a_dir = tmp + "/a";
VERIFY(!mkdir(a_dir.c_str(), 0755)) << Errno();
ON_SCOPE_EXIT(&) { rmdir(a_dir.c_str()); };
struct stat a_st;
VERIFY(!lstat(a_dir.c_str(), &a_st)) << Errno();
std::string b_dir = tmp + "/b";
VERIFY(!mkdir(b_dir.c_str(), 0755)) << Errno();
ON_SCOPE_EXIT(&) { rmdir(b_dir.c_str()); };
struct stat b_st;
VERIFY(!lstat(b_dir.c_str(), &b_st)) << Errno();
while (sleep(1)) {
// zzzz
}
std::string a1 = a_dir + "/1";
VERIFY(!mkdir(a1.c_str(), 0755)) << Errno();
ON_SCOPE_EXIT(&) { rmdir(a1.c_str()); };
if (!StatChanged(a_dir.c_str(), a_st)) {
LOG(WARN) << "Creating a directory doesn't change mtime of the parent: " << Print(root_dir);
return false;
}
std::string b1 = b_dir + "/1";
Touch(b1.c_str());
ON_SCOPE_EXIT(&) { unlink(b1.c_str()); };
if (!StatChanged(b_dir.c_str(), b_st)) {
LOG(WARN) << "Creating a file doesn't change mtime of the parent: " << Print(root_dir);
return false;
}
LOG(INFO) << "All mtime checks have passes. Enabling untracked cache: " << Print(root_dir);
return true;
} catch (const Exception&) {
LOG(WARN) << "Error while testing for mtime capability: " << Print(root_dir);
return false;
}
}
} // namespace gitstatus

View File

@@ -0,0 +1,31 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_CHECK_DIR_MTIME_H_
#define ROMKATV_GITSTATUS_CHECK_DIR_MTIME_H_
namespace gitstatus {
// Similar to `git update-index --test-untracked-cache` but performs all tests
// in parallel, so the total testing time is one second regardless of the number
// of tests. It also performs fewer tests because gitstatus imposes fewer
// requirements on the filesystem in order to take advantage of untracked cache.
bool CheckDirMtime(const char* root_dir);
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_CHECK_DIR_MTIME_H_

237
gitstatus/src/dir.cc Normal file
View File

@@ -0,0 +1,237 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "dir.h"
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cstring>
#include <dirent.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#ifdef __linux__
#include <endian.h>
#include <sys/syscall.h>
#endif
#ifdef __APPLE__
#include <iconv.h>
#endif
#include "bits.h"
#include "check.h"
#include "scope_guard.h"
#include "string_cmp.h"
#include "tribool.h"
namespace gitstatus {
namespace {
bool Dots(const char* name) {
if (name[0] == '.') {
if (name[1] == 0) return true;
if (name[1] == '.' && name[2] == 0) return true;
}
return false;
}
} // namespace
// The linux-specific implementation is about 20% faster than the generic (posix) implementation.
#ifdef __linux__
uint64_t Read64(const void* p) {
uint64_t res;
std::memcpy(&res, p, 8);
return res;
}
void Write64(uint64_t x, void* p) { std::memcpy(p, &x, 8); }
void SwapBytes(char** begin, char** end) {
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
for (; begin != end; ++begin) Write64(__builtin_bswap64(Read64(*begin)), *begin);
#elif __BYTE_ORDER__ != __ORDER_BIG_ENDIAN__
#error "sorry, not implemented"
#endif
}
template <bool kCaseSensitive>
void SortEntries(char** begin, char** end) {
static_assert(kCaseSensitive, "");
SwapBytes(begin, end);
std::sort(begin, end, [](const char* a, const char* b) {
uint64_t x = Read64(a);
uint64_t y = Read64(b);
// Add 5 for good luck.
return x < y || (x == y && std::memcmp(a + 5, b + 5, 256) < 0);
});
SwapBytes(begin, end);
}
template <>
void SortEntries<false>(char** begin, char** end) {
std::sort(begin, end, StrLt<false>());
}
bool ListDir(int dir_fd, Arena& arena, std::vector<char*>& entries, bool precompose_unicode,
bool case_sensitive) {
struct linux_dirent64 {
ino64_t d_ino;
off64_t d_off;
unsigned short d_reclen;
unsigned char d_type;
char d_name[];
};
constexpr size_t kBufSize = 8 << 10;
const size_t orig_size = entries.size();
while (true) {
char* buf = static_cast<char*>(arena.Allocate(kBufSize, alignof(linux_dirent64)));
// Save 256 bytes for the rainy day.
int n = syscall(SYS_getdents64, dir_fd, buf, kBufSize - 256);
if (n < 0) {
entries.resize(orig_size);
return false;
}
for (int pos = 0; pos < n;) {
auto* ent = reinterpret_cast<linux_dirent64*>(buf + pos);
if (!Dots(ent->d_name)) entries.push_back(ent->d_name);
pos += ent->d_reclen;
}
if (n == 0) break;
// The following optimization relies on SYS_getdents64 always returning as many
// entries as would fit. This is not guaranteed by the specification and I don't
// know if this is true in practice. The optimization has no measurable effect on
// gitstatus performance, so it's turned off.
//
// if (n + sizeof(linux_dirent64) + 512 <= kBufSize) break;
}
if (case_sensitive) {
SortEntries<true>(entries.data() + orig_size, entries.data() + entries.size());
} else {
SortEntries<false>(entries.data() + orig_size, entries.data() + entries.size());
}
return true;
}
#else // __linux__
namespace {
char* DirentDup(Arena& arena, const struct dirent& ent, size_t len) {
char* p = arena.Allocate<char>(len + 2);
*p++ = ent.d_type;
std::memcpy(p, ent.d_name, len + 1);
return p;
}
#ifdef __APPLE__
std::atomic<bool> g_iconv_error(true);
Tribool IConvTry(char* inp, size_t ins, char* outp, size_t outs) {
if (outs == 0) return Tribool::kUnknown;
iconv_t ic = iconv_open("UTF-8", "UTF-8-MAC");
if (ic == (iconv_t)-1) {
if (g_iconv_error.load(std::memory_order_relaxed) &&
g_iconv_error.exchange(false, std::memory_order_relaxed)) {
LOG(ERROR) << "iconv_open(\"UTF-8\", \"UTF-8-MAC\") failed";
}
return Tribool::kFalse;
}
ON_SCOPE_EXIT(&) { CHECK(iconv_close(ic) == 0) << Errno(); };
--outs;
if (iconv(ic, &inp, &ins, &outp, &outs) >= 0) {
*outp = 0;
return Tribool::kTrue;
}
return errno == E2BIG ? Tribool::kUnknown : Tribool::kFalse;
}
char* DirenvConvert(Arena& arena, struct dirent& ent, bool do_convert) {
if (!do_convert) return DirentDup(arena, ent, std::strlen(ent.d_name));
size_t len = 0;
do_convert = false;
for (unsigned char c; (c = ent.d_name[len]); ++len) {
if (c & 0x80) do_convert = true;
}
if (!do_convert) return DirentDup(arena, ent, len);
size_t n = NextPow2(len + 2);
while (true) {
char* p = arena.Allocate<char>(n);
switch (IConvTry(ent.d_name, len, p + 1, n - 1)) {
case Tribool::kFalse:
return DirentDup(arena, ent, len);
case Tribool::kTrue:
*p = ent.d_type;
return p + 1;
case Tribool::kUnknown:
break;
}
n *= 2;
}
}
#else // __APPLE__
char* DirenvConvert(Arena& arena, struct dirent& ent, bool do_convert) {
return DirentDup(arena, ent, std::strlen(ent.d_name));
}
#endif // __APPLE__
} // namespace
bool ListDir(int dir_fd, Arena& arena, std::vector<char*>& entries, bool precompose_unicode,
bool case_sensitive) {
const size_t orig_size = entries.size();
dir_fd = dup(dir_fd);
if (dir_fd < 0) return false;
DIR* dir = fdopendir(dir_fd);
if (!dir) {
CHECK(!close(dir_fd)) << Errno();
return false;
}
ON_SCOPE_EXIT(&) { CHECK(!closedir(dir)) << Errno(); };
while (struct dirent* ent = (errno = 0, readdir(dir))) {
if (Dots(ent->d_name)) continue;
entries.push_back(DirenvConvert(arena, *ent, precompose_unicode));
}
if (errno) {
entries.resize(orig_size);
return false;
}
StrSort(entries.data() + orig_size, entries.data() + entries.size(), case_sensitive);
return true;
}
#endif // __linux__
} // namespace gitstatus

50
gitstatus/src/dir.h Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_DIR_H_
#define ROMKATV_GITSTATUS_DIR_H_
#include <cstddef>
#include <vector>
#include "arena.h"
namespace gitstatus {
// On error, leaves entries unchaged and returns false. Does not throw.
//
// On success, appends names of files from the specified directory to entries and returns true.
// Every appended entry is a null-terminated string. At -1 offset is its d_type. All elements
// point into the arena. They are sorted either by strcmp or strcasecmp depending on case_sensitive.
//
// Does not close dir_fd.
//
// There are two distinct implementations of ListDir -- one for Linux and another for everything
// else. The linux-specific implementation is 20% faster.
//
// The reason sorting is bundled with directory listing is performance on Linux. The API of
// getdents64 allows for much faster sorting than what can be done with a plain vector<char*>.
// For the POSIX implementation there is no need to bundle sorting in this way. In fact, it's
// done at the end with a generic StrSort() call.
//
// For best results, reuse the arena and vector for multiple calls to avoid heap allocations.
bool ListDir(int dir_fd, Arena& arena, std::vector<char*>& entries, bool precompose_unicode,
bool case_sensitive);
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_DIR_H_

250
gitstatus/src/git.cc Normal file
View File

@@ -0,0 +1,250 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "git.h"
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "arena.h"
#include "check.h"
#include "print.h"
#include "scope_guard.h"
namespace gitstatus {
const char* GitError() {
const git_error* err = git_error_last();
return err && err->message ? err->message : "unknown error";
}
std::string RepoState(git_repository* repo) {
Arena arena;
StringView gitdir(git_repository_path(repo));
// These names mostly match gitaction in vcs_info:
// https://github.com/zsh-users/zsh/blob/master/Functions/VCS_Info/Backends/VCS_INFO_get_data_git.
auto State = [&]() {
switch (git_repository_state(repo)) {
case GIT_REPOSITORY_STATE_NONE:
return "";
case GIT_REPOSITORY_STATE_MERGE:
return "merge";
case GIT_REPOSITORY_STATE_REVERT:
return "revert";
case GIT_REPOSITORY_STATE_REVERT_SEQUENCE:
return "revert-seq";
case GIT_REPOSITORY_STATE_CHERRYPICK:
return "cherry";
case GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE:
return "cherry-seq";
case GIT_REPOSITORY_STATE_BISECT:
return "bisect";
case GIT_REPOSITORY_STATE_REBASE:
return "rebase";
case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE:
return "rebase-i";
case GIT_REPOSITORY_STATE_REBASE_MERGE:
return "rebase-m";
case GIT_REPOSITORY_STATE_APPLY_MAILBOX:
return "am";
case GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE:
return "am/rebase";
}
return "action";
};
auto DirExists = [&](StringView name) {
int fd = open(arena.StrCat(gitdir, "/", name), O_DIRECTORY | O_CLOEXEC);
if (fd < 0) return false;
CHECK(!close(fd)) << Errno();
return true;
};
auto ReadFile = [&](StringView name) {
std::ifstream strm(arena.StrCat(gitdir, "/", name));
std::string res;
strm >> res;
return res;
};
std::string next;
std::string last;
if (DirExists("rebase-merge")) {
next = ReadFile("rebase-merge/msgnum");
last = ReadFile("rebase-merge/end");
} else if (DirExists("rebase-apply")) {
next = ReadFile("rebase-apply/next");
last = ReadFile("rebase-apply/last");
}
std::ostringstream res;
res << State();
if (!next.empty() && !last.empty()) res << ' ' << next << '/' << last;
return res.str();
}
size_t CountRange(git_repository* repo, const std::string& range) {
git_revwalk* walk = nullptr;
VERIFY(!git_revwalk_new(&walk, repo)) << GitError();
ON_SCOPE_EXIT(=) { git_revwalk_free(walk); };
VERIFY(!git_revwalk_push_range(walk, range.c_str())) << GitError();
size_t res = 0;
while (true) {
git_oid oid;
switch (git_revwalk_next(&oid, walk)) {
case 0:
++res;
break;
case GIT_ITEROVER:
return res;
default:
LOG(ERROR) << "git_revwalk_next: " << range << ": " << GitError();
throw Exception();
}
}
}
size_t NumStashes(git_repository* repo) {
size_t res = 0;
auto* cb = +[](size_t index, const char* message, const git_oid* stash_id, void* payload) {
++*static_cast<size_t*>(payload);
return 0;
};
if (!git_stash_foreach(repo, cb, &res)) return res;
// Example error: failed to parse signature - malformed e-mail.
// See https://github.com/romkatv/powerlevel10k/issues/216.
LOG(WARN) << "git_stash_foreach: " << GitError();
return 0;
}
git_reference* Head(git_repository* repo) {
git_reference* symbolic = nullptr;
switch (git_reference_lookup(&symbolic, repo, "HEAD")) {
case 0:
break;
case GIT_ENOTFOUND:
return nullptr;
default:
LOG(ERROR) << "git_reference_lookup: " << GitError();
throw Exception();
}
git_reference* direct = nullptr;
if (git_reference_resolve(&direct, symbolic)) {
LOG(INFO) << "Empty git repo (no HEAD)";
return symbolic;
}
git_reference_free(symbolic);
return direct;
}
const char* LocalBranchName(const git_reference* ref) {
CHECK(ref);
git_reference_t type = git_reference_type(ref);
switch (type) {
case GIT_REFERENCE_DIRECT: {
return git_reference_is_branch(ref) ? git_reference_shorthand(ref) : "";
}
case GIT_REFERENCE_SYMBOLIC: {
static constexpr char kHeadPrefix[] = "refs/heads/";
const char* target = git_reference_symbolic_target(ref);
if (!target) return "";
size_t len = std::strlen(target);
if (len < sizeof(kHeadPrefix)) return "";
if (std::memcmp(target, kHeadPrefix, sizeof(kHeadPrefix) - 1)) return "";
return target + (sizeof(kHeadPrefix) - 1);
}
case GIT_REFERENCE_INVALID:
case GIT_REFERENCE_ALL:
break;
}
LOG(ERROR) << "Invalid reference type: " << type;
throw Exception();
}
RemotePtr GetRemote(git_repository* repo, const git_reference* local) {
git_remote* remote;
git_buf symref = {};
if (git_branch_remote(&remote, &symref, repo, git_reference_name(local))) return nullptr;
ON_SCOPE_EXIT(&) {
git_remote_free(remote);
git_buf_free(&symref);
};
git_reference* ref;
if (git_reference_lookup(&ref, repo, symref.ptr)) return nullptr;
ON_SCOPE_EXIT(&) { if (ref) git_reference_free(ref); };
const char* branch = nullptr;
std::string name = remote ? git_remote_name(remote) : ".";
if (git_branch_name(&branch, ref)) {
branch = "";
} else if (remote) {
VERIFY(std::strstr(branch, name.c_str()) == branch);
VERIFY(branch[name.size()] == '/');
branch += name.size() + 1;
}
auto res = std::make_unique<Remote>();
res->name = std::move(name);
res->branch = branch;
res->url = remote ? (git_remote_url(remote) ?: "") : "";
res->ref = std::exchange(ref, nullptr);
return RemotePtr(res.release());
}
PushRemotePtr GetPushRemote(git_repository* repo, const git_reference* local) {
git_remote* remote;
git_buf symref = {};
if (git_branch_push_remote(&remote, &symref, repo, git_reference_name(local))) return nullptr;
ON_SCOPE_EXIT(&) {
git_remote_free(remote);
git_buf_free(&symref);
};
git_reference* ref;
if (git_reference_lookup(&ref, repo, symref.ptr)) return nullptr;
ON_SCOPE_EXIT(&) { if (ref) git_reference_free(ref); };
std::string name = remote ? git_remote_name(remote) : ".";
auto res = std::make_unique<PushRemote>();
res->name = std::move(name);
res->url = remote ? (git_remote_url(remote) ?: "") : "";
res->ref = std::exchange(ref, nullptr);
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

115
gitstatus/src/git.h Normal file
View File

@@ -0,0 +1,115 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_GIT_H_
#define ROMKATV_GITSTATUS_GIT_H_
#include <git2.h>
#include <cstddef>
#include <memory>
#include <string>
namespace gitstatus {
// Not null.
const char* GitError();
// Not null.
std::string RepoState(git_repository* repo);
// Returns the number of commits in the range.
size_t CountRange(git_repository* repo, const std::string& range);
// How many stashes are there?
size_t NumStashes(git_repository* repo);
// Returns the origin URL or an empty string. Not null.
std::string RemoteUrl(git_repository* repo, const git_reference* ref);
// Returns reference to HEAD or null if not found. The reference is symbolic if the repo is empty
// and direct otherwise.
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;
// Name of the tracking remote. For example, "origin".
std::string name;
// Name of the tracking remote branch. For example, "master".
std::string branch;
// URL of the tracking remote. For example, "https://foo.com/repo.git".
std::string url;
// Note: pushurl is not exposed (but could be).
struct Free {
void operator()(const Remote* p) const {
if (p) {
if (p->ref) git_reference_free(p->ref);
delete p;
}
}
};
};
struct PushRemote {
// Tip of the remote branch.
git_reference* ref;
// Name of the tracking remote. For example, "origin".
std::string name;
// URL of the tracking remote. For example, "https://foo.com/repo.git".
std::string url;
// Note: pushurl is not exposed (but could be).
struct Free {
void operator()(const PushRemote* p) const {
if (p) {
if (p->ref) git_reference_free(p->ref);
delete p;
}
}
};
};
using RemotePtr = std::unique_ptr<Remote, Remote::Free>;
using PushRemotePtr = std::unique_ptr<PushRemote, PushRemote::Free>;
RemotePtr GetRemote(git_repository* repo, const git_reference* local);
PushRemotePtr GetPushRemote(git_repository* repo, const git_reference* local);
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_GIT_H_

219
gitstatus/src/gitstatus.cc Normal file
View File

@@ -0,0 +1,219 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include <time.h>
#include <cstddef>
#include <future>
#include <string>
#include <git2.h>
#include "check.h"
#include "git.h"
#include "logging.h"
#include "options.h"
#include "print.h"
#include "repo.h"
#include "repo_cache.h"
#include "request.h"
#include "response.h"
#include "scope_guard.h"
#include "thread_pool.h"
#include "timer.h"
namespace gitstatus {
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"); };
ResponseWriter resp(req.id);
Repo* repo = cache.Open(req.dir, req.from_dotgit);
if (!repo) return;
git_config* cfg;
VERIFY(!git_repository_config(&cfg, repo->repo())) << GitError();
ON_SCOPE_EXIT(=) { git_config_free(cfg); };
VERIFY(!git_config_refresh(cfg)) << GitError();
// Symbolic reference if and only if the repo is empty.
git_reference* head = Head(repo->repo());
if (!head) return;
ON_SCOPE_EXIT(=) { git_reference_free(head); };
// Null if and only if the repo is empty.
const git_oid* head_target = git_reference_target(head);
// Looking up tags may take some time. Do it in the background while we check for stuff.
// Note that GetTagName() doesn't access index, so it'll overlap with index reading and
// parsing.
std::future<std::string> tag = repo->GetTagName(head_target);
ON_SCOPE_EXIT(&) {
if (tag.valid()) {
try {
tag.wait();
} catch (const Exception&) {
}
}
};
// Repository working directory. Absolute; no trailing slash. E.g., "/home/romka/gitstatus".
StringView workdir(git_repository_workdir(repo->repo()));
if (workdir.len == 0) return;
if (workdir.len > 1 && workdir.ptr[workdir.len - 1] == '/') --workdir.len;
resp.Print(workdir);
// Revision. Either 40 hex digits or an empty string for empty repo.
resp.Print(head_target ? git_oid_tostr_s(head_target) : "");
// Local branch name (e.g., "master") or empty string if not on a branch.
resp.Print(LocalBranchName(head));
// Remote tracking branch or null.
RemotePtr remote = GetRemote(repo->repo(), head);
// Tracking remote branch name (e.g., "master") or empty string if there is no tracking remote.
resp.Print(remote ? remote->branch : "");
// Tracking remote name (e.g., "origin") or empty string if there is no tracking remote.
resp.Print(remote ? remote->name : "");
// Tracking remote URL or empty string if there is no tracking remote.
resp.Print(remote ? remote->url : "");
// Repository state, A.K.A. action. For example, "merge".
resp.Print(RepoState(repo->repo()));
IndexStats stats;
// Look for staged, unstaged and untracked. This is where most of the time is spent.
if (req.diff) stats = repo->GetIndexStats(head_target, cfg);
// The number of files in the index.
resp.Print(stats.index_size);
// The number of staged changes. At most opts.max_num_staged.
resp.Print(stats.num_staged);
// The number of unstaged changes. At most opts.max_num_unstaged. 0 if index is too large.
resp.Print(stats.num_unstaged);
// The number of conflicted changes. At most opts.max_num_conflicted. 0 if index is too large.
resp.Print(stats.num_conflicted);
// The number of untracked changes. At most opts.max_num_untracked. 0 if index is too large.
resp.Print(stats.num_untracked);
if (remote && remote->ref) {
const char* ref = git_reference_name(remote->ref);
// Number of commits we are ahead of upstream. Non-negative integer.
resp.Print(CountRange(repo->repo(), ref + "..HEAD"s));
// Number of commits we are behind upstream. Non-negative integer.
resp.Print(CountRange(repo->repo(), "HEAD.."s + ref));
} else {
resp.Print("0");
resp.Print("0");
}
// Number of stashes. Non-negative integer.
resp.Print(NumStashes(repo->repo()));
// Tag that points to HEAD (e.g., "v4.2") or empty string if there aren't any. The same as
// `git describe --tags --exact-match`.
resp.Print(tag.get());
// The number of unstaged deleted files. At most stats.num_unstaged.
resp.Print(stats.num_unstaged_deleted);
// The number of staged new files. At most stats.num_staged.
resp.Print(stats.num_staged_new);
// The number of staged deleted files. At most stats.num_staged.
resp.Print(stats.num_staged_deleted);
// Push remote or null.
PushRemotePtr push_remote = GetPushRemote(repo->repo(), head);
// Push remote name (e.g., "origin") or empty string if there is no push remote.
resp.Print(push_remote ? push_remote->name : "");
// Push remote URL or empty string if there is no push remote.
resp.Print(push_remote ? push_remote->url : "");
if (push_remote && push_remote->ref) {
const char* ref = git_reference_name(push_remote->ref);
// Number of commits we are ahead of push remote. Non-negative integer.
resp.Print(CountRange(repo->repo(), ref + "..HEAD"s));
// Number of commits we are behind upstream. Non-negative integer.
resp.Print(CountRange(repo->repo(), "HEAD.."s + ref));
} else {
resp.Print("0");
resp.Print("0");
}
// The number of files in the index with skip-worktree bit set.
resp.Print(stats.num_skip_worktree);
// 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");
}
int GitStatus(int argc, char** argv) {
tzset();
Options opts = ParseOptions(argc, argv);
g_min_log_level = opts.log_level;
for (int i = 0; i != argc; ++i) LOG(INFO) << "argv[" << i << "]: " << Print(argv[i]);
RequestReader reader(fileno(stdin), opts.lock_fd, opts.parent_pid);
RepoCache cache(opts);
InitGlobalThreadPool(opts.num_threads);
git_libgit2_opts(GIT_OPT_ENABLE_STRICT_HASH_VERIFICATION, 0);
git_libgit2_opts(GIT_OPT_DISABLE_INDEX_CHECKSUM_VERIFICATION, 1);
git_libgit2_opts(GIT_OPT_DISABLE_INDEX_FILEPATH_VALIDATION, 1);
git_libgit2_opts(GIT_OPT_DISABLE_READNG_PACKED_TAGS, 1);
git_libgit2_init();
while (true) {
try {
Request req;
if (reader.ReadRequest(req)) {
LOG(INFO) << "Processing request: " << req;
try {
ProcessRequest(opts, cache, req);
LOG(INFO) << "Successfully processed request: " << req;
} catch (const Exception&) {
LOG(ERROR) << "Error processing request: " << req;
}
} else if (opts.repo_ttl >= Duration()) {
cache.Free(Clock::now() - opts.repo_ttl);
}
} catch (const Exception&) {
}
}
}
} // namespace
} // namespace gitstatus
int main(int argc, char** argv) { gitstatus::GitStatus(argc, argv); }

456
gitstatus/src/index.cc Normal file
View File

@@ -0,0 +1,456 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "index.h"
#include <dirent.h>
#include <fcntl.h>
#include <unistd.h>
#include <algorithm>
#include <condition_variable>
#include <cstdint>
#include <cstring>
#include <iomanip>
#include <iterator>
#include <mutex>
#include <stack>
#include "algorithm.h"
#include "check.h"
#include "dir.h"
#include "git.h"
#include "index.h"
#include "print.h"
#include "scope_guard.h"
#include "stat.h"
#include "string_cmp.h"
#include "thread_pool.h"
namespace gitstatus {
namespace {
void CommonDir(Str<> str, const char* a, const char* b, size_t* dir_len, size_t* dir_depth) {
*dir_len = 0;
*dir_depth = 0;
for (size_t i = 1; str.Eq(*a, *b) && *a; ++i, ++a, ++b) {
if (*a == '/') {
*dir_len = i;
++*dir_depth;
}
}
}
size_t Weight(const IndexDir& dir) { return 1 + dir.subdirs.size() + dir.files.size(); }
bool MTimeEq(const git_index_time& index, const struct timespec& workdir) {
if (index.seconds != workdir.tv_sec) return false;
if (int64_t{index.nanoseconds} == workdir.tv_nsec) return true;
#ifdef GITSTATUS_ZERO_NSEC
return index.nanoseconds == 0;
#else
return false;
#endif
}
bool IsModified(const git_index_entry* entry, const struct stat& st, const RepoCaps& caps) {
mode_t mode = st.st_mode;
if (S_ISREG(mode)) {
if (!caps.has_symlinks && S_ISLNK(entry->mode)) {
mode = entry->mode;
} else if (!caps.trust_filemode) {
mode = entry->mode;
} else {
mode = S_IFREG | (mode & 0100 ? 0755 : 0644);
}
} else {
mode &= S_IFMT;
}
bool res = false;
#define COND(field, cond...) \
if (cond) { \
} else \
res = true, \
LOG(DEBUG) << "Dirty candidate (modified): " << Print(entry->path) << ": " #field " "
COND(ino, !entry->ino || entry->ino == static_cast<std::uint32_t>(st.st_ino))
<< entry->ino << " => " << static_cast<std::uint32_t>(st.st_ino);
COND(stage, GIT_INDEX_ENTRY_STAGE(entry) == 0) << "=> " << GIT_INDEX_ENTRY_STAGE(entry);
COND(fsize, int64_t{entry->file_size} == st.st_size) << entry->file_size << " => " << st.st_size;
COND(mtime, MTimeEq(entry->mtime, MTim(st))) << Print(entry->mtime) << " => " << Print(MTim(st));
COND(mode, entry->mode == mode) << std::oct << entry->mode << " => " << std::oct << mode;
#undef COND
return res;
}
int OpenDir(int parent_fd, const char* name) {
return openat(parent_fd, name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
}
void OpenTail(int* fds, size_t nfds, int root_fd, StringView dirname, Arena& arena) {
CHECK(fds && nfds && root_fd >= 0);
std::fill(fds, fds + nfds, -1);
if (!dirname.len) return;
CHECK(dirname.len > 1);
CHECK(dirname.ptr[0] != '/');
CHECK(dirname.ptr[dirname.len - 1] == '/');
char* begin = arena.StrDup(dirname.ptr, dirname.len - 1);
WithArena<std::vector<const char*>> subdirs(&arena);
subdirs.reserve(nfds + 1);
for (char* sep = begin + dirname.len - 1; subdirs.size() < nfds;) {
sep = FindLast(begin, sep, '/');
if (sep == begin) break;
*sep = 0;
subdirs.push_back(sep + 1);
}
subdirs.push_back(begin);
if (subdirs.size() < nfds + 1) subdirs.push_back(".");
CHECK(subdirs.size() <= nfds + 1);
for (size_t i = subdirs.size(); i != 1; --i) {
const char* path = subdirs[i - 1];
if ((root_fd = OpenDir(root_fd, path)) < 0) {
for (; i != subdirs.size(); ++i) {
CHECK(!close(fds[i - 1])) << Errno();
fds[i - 1] = -1;
}
return;
}
fds[i - 2] = root_fd;
}
}
std::vector<const char*> ScanDirs(git_index* index, int root_fd, IndexDir* const* begin,
IndexDir* const* end, const RepoCaps& caps,
const ScanOpts& opts) {
const Str<> str(caps.case_sensitive);
Arena arena;
std::vector<const char*> dirty_candidates;
std::vector<char*> entries;
entries.reserve(128);
auto AddCandidate = [&](const char* kind, const char* path) {
if (kind) LOG(DEBUG) << "Dirty candidate (" << kind << "): " << Print(path);
dirty_candidates.push_back(path);
};
constexpr ssize_t kDirStackSize = 5;
int dir_fd[kDirStackSize];
std::fill(std::begin(dir_fd), std::end(dir_fd), -1);
auto Close = [](int& fd) {
if (fd >= 0) {
CHECK(!close(fd)) << Errno();
fd = -1;
}
};
auto CloseAll = [&] { std::for_each(std::begin(dir_fd), std::end(dir_fd), Close); };
ON_SCOPE_EXIT(&) { CloseAll(); };
if (begin != end) OpenTail(dir_fd, kDirStackSize, root_fd, (*begin)->path, arena);
for (IndexDir* const* it = begin; it != end; ++it) {
IndexDir& dir = **it;
auto Basename = [&](const git_index_entry* e) { return e->path + dir.path.len; };
auto AddUnmached = [&](StringView basename) {
if (!basename.len) {
dir.st = {};
dir.unmatched.clear();
dir.arena.Reuse();
} else if (str.Eq(basename, StringView(".git/"))) {
return;
}
char* path = dir.arena.StrCat(dir.path, basename);
dir.unmatched.push_back(path);
AddCandidate(basename.len ? "new" : "unreadable", path);
};
auto StatFiles = [&]() {
struct stat st;
for (const git_index_entry* file : dir.files) {
if (fstatat(*dir_fd, Basename(file), &st, AT_SYMLINK_NOFOLLOW)) {
AddCandidate(errno == ENOENT ? "deleted" : "unreadable", file->path);
} else if (IsModified(file, st, caps)) {
AddCandidate(nullptr, file->path);
}
}
};
ssize_t d = 0;
if ((it == begin || (d = it[-1]->depth + 1 - dir.depth) < kDirStackSize) && dir_fd[d] >= 0) {
CHECK(d >= 0);
int fd = OpenDir(dir_fd[d], arena.StrDup(dir.basename.ptr, dir.basename.len));
for (ssize_t i = 0; i != d; ++i) Close(dir_fd[i]);
std::rotate(dir_fd, dir_fd + (d ? d : kDirStackSize) - 1, dir_fd + kDirStackSize);
Close(*dir_fd);
*dir_fd = fd;
} else {
CloseAll();
if (dir.path.len) {
CHECK(dir.path.ptr[0] != '/');
CHECK(dir.path.ptr[dir.path.len - 1] == '/');
*dir_fd = OpenDir(root_fd, arena.StrDup(dir.path.ptr, dir.path.len - 1));
} else {
VERIFY((*dir_fd = dup(root_fd)) >= 0) << Errno();
}
}
if (*dir_fd < 0) {
CloseAll();
AddUnmached("");
continue;
}
if (!opts.include_untracked) {
StatFiles();
continue;
}
if (opts.untracked_cache != Tribool::kFalse) {
struct stat st;
if (fstat(*dir_fd, &st)) {
AddUnmached("");
continue;
}
if (opts.untracked_cache == Tribool::kTrue && StatEq(st, dir.st)) {
StatFiles();
for (const char* path : dir.unmatched) AddCandidate("new", path);
continue;
}
dir.st = st;
}
entries.clear();
arena.Reuse();
if (!ListDir(*dir_fd, arena, entries, caps.precompose_unicode, caps.case_sensitive)) {
AddUnmached("");
continue;
}
dir.unmatched.clear();
dir.arena.Reuse();
const git_index_entry* const* file = dir.files.data();
const git_index_entry* const* file_end = file + dir.files.size();
const StringView* subdir = dir.subdirs.data();
const StringView* subdir_end = subdir + dir.subdirs.size();
for (char* entry : entries) {
bool matched = false;
for (; file != file_end; ++file) {
int cmp = str.Cmp(Basename(*file), entry);
if (cmp < 0) {
AddCandidate("deleted", (*file)->path);
} else if (cmp == 0) {
struct stat st;
if (fstatat(*dir_fd, entry, &st, AT_SYMLINK_NOFOLLOW)) {
AddCandidate("unreadable", (*file)->path);
} else if (IsModified(*file, st, caps)) {
AddCandidate(nullptr, (*file)->path);
}
matched = true;
++file;
break;
} else {
break;
}
}
if (matched) continue;
for (; subdir != subdir_end; ++subdir) {
int cmp = str.Cmp(*subdir, entry);
if (cmp > 0) break;
if (cmp == 0) {
matched = true;
++subdir;
break;
}
}
if (!matched) {
StringView basename(entry);
if (entry[-1] == DT_DIR) entry[basename.len++] = '/';
AddUnmached(basename);
}
}
for (; file != file_end; ++file) AddCandidate("deleted", (*file)->path);
}
return dirty_candidates;
}
} // namespace
RepoCaps::RepoCaps(git_repository* repo, git_index* index) {
trust_filemode = git_index_is_filemode_trustworthy(index);
has_symlinks = git_index_supports_symlinks(index);
case_sensitive = git_index_is_case_sensitive(index);
precompose_unicode = git_index_precompose_unicode(index);
LOG(DEBUG) << "Repository capabilities for " << Print(git_repository_workdir(repo)) << ": "
<< "is_filemode_trustworthy = " << std::boolalpha << trust_filemode << ", "
<< "index_supports_symlinks = " << std::boolalpha << has_symlinks << ", "
<< "index_is_case_sensitive = " << std::boolalpha << case_sensitive << ", "
<< "precompose_unicode = " << std::boolalpha << precompose_unicode;
}
Index::Index(git_repository* repo, git_index* index)
: dirs_(&arena_),
splits_(&arena_),
git_index_(index),
root_dir_(git_repository_workdir(repo)),
caps_(repo, index) {
size_t total_weight = InitDirs(index);
InitSplits(total_weight);
}
size_t Index::InitDirs(git_index* index) {
const Str<> str(git_index_is_case_sensitive(index));
const size_t index_size = git_index_entrycount(index);
dirs_.reserve(index_size / 8);
std::stack<IndexDir*> stack;
stack.push(arena_.DirectInit<IndexDir>(&arena_));
size_t total_weight = 0;
auto PopDir = [&] {
CHECK(!stack.empty());
IndexDir* top = stack.top();
CHECK(top->depth + 1 == stack.size());
if (!std::is_sorted(top->subdirs.begin(), top->subdirs.end(), str.Lt)) {
StrSort(top->subdirs.begin(), top->subdirs.end(), str.case_sensitive);
}
total_weight += Weight(*top);
dirs_.push_back(top);
stack.pop();
};
for (size_t i = 0; i != index_size; ++i) {
const git_index_entry* entry = git_index_get_byindex_no_sort(index, i);
IndexDir* prev = stack.top();
size_t common_len, common_depth;
CommonDir(str, prev->path.ptr, entry->path, &common_len, &common_depth);
CHECK(common_depth <= prev->depth);
for (size_t i = common_depth; i != prev->depth; ++i) PopDir();
for (const char* p = entry->path + common_len; (p = std::strchr(p, '/')); ++p) {
IndexDir* top = stack.top();
StringView subdir(entry->path + top->path.len, p);
top->subdirs.push_back(subdir);
IndexDir* dir = arena_.DirectInit<IndexDir>(&arena_);
dir->path = StringView(entry->path, p - entry->path + 1);
dir->basename = subdir;
dir->depth = stack.size();
CHECK(dir->path.ptr[dir->path.len - 1] == '/');
stack.push(dir);
}
CHECK(!stack.empty());
IndexDir* dir = stack.top();
dir->files.push_back(entry);
}
CHECK(!stack.empty());
do {
PopDir();
} while (!stack.empty());
std::reverse(dirs_.begin(), dirs_.end());
return total_weight;
}
void Index::InitSplits(size_t total_weight) {
constexpr size_t kMinShardWeight = 512;
const size_t kNumShards = 16 * GlobalThreadPool()->num_threads();
const size_t shard_weight = std::max(kMinShardWeight, total_weight / kNumShards);
splits_.reserve(kNumShards + 1);
splits_.push_back(0);
for (size_t i = 0, w = 0; i != dirs_.size(); ++i) {
w += Weight(*dirs_[i]);
if (w >= shard_weight) {
w = 0;
splits_.push_back(i + 1);
}
}
if (splits_.back() != dirs_.size()) splits_.push_back(dirs_.size());
CHECK(splits_.size() <= kNumShards + 1);
CHECK(std::is_sorted(splits_.begin(), splits_.end()));
CHECK(std::adjacent_find(splits_.begin(), splits_.end()) == splits_.end());
}
std::vector<const char*> Index::GetDirtyCandidates(const ScanOpts& opts) {
int root_fd = open(root_dir_, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
VERIFY(root_fd >= 0);
ON_SCOPE_EXIT(&) { CHECK(!close(root_fd)) << Errno(); };
CHECK(!splits_.empty());
std::mutex mutex;
std::condition_variable cv;
size_t inflight = splits_.size() - 1;
bool error = false;
std::vector<const char*> res;
for (size_t i = 0; i != splits_.size() - 1; ++i) {
size_t from = splits_[i];
size_t to = splits_[i + 1];
GlobalThreadPool()->Schedule([&, from, to]() {
ON_SCOPE_EXIT(&) {
std::unique_lock<std::mutex> lock(mutex);
CHECK(inflight);
if (--inflight == 0) cv.notify_one();
};
try {
std::vector<const char*> candidates =
ScanDirs(git_index_, root_fd, dirs_.data() + from, dirs_.data() + to, caps_, opts);
if (!candidates.empty()) {
std::unique_lock<std::mutex> lock(mutex);
res.insert(res.end(), candidates.begin(), candidates.end());
}
} catch (const Exception&) {
std::unique_lock<std::mutex> lock(mutex);
error = true;
}
});
}
{
std::unique_lock<std::mutex> lock(mutex);
while (inflight) cv.wait(lock);
}
VERIFY(!error);
StrSort(res.begin(), res.end(), git_index_is_case_sensitive(git_index_));
auto StrEq = [](const char* a, const char* b) { return !strcmp(a, b); };
res.erase(std::unique(res.begin(), res.end(), StrEq), res.end());
return res;
}
} // namespace gitstatus

84
gitstatus/src/index.h Normal file
View File

@@ -0,0 +1,84 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_INDEX_H_
#define ROMKATV_GITSTATUS_INDEX_H_
#include <sys/stat.h>
#include <git2.h>
#include <cstddef>
#include <string>
#include <vector>
#include "arena.h"
#include "options.h"
#include "string_view.h"
#include "tribool.h"
namespace gitstatus {
struct RepoCaps {
RepoCaps(git_repository* repo, git_index* index);
bool trust_filemode;
bool has_symlinks;
bool case_sensitive;
bool precompose_unicode;
};
struct ScanOpts {
bool include_untracked;
Tribool untracked_cache;
};
struct IndexDir {
explicit IndexDir(Arena* arena) : files(arena), subdirs(arena) {}
StringView path;
StringView basename;
size_t depth = 0;
struct stat st = {};
WithArena<std::vector<const git_index_entry*>> files;
WithArena<std::vector<StringView>> subdirs;
Arena arena;
std::vector<const char*> unmatched;
};
class Index {
public:
Index(git_repository* repo, git_index* index);
std::vector<const char*> GetDirtyCandidates(const ScanOpts& opts);
private:
size_t InitDirs(git_index* index);
void InitSplits(size_t total_weight);
Arena arena_;
WithArena<std::vector<IndexDir*>> dirs_;
WithArena<std::vector<size_t>> splits_;
git_index* git_index_;
const char* root_dir_;
RepoCaps caps_;
};
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_GIT_H_

139
gitstatus/src/logging.cc Normal file
View File

@@ -0,0 +1,139 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "logging.h"
#include <pthread.h>
#include <time.h>
#include <cerrno>
#include <cstdio>
#include <cstring>
#include <ctime>
#include <mutex>
#include <string>
namespace gitstatus {
namespace internal_logging {
namespace {
std::mutex g_log_mutex;
constexpr char kHexLower[] = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
void FormatThreadId(char (&out)[2 * sizeof(std::uintptr_t) + 1]) {
std::uintptr_t tid = (std::uintptr_t)pthread_self();
char* p = out + sizeof(out) - 1;
*p = 0;
do {
--p;
*p = kHexLower[tid & 0xF];
tid >>= 4;
} while (p != out);
}
void FormatCurrentTime(char (&out)[64]) {
std::time_t time = std::time(nullptr);
struct tm tm;
if (localtime_r(&time, &tm) != &tm || std::strftime(out, sizeof(out), "%F %T", &tm) == 0) {
std::strcpy(out, "undef");
}
}
} // namespace
LogStreamBase::LogStreamBase(const char* file, int line, LogLevel lvl)
: errno_(errno), file_(file), line_(line), lvl_(LogLevelStr(lvl)) {
strm_ = std::make_unique<std::ostringstream>();
}
void LogStreamBase::Flush() {
{
std::string msg = strm_->str();
char tid[2 * sizeof(std::uintptr_t) + 1];
FormatThreadId(tid);
char time[64];
FormatCurrentTime(time);
std::unique_lock<std::mutex> lock(g_log_mutex);
std::fprintf(stderr, "[%s %s %s %s:%d] %s\n", time, tid, lvl_, file_, line_, msg.c_str());
}
strm_.reset();
errno = errno_;
}
std::ostream& operator<<(std::ostream& strm, Errno e) {
// GNU C Library uses a buffer of 1024 characters for strerror(). Mimic to avoid truncations.
char buf[1024];
auto x = strerror_r(e.err, buf, sizeof(buf));
// There are two versions of strerror_r with different semantics. We can figure out which
// one we've got by looking at the result type.
if (std::is_same<decltype(x), int>::value) {
// XSI-compliant version.
strm << (x ? "unknown error" : buf);
} else if (std::is_same<decltype(x), char*>::value) {
// GNU-specific version.
strm << x;
} else {
// Something else entirely.
strm << "unknown error";
}
return strm;
}
} // namespace internal_logging
LogLevel g_min_log_level = INFO;
const char* LogLevelStr(LogLevel lvl) {
switch (lvl) {
case DEBUG:
return "DEBUG";
case INFO:
return "INFO";
case WARN:
return "WARN";
case ERROR:
return "ERROR";
case FATAL:
return "FATAL";
}
return "UNKNOWN";
}
bool ParseLogLevel(const char* s, LogLevel& lvl) {
if (!s)
return false;
else if (!std::strcmp(s, "DEBUG"))
lvl = DEBUG;
else if (!std::strcmp(s, "INFO"))
lvl = INFO;
else if (!std::strcmp(s, "WARN"))
lvl = WARN;
else if (!std::strcmp(s, "ERROR"))
lvl = ERROR;
else if (!std::strcmp(s, "FATAL"))
lvl = FATAL;
else
return false;
return true;
}
} // namespace gitstatus

124
gitstatus/src/logging.h Normal file
View File

@@ -0,0 +1,124 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_LOGGING_H_
#define ROMKATV_GITSTATUS_LOGGING_H_
#include <cstdlib>
#include <memory>
#include <ostream>
#include <sstream>
#define LOG(severity) LOG_I(severity)
#define LOG_I(severity) \
(::gitstatus::severity < ::gitstatus::g_min_log_level) \
? static_cast<void>(0) \
: ::gitstatus::internal_logging::Assignable() = \
::gitstatus::internal_logging::LogStream<::gitstatus::severity>(__FILE__, __LINE__, \
::gitstatus::severity) \
.ref()
namespace gitstatus {
enum LogLevel {
DEBUG,
INFO,
WARN,
ERROR,
FATAL,
};
const char* LogLevelStr(LogLevel lvl);
bool ParseLogLevel(const char* s, LogLevel& lvl);
extern LogLevel g_min_log_level;
namespace internal_logging {
struct Assignable {
template <class T>
void operator=(const T&) const {}
};
class LogStreamBase {
public:
LogStreamBase(const char* file, int line, LogLevel lvl);
LogStreamBase& ref() { return *this; }
std::ostream& strm() { return *strm_; }
int stashed_errno() const { return errno_; }
protected:
void Flush();
private:
int errno_;
const char* file_;
int line_;
const char* lvl_;
std::unique_ptr<std::ostringstream> strm_;
};
template <LogLevel>
class LogStream : public LogStreamBase {
public:
using LogStreamBase::LogStreamBase;
~LogStream() { this->Flush(); }
};
template <>
class LogStream<FATAL> : public LogStreamBase {
public:
using LogStreamBase::LogStreamBase;
~LogStream() __attribute__((noreturn)) {
this->Flush();
std::abort();
}
};
template <class T>
LogStreamBase& operator<<(LogStreamBase& strm, const T& val) {
strm.strm() << val;
return strm;
}
inline LogStreamBase& operator<<(LogStreamBase& strm, std::ostream& (*manip)(std::ostream&)) {
strm.strm() << manip;
return strm;
}
struct Errno {
int err;
};
std::ostream& operator<<(std::ostream& strm, Errno e);
struct StashedErrno {};
inline LogStreamBase& operator<<(LogStreamBase& strm, StashedErrno) {
return strm << Errno{strm.stashed_errno()};
}
} // namespace internal_logging
inline internal_logging::Errno Errno(int err) { return {err}; }
inline internal_logging::StashedErrno Errno() { return {}; }
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_LOGGING_H_

362
gitstatus/src/options.cc Normal file
View File

@@ -0,0 +1,362 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "options.h"
#include <fnmatch.h>
#include <getopt.h>
#include <unistd.h>
#include <algorithm>
#include <climits>
#include <cstdlib>
#include <cstring>
#include <iostream>
#include "print.h"
namespace gitstatus {
namespace {
long ParseLong(const char* s) {
errno = 0;
char* end = nullptr;
long res = std::strtol(s, &end, 10);
if (*end || end == s || errno) {
std::cerr << "gitstatusd: not an integer: " << s << std::endl;
std::exit(10);
}
return res;
}
long ParseInt(const char* s) {
long res = ParseLong(s);
if (res < INT_MIN || res > INT_MAX) {
std::cerr << "gitstatusd: integer out of bounds: " << s << std::endl;
std::exit(10);
}
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"
<< "\n"
<< "OPTIONS\n"
<< " -l, --lock-fd=NUM [default=-1]\n"
<< " If non-negative, check whether the specified file descriptor is locked when\n"
<< " not receiving any requests for one second; exit if it isn't locked.\n"
<< "\n"
<< " -p, --parent-pid=NUM [default=-1]\n"
<< " If non-negative, send signal 0 to the specified PID when not receiving any\n"
<< " requests for one second; exit if signal sending fails.\n"
<< "\n"
<< " -t, --num-threads=NUM [default=1]\n"
<< " Use this many threads to scan git workdir for unstaged and untracked files.\n"
<< " Empirically, setting this parameter to twice the number of virtual CPU yields\n"
<< " maximum performance.\n"
<< "\n"
<< " -v, --log-level=STR [default=INFO]\n"
<< " Don't write entires to log whose log level is below this. Log levels in\n"
<< " increasing order: DEBUG, INFO, WARN, ERROR, FATAL.\n"
<< "\n"
<< " -r, --repo-ttl-seconds=NUM [default=3600]\n"
<< " Close git repositories that haven't been used for this long. This is meant to\n"
<< " release resources such as memory and file descriptors. The next request for a\n"
<< " 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"
<< " -m, --dirty-max-index-size=NUM [default=-1]\n"
<< " If a repo has more files in its index than this, override --max-num-unstaged\n"
<< " and --max-num-untracked (but not --max-num-staged) with zeros; negative value\n"
<< " means infinity.\n"
<< "\n"
<< " -e, --recurse-untracked-dirs\n"
<< " Count files within untracked directories like `git status --untracked-files`.\n"
<< "\n"
<< " -U, --ignore-status-show-untracked-files\n"
<< " Unless this option is specified, report zero untracked files for repositories\n"
<< " with status.showUntrackedFiles = false.\n"
<< "\n"
<< " -W, --ignore-bash-show-untracked-files\n"
<< " Unless this option is specified, report zero untracked files for repositories\n"
<< " with bash.showUntrackedFiles = false.\n"
<< "\n"
<< " -D, --ignore-bash-show-dirty-state\n"
<< " Unless this option is specified, report zero staged, unstaged and conflicted\n"
<< " changes for repositories with bash.showDirtyState = false.\n"
<< "\n"
<< " -V, --version\n"
<< " Print gitstatusd version and exit.\n"
<< "\n"
<< " -G, --version-glob=STR [default=*]\n"
<< " Immediately exit with code 11 if gitstatusd version (see --version) doesn't\n"
<< " does not match the specified pattern. Matching is done with fnmatch(3)\n"
<< " without flags.\n"
<< "\n"
<< " -h, --help\n"
<< " Display this help and exit.\n"
<< "\n"
<< "INPUT\n"
<< "\n"
<< " Requests are read from stdin, separated by ascii 30 (record separator). Each\n"
<< " request is made of the following fields, in the specified order, separated by\n"
<< " ascii 31 (unit separator):\n"
<< "\n"
<< " 1. Request ID. Any string. Can be empty.\n"
<< " 2. Path to the directory for which git stats are being requested.\n"
<< " If the first character is ':', it is removed and the remaning path\n"
<< " is treated as GIT_DIR.\n"
<< " 3. (Optional) '1' to disable computation of anything that requires reading\n"
<< " git index; '0' for the default behavior of computing everything.\n"
<< "\n"
<< "OUTPUT\n"
<< "\n"
<< " For every request read from stdin there is response written to stdout.\n"
<< " Responses are separated by ascii 30 (record separator). Each response is made\n"
<< " of the following fields, in the specified order, separated by ascii 31\n"
<< " (unit separator):\n"
<< "\n"
<< " 1. Request id. The same as the first field in the request.\n"
<< " 2. 0 if the directory isn't a git repo, 1 otherwise. If 0, all the\n"
<< " following fields are missing.\n"
<< " 3. Absolute path to the git repository workdir.\n"
<< " 4. Commit hash that HEAD is pointing to. 40 hex digits.\n"
<< " 5. Local branch name or empty if not on a branch.\n"
<< " 6. Upstream branch name. Can be empty.\n"
<< " 7. The remote name, e.g. \"upstream\" or \"origin\".\n"
<< " 8. Remote URL. Can be empty.\n"
<< " 9. Repository state, A.K.A. action. Can be empty.\n"
<< " 10. The number of files in the index.\n"
<< " 11. The number of staged changes.\n"
<< " 12. The number of unstaged changes.\n"
<< " 13. The number of conflicted changes.\n"
<< " 14. The number of untracked files.\n"
<< " 15. Number of commits the current branch is ahead of upstream.\n"
<< " 16. Number of commits the current branch is behind upstream.\n"
<< " 17. The number of stashes.\n"
<< " 18. The last tag (in lexicographical order) that points to the same\n"
<< " commit as HEAD.\n"
<< " 19. The number of unstaged deleted files.\n"
<< " 20. The number of staged new files.\n"
<< " 21. The number of staged deleted files.\n"
<< " 22. The push remote name, e.g. \"upstream\" or \"origin\".\n"
<< " 23. Push remote URL. Can be empty.\n"
<< " 24. Number of commits the current branch is ahead of push remote.\n"
<< " 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"
<< "EXAMPLE\n"
<< "\n"
<< " Send a single request and print response (zsh syntax):\n"
<< "\n"
<< " local req_id=id\n"
<< " local dir=$PWD\n"
<< " echo -nE $req_id$'\\x1f'$dir$'\\x1e' | ./gitstatusd | {\n"
<< " local resp\n"
<< " IFS=$'\\x1f' read -rd $'\\x1e' -A resp && print -lr -- \"${(@qq)resp}\"\n"
<< " }\n"
<< "\n"
<< " Output:"
<< "\n"
<< " 'id'\n"
<< " '1'\n"
<< " '/home/romka/gitstatus'\n"
<< " 'bf46bf03dbab7108801b53f8a720caee8464c9c3'\n"
<< " 'master'\n"
<< " 'master'\n"
<< " 'origin'\n"
<< " 'git@github.com:romkatv/gitstatus.git'\n"
<< " ''\n"
<< " '70'\n"
<< " '1'\n"
<< " '0'\n"
<< " '0'\n"
<< " '2'\n"
<< " '0'\n"
<< " '0'\n"
<< " ''\n"
<< " '0'\n"
<< " '0'\n"
<< " '0'\n"
<< " ''\n"
<< " ''\n"
<< " '0'\n"
<< " '0'\n"
<< " '0'\n"
<< " '0'\n"
<< " ''\n"
<< " 'add a build server for darwin-arm64'\n"
<< "\n"
<< "EXIT STATUS\n"
<< "\n"
<< " The command returns zero on success (when printing help or on EOF),\n"
<< " non-zero on failure. In the latter case the output is unspecified.\n"
<< "\n"
<< "COPYRIGHT\n"
<< "\n"
<< " Copyright 2019 Roman Perepelitsa\n"
<< " This is free software; see https://github.com/romkatv/gitstatus for copying\n"
<< " conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR\n"
<< " A PARTICULAR PURPOSE." << std::endl;
}
const char* Version() {
#define _INTERNAL_GITSTATUS_STRINGIZE(x) _INTERNAL_GITSTATUS_STRINGIZE_I(x)
#define _INTERNAL_GITSTATUS_STRINGIZE_I(x) #x
return _INTERNAL_GITSTATUS_STRINGIZE(GITSTATUS_VERSION);
#undef _INTERNAL_GITSTATUS_STRINGIZE_I
#undef _INTERNAL_GITSTATUS_STRINGIZE
}
} // namespace
Options ParseOptions(int argc, char** argv) {
const struct option opts[] = {{"help", no_argument, nullptr, 'h'},
{"version", no_argument, nullptr, 'V'},
{"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'},
{"max-num-untracked", required_argument, nullptr, 'd'},
{"dirty-max-index-size", required_argument, nullptr, 'm'},
{"recurse-untracked-dirs", no_argument, nullptr, 'e'},
{"ignore-status-show-untracked-files", no_argument, nullptr, 'U'},
{"ignore-bash-show-untracked-files", no_argument, nullptr, 'W'},
{"ignore-bash-show-dirty-state", no_argument, nullptr, 'D'},
{}};
Options res;
while (true) {
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;
std::exit(10);
}
return res;
case 'h':
PrintUsage();
std::exit(0);
case 'V':
std::cout << Version() << std::endl;
std::exit(0);
case 'G':
if (int err = fnmatch(optarg, Version(), 0)) {
if (err != FNM_NOMATCH) {
std::cerr << "Cannot match " << Print(Version()) << " against pattern "
<< Print(optarg) << ": error " << err;
std::exit(10);
}
std::cerr << "Version mismatch. Wanted (pattern): " << Print(optarg)
<< ". Actual: " << Print(Version()) << "." << std::endl;
std::exit(11);
}
break;
case 'l':
res.lock_fd = ParseInt(optarg);
break;
case 'p':
res.parent_pid = ParseInt(optarg);
break;
case 'v':
if (!ParseLogLevel(optarg, res.log_level)) {
std::cerr << "invalid log level: " << optarg << std::endl;
std::exit(10);
}
break;
case 'r':
res.repo_ttl = std::chrono::seconds(ParseLong(optarg));
break;
case 't': {
long n = ParseLong(optarg);
if (n <= 0) {
std::cerr << "invalid number of threads: " << n << std::endl;
std::exit(10);
}
res.num_threads = n;
break;
}
case 'z':
res.max_commit_summary_length = ParseSizeT(optarg);
break;
case 's':
res.max_num_staged = ParseSizeT(optarg);
break;
case 'u':
res.max_num_unstaged = ParseSizeT(optarg);
break;
case 'c':
res.max_num_conflicted = ParseSizeT(optarg);
break;
case 'd':
res.max_num_untracked = ParseSizeT(optarg);
break;
case 'm':
res.dirty_max_index_size = ParseSizeT(optarg);
break;
case 'e':
res.recurse_untracked_dirs = true;
break;
case 'U':
res.ignore_status_show_untracked_files = true;
break;
case 'W':
res.ignore_bash_show_untracked_files = true;
break;
case 'D':
res.ignore_bash_show_dirty_state = true;
break;
default:
std::exit(10);
}
}
}
} // namespace gitstatus

78
gitstatus/src/options.h Normal file
View File

@@ -0,0 +1,78 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_OPTIONS_H_
#define ROMKATV_GITSTATUS_OPTIONS_H_
#include <chrono>
#include <string>
#include "logging.h"
#include "time.h"
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.
size_t max_num_unstaged = 1;
// Report at most this many conflicted changes.
size_t max_num_conflicted = 1;
// Report at most this many untracked files.
size_t max_num_untracked = 1;
// If a repo has more files in its index than this, override max_num_unstaged and
// max_num_untracked (but not max_num_staged) with zeros.
size_t dirty_max_index_size = -1;
// If true, report untracked files like `git status --untracked-files`.
bool recurse_untracked_dirs = false;
// Unless true, report zero untracked files for repositories with
// status.showUntrackedFiles = false.
bool ignore_status_show_untracked_files = false;
// Unless true, report zero untracked files for repositories with
// bash.showUntrackedFiles = false.
bool ignore_bash_show_untracked_files = false;
// Unless true, report zero staged, unstaged and conflicted changes for repositories with
// bash.showDirtyState = false.
bool ignore_bash_show_dirty_state = false;
};
struct Options : Limits {
// Use this many threads to scan git workdir for unstaged and untracked files. Must be positive.
size_t num_threads = 1;
// If non-negative, check whether the specified file descriptor is locked when not receiving any
// requests for one second; exit if it isn't locked.
int lock_fd = -1;
// If non-negative, send signal 0 to the specified PID when not receiving any requests for one
// second; exit if signal sending fails.
int parent_pid = -1;
// Don't write entires to log whose log level is below this. Log levels in increasing order:
// DEBUG, INFO, WARN, ERROR, FATAL.
LogLevel log_level = INFO;
// Close git repositories that haven't been used for this long. This is meant to release resources
// such as memory and file descriptors. The next request for a repo that's been closed is much
// slower than for a repo that hasn't been. Negative value means infinity.
Duration repo_ttl = std::chrono::seconds(3600);
};
Options ParseOptions(int argc, char** argv);
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_OPTIONS_H_

101
gitstatus/src/print.h Normal file
View File

@@ -0,0 +1,101 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_PRINT_H_
#define ROMKATV_GITSTATUS_PRINT_H_
#include <sys/stat.h>
#include <iomanip>
#include <ostream>
#include <string>
#include <type_traits>
#include <utility>
#include <vector>
#include <git2.h>
#include "string_view.h"
#include "strings.h"
namespace gitstatus {
template <class T>
struct Printable {
const T& value;
};
template <class T>
Printable<T> Print(const T& val) {
return {val};
}
template <class T>
std::ostream& operator<<(std::ostream& strm, const Printable<T>& p) {
static_assert(!std::is_pointer<std::decay_t<T>>(), "");
return strm << p.value;
}
inline std::ostream& operator<<(std::ostream& strm, const Printable<StringView>& p) {
Quote(strm, p.value.ptr, p.value.ptr + p.value.len);
return strm;
}
inline std::ostream& operator<<(std::ostream& strm, const Printable<std::string>& p) {
Quote(strm, p.value.data(), p.value.data() + p.value.size());
return strm;
}
inline std::ostream& operator<<(std::ostream& strm, const Printable<const char*>& p) {
Quote(strm, p.value, p.value ? p.value + std::strlen(p.value) : nullptr);
return strm;
}
inline std::ostream& operator<<(std::ostream& strm, const Printable<char*>& p) {
Quote(strm, p.value, p.value ? p.value + std::strlen(p.value) : nullptr);
return strm;
}
template <class T, class U>
std::ostream& operator<<(std::ostream& strm, const Printable<std::pair<T, U>>& p) {
return strm << '{' << Print(p.value.first) << ", " << Print(p.value.second) << '}';
}
template <class T>
std::ostream& operator<<(std::ostream& strm, const Printable<std::vector<T>>& p) {
strm << '[';
for (size_t i = 0; i != p.value.size(); ++i) {
if (i) strm << ", ";
strm << Print(p.value[i]);
}
strm << ']';
return strm;
}
inline std::ostream& operator<<(std::ostream& strm, const Printable<struct timespec>& p) {
strm << p.value.tv_sec << '.' << std::setw(9) << std::setfill('0') << p.value.tv_nsec;
return strm;
}
inline std::ostream& operator<<(std::ostream& strm, const Printable<git_index_time>& p) {
strm << p.value.seconds << '.' << std::setw(9) << std::setfill('0') << p.value.nanoseconds;
return strm;
}
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_PRINT_H_

503
gitstatus/src/repo.cc Normal file
View File

@@ -0,0 +1,503 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "repo.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <atomic>
#include <cstdlib>
#include <cstring>
#include <exception>
#include <iterator>
#include <memory>
#include <type_traits>
#include <utility>
#include "arena.h"
#include "check.h"
#include "check_dir_mtime.h"
#include "dir.h"
#include "git.h"
#include "print.h"
#include "scope_guard.h"
#include "stat.h"
#include "string_cmp.h"
#include "thread_pool.h"
#include "timer.h"
namespace gitstatus {
namespace {
using namespace std::string_literals;
template <class T>
T Load(const std::atomic<T>& x) {
return x.load(std::memory_order_relaxed);
}
template <class T>
void Store(std::atomic<T>& x, T v) {
x.store(v, std::memory_order_relaxed);
}
template <class T>
T Inc(std::atomic<T>& x, T by = 1) {
return x.fetch_add(by, std::memory_order_relaxed);
}
template <class T>
T Dec(std::atomic<T>& x) {
return x.fetch_sub(1, std::memory_order_relaxed);
}
template <class T>
T Exchange(std::atomic<T>& x, T v) {
return x.exchange(v, std::memory_order_relaxed);
}
const char* DeltaStr(git_delta_t t) {
switch (t) {
case GIT_DELTA_UNMODIFIED: return "unmodified";
case GIT_DELTA_ADDED: return "added";
case GIT_DELTA_DELETED: return "deleted";
case GIT_DELTA_MODIFIED: return "modified";
case GIT_DELTA_RENAMED: return "renamed";
case GIT_DELTA_COPIED: return "copied";
case GIT_DELTA_IGNORED: return "ignored";
case GIT_DELTA_UNTRACKED: return "untracked";
case GIT_DELTA_TYPECHANGE: return "typechange";
case GIT_DELTA_UNREADABLE: return "unreadable";
case GIT_DELTA_CONFLICTED: return "conflicted";
}
return "unknown";
}
} // namespace
bool Repo::Shard::Contains(Str<> str, StringView path) const {
if (str.Lt(path, start_s)) return false;
if (end_s.empty()) return true;
path.len = std::min(path.len, end_s.size());
return !str.Lt(end_s, path);
}
Repo::Repo(git_repository* repo, Limits lim) : lim_(std::move(lim)), repo_(repo), tag_db_(repo) {
if (lim_.max_num_untracked) {
GlobalThreadPool()->Schedule([this] {
bool check = CheckDirMtime(git_repository_path(repo_));
std::unique_lock<std::mutex> lock(mutex_);
CHECK(Load(untracked_cache_) == Tribool::kUnknown);
Store(untracked_cache_, check ? Tribool::kTrue : Tribool::kFalse);
cv_.notify_one();
});
} else {
untracked_cache_ = Tribool::kFalse;
}
}
Repo::~Repo() {
{
std::unique_lock<std::mutex> lock(mutex_);
while (untracked_cache_ == Tribool::kUnknown) cv_.wait(lock);
}
if (git_index_) git_index_free(git_index_);
git_repository_free(repo_);
}
IndexStats Repo::GetIndexStats(const git_oid* head, git_config* cfg) {
ON_SCOPE_EXIT(this, orig_lim = lim_) { lim_ = orig_lim; };
auto Off = [&](const char* name) {
int val;
if (git_config_get_bool(&val, cfg, name) || val) return false;
LOG(INFO) << "Honoring git config option: " << name << " = false";
return true;
};
if (!lim_.ignore_status_show_untracked_files && Off("status.showUntrackedFiles")) {
lim_.max_num_untracked = 0;
}
if (!lim_.ignore_bash_show_untracked_files && Off("bash.showUntrackedFiles")) {
lim_.max_num_untracked = 0;
}
if (!lim_.ignore_bash_show_dirty_state && Off("bash.showDirtyState")) {
lim_.max_num_staged = 0;
lim_.max_num_unstaged = 0;
lim_.max_num_conflicted = 0;
}
if (git_index_) {
int new_index;
VERIFY(!git_index_read_ex(git_index_, 0, &new_index)) << GitError();
if (new_index) {
head_ = {};
index_.reset();
}
} else {
VERIFY(!git_repository_index(&git_index_, repo_)) << GitError();
// Query an attribute (doesn't matter which) to initialize repo's attribute
// cache. It's a workaround for synchronization bugs (data races) in libgit2
// that result from lazy cache initialization without synchrnonization.
// Thankfully, subsequent cache reads and writes are properly synchronized.
const char* attr;
VERIFY(!git_attr_get(&attr, repo_, 0, "x", "x")) << GitError();
}
UpdateShards();
Store(error_, false);
Store(unstaged_, {});
Store(untracked_, {});
Store(unstaged_deleted_, {});
std::vector<const char*> dirty_candidates;
const size_t index_size = git_index_entrycount(git_index_);
if (!lim_.max_num_staged && !lim_.max_num_conflicted) {
head_ = {};
Store(staged_, {});
Store(conflicted_, {});
Store(staged_new_, {});
Store(staged_deleted_, {});
Store(skip_worktree_, {});
Store(assume_unchanged_, {});
} else if (head) {
if (git_oid_equal(head, &head_)) {
LOG(INFO) << "Index and HEAD unchanged; staged = " << Load(staged_)
<< ", conflicted = " << Load(conflicted_);
} else {
head_ = *head;
Store(staged_, {});
Store(conflicted_, {});
Store(staged_new_, {});
Store(staged_deleted_, {});
Store(skip_worktree_, {});
Store(assume_unchanged_, {});
StartStagedScan(head);
}
} else {
head_ = {};
size_t staged = 0;
size_t skip_worktree = 0;
size_t assume_unchanged = 0;
for (size_t i = 0; i != index_size; ++i) {
const git_index_entry* entry = git_index_get_byindex_no_sort(git_index_, i);
if (!(entry->flags_extended & GIT_INDEX_ENTRY_INTENT_TO_ADD)) ++staged;
if (entry->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) ++skip_worktree;
if (entry->flags & GIT_INDEX_ENTRY_VALID) ++assume_unchanged;
}
Store(staged_, staged);
Store(conflicted_, {});
Store(staged_new_, staged);
Store(staged_deleted_, {});
Store(skip_worktree_, skip_worktree);
Store(assume_unchanged_, assume_unchanged);
}
if (index_size <= lim_.dirty_max_index_size &&
(lim_.max_num_unstaged || lim_.max_num_untracked)) {
if (!index_) index_ = std::make_unique<Index>(repo_, git_index_);
dirty_candidates = index_->GetDirtyCandidates({.include_untracked = lim_.max_num_untracked > 0,
.untracked_cache = Load(untracked_cache_)});
if (dirty_candidates.empty()) {
LOG(INFO) << "Clean repo: no dirty candidates";
} else {
LOG(INFO) << "Found " << dirty_candidates.size() << " dirty candidate(s) spanning from "
<< Print(dirty_candidates.front()) << " to " << Print(dirty_candidates.back());
}
StartDirtyScan(dirty_candidates);
}
Wait();
VERIFY(!Load(error_));
size_t num_staged = std::min(Load(staged_), lim_.max_num_staged);
size_t num_unstaged = std::min(Load(unstaged_), lim_.max_num_unstaged);
return {.index_size = index_size,
.num_staged = num_staged,
.num_unstaged = num_unstaged,
.num_conflicted = std::min(Load(conflicted_), lim_.max_num_conflicted),
.num_untracked = std::min(Load(untracked_), lim_.max_num_untracked),
.num_staged_new = std::min(Load(staged_new_), num_staged),
.num_staged_deleted = std::min(Load(staged_deleted_), num_staged),
.num_unstaged_deleted = std::min(Load(unstaged_deleted_), num_unstaged),
.num_skip_worktree = Load(skip_worktree_),
.num_assume_unchanged = Load(assume_unchanged_)};
}
int Repo::OnDelta(const char* type, const git_diff_delta& d, std::atomic<size_t>& c1, size_t m1,
const std::atomic<size_t>& c2, size_t m2) {
auto Msg = [&]() {
const char* status = DeltaStr(d.status);
std::ostringstream strm;
strm << "Found " << type << " file";
if (strcmp(status, type)) strm << " (" << status << ")";
strm << ": " << Print(d.new_file.path);
return strm.str();
};
size_t v = Inc(c1);
if (v) {
LOG(DEBUG) << Msg();
} else {
LOG(INFO) << Msg();
}
if (v + 1 < m1) return GIT_DIFF_DELTA_DO_NOT_INSERT;
if (Load(c2) < m2) return GIT_DIFF_DELTA_DO_NOT_INSERT | GIT_DIFF_DELTA_SKIP_TYPE;
return GIT_EUSER;
}
void Repo::StartDirtyScan(const std::vector<const char*>& paths) {
if (paths.empty()) return;
git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
opt.payload = this;
opt.flags = GIT_DIFF_INCLUDE_TYPECHANGE_TREES | GIT_DIFF_SKIP_BINARY_CHECK |
GIT_DIFF_DISABLE_PATHSPEC_MATCH | GIT_DIFF_EXEMPLARS;
if (lim_.max_num_untracked) {
opt.flags |= GIT_DIFF_INCLUDE_UNTRACKED;
if (lim_.recurse_untracked_dirs) opt.flags |= GIT_DIFF_RECURSE_UNTRACKED_DIRS;
} else {
opt.flags |= GIT_DIFF_ENABLE_FAST_UNTRACKED_DIRS;
}
opt.ignore_submodules = GIT_SUBMODULE_IGNORE_DIRTY;
opt.notify_cb = +[](const git_diff* diff, const git_diff_delta* delta,
const char* matched_pathspec, void* payload) -> int {
if (delta->status == GIT_DELTA_CONFLICTED) return GIT_DIFF_DELTA_DO_NOT_INSERT;
Repo* repo = static_cast<Repo*>(payload);
if (Load(repo->error_)) return GIT_EUSER;
if (delta->status == GIT_DELTA_UNTRACKED) {
return repo->OnDelta("untracked", *delta, repo->untracked_, repo->lim_.max_num_untracked,
repo->unstaged_, repo->lim_.max_num_unstaged);
} else {
if (delta->status == GIT_DELTA_DELETED) Inc(repo->unstaged_deleted_);
return repo->OnDelta("unstaged", *delta, repo->unstaged_, repo->lim_.max_num_unstaged,
repo->untracked_, repo->lim_.max_num_untracked);
}
};
const Str<> str(git_index_is_case_sensitive(git_index_));
auto shard = shards_.begin();
for (auto p = paths.begin(); p != paths.end();) {
opt.range_start = *p;
opt.range_end = *p;
opt.pathspec.strings = const_cast<char**>(&*p);
opt.pathspec.count = 1;
while (!shard->Contains(str, StringView(*p))) ++shard;
while (++p != paths.end() && shard->Contains(str, StringView(*p))) {
opt.range_end = *p;
++opt.pathspec.count;
}
RunAsync([this, opt]() {
git_diff* diff = nullptr;
LOG(DEBUG) << "git_diff_index_to_workdir from " << Print(opt.range_start) << " to "
<< Print(opt.range_end);
switch (git_diff_index_to_workdir(&diff, repo_, git_index_, &opt)) {
case 0:
git_diff_free(diff);
break;
case GIT_EUSER:
break;
default:
LOG(ERROR) << "git_diff_index_to_workdir: " << GitError();
throw Exception();
}
});
}
}
void Repo::StartStagedScan(const git_oid* head) {
git_commit* commit = nullptr;
VERIFY(!git_commit_lookup(&commit, repo_, head)) << GitError();
ON_SCOPE_EXIT(=) { git_commit_free(commit); };
git_tree* tree = nullptr;
VERIFY(!git_commit_tree(&tree, commit)) << GitError();
git_diff_options opt = GIT_DIFF_OPTIONS_INIT;
opt.flags = GIT_DIFF_EXEMPLARS | GIT_DIFF_INCLUDE_TYPECHANGE_TREES;
opt.payload = this;
opt.notify_cb = +[](const git_diff* diff, const git_diff_delta* delta,
const char* matched_pathspec, void* payload) -> int {
Repo* repo = static_cast<Repo*>(payload);
if (Load(repo->error_)) return GIT_EUSER;
if (delta->status == GIT_DELTA_CONFLICTED) {
return repo->OnDelta("conflicted", *delta, repo->conflicted_, repo->lim_.max_num_conflicted,
repo->staged_, repo->lim_.max_num_staged);
} else {
if (delta->status == GIT_DELTA_ADDED) Inc(repo->staged_new_);
if (delta->status == GIT_DELTA_DELETED) Inc(repo->staged_deleted_);
return repo->OnDelta("staged", *delta, repo->staged_, repo->lim_.max_num_staged,
repo->conflicted_, repo->lim_.max_num_conflicted);
}
};
for (const Shard& shard : shards_) {
RunAsync([this, tree, opt, shard]() mutable {
size_t skip_worktree = 0;
size_t assume_unchanged = 0;
for (size_t i = shard.start_i; i != shard.end_i; ++i) {
const git_index_entry* entry = git_index_get_byindex_no_sort(git_index_, i);
if (entry->flags_extended & GIT_INDEX_ENTRY_SKIP_WORKTREE) ++skip_worktree;
if (entry->flags & GIT_INDEX_ENTRY_VALID) ++assume_unchanged;
}
Inc(skip_worktree_, skip_worktree);
Inc(assume_unchanged_, assume_unchanged);
opt.range_start = shard.start_s.c_str();
opt.range_end = shard.end_s.c_str();
git_diff* diff = nullptr;
LOG(DEBUG) << "git_diff_tree_to_index from " << Print(opt.range_start) << " to "
<< Print(opt.range_end);
switch (git_diff_tree_to_index(&diff, repo_, tree, git_index_, &opt)) {
case 0:
git_diff_free(diff);
break;
case GIT_EUSER:
break;
default:
LOG(ERROR) << "git_diff_tree_to_index: " << GitError();
throw Exception();
}
});
}
}
void Repo::UpdateShards() {
constexpr size_t kEntriesPerShard = 512;
const Str<> str(git_index_is_case_sensitive(git_index_));
size_t index_size = git_index_entrycount(git_index_);
ON_SCOPE_EXIT(&) {
LOG(INFO) << "Splitting " << index_size << " object(s) into " << shards_.size() << " shard(s)";
};
if (index_size <= kEntriesPerShard || GlobalThreadPool()->num_threads() < 2) {
shards_ = {{
.start_s = "",
.end_s = "",
.start_i = 0,
.end_i = index_size}};
return;
}
size_t shards =
std::min(index_size / kEntriesPerShard + 1, 2 * GlobalThreadPool()->num_threads());
shards_.clear();
shards_.reserve(shards);
std::string last_s;
size_t last_i = 0;
for (size_t i = 0; i != shards - 1; ++i) {
size_t idx = (i + 1) * index_size / shards;
std::string split = git_index_get_byindex_no_sort(git_index_, idx)->path;
auto pos = split.find_last_of('/');
if (pos == std::string::npos) continue;
split = split.substr(0, pos + 1);
Shard shard;
shard.end_s = split;
--shard.end_s.back();
if (!str.Lt(last_s, shard.end_s)) continue;
shard.start_s = std::move(last_s);
last_s = std::move(split);
shard.start_i = last_i;
shard.end_i = idx;
last_i = idx;
shards_.push_back(std::move(shard));
}
shards_.push_back({
.start_s = std::move(last_s),
.end_s = "",
.start_i = last_i,
.end_i = index_size});
CHECK(!shards_.empty());
CHECK(shards_.size() <= shards);
CHECK(shards_.front().start_s.empty());
CHECK(shards_.front().start_i == 0);
CHECK(shards_.back().end_s.empty());
CHECK(shards_.back().end_i == index_size);
for (size_t i = 0; i != shards_.size(); ++i) {
if (i) {
const git_index_entry* entry = git_index_get_byindex_no_sort(git_index_, shards_[i].start_i);
CHECK(!std::memcmp(shards_[i].start_s.c_str(), entry->path, shards_[i].start_s.size()));
CHECK(str.Lt(shards_[i - 1].end_s, shards_[i].start_s));
CHECK(shards_[i - 1].end_i == shards_[i].start_i);
}
if (i != shards_.size() - 1) {
CHECK(shards_[i].start_i < shards_[i].end_i);
CHECK(str.Lt(shards_[i].start_s, shards_[i].end_s));
}
}
}
void Repo::DecInflight() {
std::unique_lock<std::mutex> lock(mutex_);
CHECK(Load(inflight_) > 0);
if (Dec(inflight_) == 1) cv_.notify_one();
}
void Repo::RunAsync(std::function<void()> f) {
Inc(inflight_);
try {
GlobalThreadPool()->Schedule([this, f = std::move(f)] {
try {
ON_SCOPE_EXIT(&) { DecInflight(); };
f();
} catch (const Exception&) {
if (!Load(error_)) {
std::unique_lock<std::mutex> lock(mutex_);
if (!Load(error_)) {
Store(error_, true);
cv_.notify_one();
}
}
}
});
} catch (...) {
DecInflight();
throw;
}
}
void Repo::Wait() {
std::unique_lock<std::mutex> lock(mutex_);
while (inflight_) cv_.wait(lock);
}
std::future<std::string> Repo::GetTagName(const git_oid* target) {
auto* promise = new std::promise<std::string>;
std::future<std::string> res = promise->get_future();
GlobalThreadPool()->Schedule([=] {
ON_SCOPE_EXIT(&) { delete promise; };
if (!target) {
promise->set_value("");
return;
}
try {
promise->set_value(tag_db_.TagForCommit(*target));
} catch (const Exception&) {
promise->set_exception(std::current_exception());
}
});
return res;
}
} // namespace gitstatus

126
gitstatus/src/repo.h Normal file
View File

@@ -0,0 +1,126 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_REPO_H_
#define ROMKATV_GITSTATUS_REPO_H_
#include <stddef.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <git2.h>
#include <algorithm>
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstring>
#include <functional>
#include <future>
#include <memory>
#include <mutex>
#include <string>
#include <utility>
#include <vector>
#include "check.h"
#include "index.h"
#include "options.h"
#include "string_cmp.h"
#include "tag_db.h"
#include "time.h"
namespace gitstatus {
struct IndexStats {
size_t index_size = 0;
size_t num_staged = 0;
size_t num_unstaged = 0;
size_t num_conflicted = 0;
size_t num_untracked = 0;
size_t num_staged_new = 0;
size_t num_staged_deleted = 0;
size_t num_unstaged_deleted = 0;
size_t num_skip_worktree = 0;
size_t num_assume_unchanged = 0;
};
class Repo {
public:
explicit Repo(git_repository* repo, Limits lim);
Repo(Repo&& other) = delete;
~Repo();
git_repository* repo() const { return repo_; }
// Head can be null, in which case has_staged will be false.
IndexStats GetIndexStats(const git_oid* head, git_config* cfg);
// Returns the last tag in lexicographical order whose target is equal to the given, or an
// empty string. Target can be null, in which case the tag is empty.
std::future<std::string> GetTagName(const git_oid* target);
private:
struct Shard {
bool Contains(Str<> str, StringView path) const;
std::string start_s;
std::string end_s;
size_t start_i;
size_t end_i;
};
void UpdateShards();
int OnDelta(const char* type, const git_diff_delta& d, std::atomic<size_t>& c1, size_t m1,
const std::atomic<size_t>& c2, size_t m2);
void StartStagedScan(const git_oid* head);
void StartDirtyScan(const std::vector<const char*>& paths);
void DecInflight();
void RunAsync(std::function<void()> f);
void Wait();
Limits lim_;
git_repository* const repo_;
git_index* git_index_ = nullptr;
std::vector<Shard> shards_;
git_oid head_ = {};
TagDb tag_db_;
std::unique_ptr<Index> index_;
std::mutex mutex_;
std::condition_variable cv_;
std::atomic<size_t> inflight_{0};
std::atomic<bool> error_{false};
std::atomic<size_t> staged_{0};
std::atomic<size_t> unstaged_{0};
std::atomic<size_t> conflicted_{0};
std::atomic<size_t> untracked_{0};
std::atomic<size_t> staged_new_{0};
std::atomic<size_t> staged_deleted_{0};
std::atomic<size_t> unstaged_deleted_{0};
std::atomic<size_t> skip_worktree_{0};
std::atomic<size_t> assume_unchanged_{0};
std::atomic<Tribool> untracked_cache_{Tribool::kUnknown};
};
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_REPO_H_

167
gitstatus/src/repo_cache.cc Normal file
View File

@@ -0,0 +1,167 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "repo_cache.h"
#include <cstring>
#include "check.h"
#include "git.h"
#include "print.h"
#include "scope_guard.h"
#include "string_view.h"
namespace gitstatus {
namespace {
void GitDirs(const char* dir, bool from_dotgit, std::string& gitdir, std::string& workdir) {
git_buf gitdir_buf = {};
git_buf workdir_buf = {};
ON_SCOPE_EXIT(&) {
git_buf_free(&gitdir_buf);
git_buf_free(&workdir_buf);
};
int flags = from_dotgit ? GIT_REPOSITORY_OPEN_NO_SEARCH | GIT_REPOSITORY_OPEN_NO_DOTGIT : 0;
switch (git_repository_discover_ex(&gitdir_buf, &workdir_buf, NULL, NULL, dir, flags, nullptr)) {
case 0:
gitdir.assign(gitdir_buf.ptr, gitdir_buf.size);
workdir.assign(workdir_buf.ptr, workdir_buf.size);
VERIFY(!gitdir.empty() && gitdir.front() == '/' && gitdir.back() == '/');
VERIFY(!workdir.empty() && workdir.front() == '/' && workdir.back() == '/');
break;
case GIT_ENOTFOUND:
gitdir.clear();
workdir.clear();
break;
default:
LOG(ERROR) << "git_repository_open_ext: " << Print(dir) << ": " << GitError();
throw Exception();
}
}
git_repository* OpenRepo(const std::string& dir, bool from_dotgit) {
git_repository* repo = nullptr;
int flags = from_dotgit ? GIT_REPOSITORY_OPEN_NO_SEARCH | GIT_REPOSITORY_OPEN_NO_DOTGIT : 0;
switch (git_repository_open_ext(&repo, dir.c_str(), flags, nullptr)) {
case 0:
return repo;
case GIT_ENOTFOUND:
return nullptr;
default:
LOG(ERROR) << "git_repository_open_ext: " << Print(dir) << ": " << GitError();
throw Exception();
}
}
std::string DirName(std::string path) {
if (path.empty()) return "";
while (path.back() == '/') {
path.pop_back();
if (path.empty()) return "";
}
do {
path.pop_back();
if (path.empty()) return "";
} while (path.back() != '/');
return path;
}
} // namespace
Repo* RepoCache::Open(const std::string& dir, bool from_dotgit) {
if (dir.empty() || dir.front() != '/') return nullptr;
std::string gitdir, workdir;
GitDirs(dir.c_str(), from_dotgit, gitdir, workdir);
if (gitdir.empty()) {
// This isn't quite correct because of differences in canonicalization, .git files and GIT_DIR.
// A proper solution would require tracking the "discovery dir" for every repository and
// performing path canonicalization.
if (from_dotgit) {
Erase(cache_.find(dir.back() == '/' ? dir : dir + '/'));
} else {
std::string path = dir;
if (path.back() != '/') path += '/';
do {
Erase(cache_.find(path + ".git/"));
path = DirName(path);
} while (!path.empty());
}
return nullptr;
}
auto it = cache_.find(gitdir);
if (it != cache_.end()) {
lru_.erase(it->second->lru);
it->second->lru = lru_.insert({Clock::now(), it});
return it->second.get();
}
// Opening from gitdir is faster but we cannot use it when gitdir came from a .git file.
git_repository* repo =
DirName(gitdir) == workdir ? OpenRepo(gitdir, true) : OpenRepo(dir, from_dotgit);
if (!repo) return nullptr;
ON_SCOPE_EXIT(&) {
if (repo) git_repository_free(repo);
};
if (git_repository_is_bare(repo)) return nullptr;
workdir = git_repository_workdir(repo) ?: "";
if (workdir.empty()) return nullptr;
VERIFY(workdir.front() == '/' && workdir.back() == '/') << Print(workdir);
auto x = cache_.emplace(gitdir, nullptr);
std::unique_ptr<Entry>& elem = x.first->second;
if (elem) {
lru_.erase(elem->lru);
} else {
LOG(INFO) << "Initializing new repository: " << Print(gitdir);
// Libgit2 initializes odb and refdb lazily with double-locking. To avoid useless work
// when multiple threads attempt to initialize the same db at the same time, we trigger
// initialization manually before threads are in play.
git_odb* odb;
VERIFY(!git_repository_odb(&odb, repo)) << GitError();
git_odb_free(odb);
git_refdb* refdb;
VERIFY(!git_repository_refdb(&refdb, repo)) << GitError();
git_refdb_free(refdb);
elem = std::make_unique<Entry>(std::exchange(repo, nullptr), lim_);
}
elem->lru = lru_.insert({Clock::now(), x.first});
return elem.get();
}
void RepoCache::Free(Time cutoff) {
while (true) {
if (lru_.empty()) break;
auto it = lru_.begin();
if (it->first > cutoff) break;
Erase(it->second);
}
}
void RepoCache::Erase(Cache::iterator it) {
if (it == cache_.end()) return;
LOG(INFO) << "Closing repository: " << Print(it->first);
lru_.erase(it->second->lru);
cache_.erase(it);
}
} // namespace gitstatus

View File

@@ -0,0 +1,60 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_REPO_CACHE_H_
#define ROMKATV_GITSTATUS_REPO_CACHE_H_
#include <map>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <git2.h>
#include "options.h"
#include "repo.h"
#include "time.h"
namespace gitstatus {
class RepoCache {
public:
explicit RepoCache(Limits lim) : lim_(std::move(lim)) {}
Repo* Open(const std::string& dir, bool from_dotgit);
void Free(Time cutoff);
private:
struct Entry;
using Cache = std::unordered_map<std::string, std::unique_ptr<Entry>>;
using LRU = std::multimap<Time, Cache::iterator>;
void Erase(Cache::iterator it);
Limits lim_;
Cache cache_;
LRU lru_;
struct Entry : Repo {
using Repo::Repo;
LRU::iterator lru;
};
};
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_REPO_CACHE_H_

130
gitstatus/src/request.cc Normal file
View File

@@ -0,0 +1,130 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "request.h"
#include <fcntl.h>
#include <signal.h>
#include <sys/select.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <cstdlib>
#include <iostream>
#include "check.h"
#include "logging.h"
#include "print.h"
#include "serialization.h"
namespace gitstatus {
namespace {
Request ParseRequest(const std::string& s) {
Request res;
auto begin = s.begin(), end = s.end(), sep = std::find(begin, end, kFieldSep);
VERIFY(sep != end) << "Malformed request: " << s;
res.id.assign(begin, sep);
begin = sep + 1;
if (*begin == ':') {
res.from_dotgit = true;
++begin;
}
sep = std::find(begin, end, kFieldSep);
res.dir.assign(begin, sep);
if (sep == end) return res;
begin = sep + 1;
VERIFY(begin + 1 == end && (*begin == '0' || *begin == '1')) << "Malformed request: " << s;
res.diff = *begin == '0';
return res;
}
bool IsLockedFd(int fd) {
CHECK(fd >= 0);
struct flock flock = {};
flock.l_type = F_RDLCK;
flock.l_whence = SEEK_SET;
CHECK(fcntl(fd, F_GETLK, &flock) != -1) << Errno();
return flock.l_type != F_UNLCK;
}
} // namespace
std::ostream& operator<<(std::ostream& strm, const Request& req) {
strm << Print(req.id) << " for " << Print(req.dir);
if (req.from_dotgit) strm << " [from-dotgit]";
if (!req.diff) strm << " [no-diff]";
return strm;
}
RequestReader::RequestReader(int fd, int lock_fd, int parent_pid)
: fd_(fd), lock_fd_(lock_fd), parent_pid_(parent_pid) {
CHECK(fd != lock_fd);
}
bool RequestReader::ReadRequest(Request& req) {
auto eol = std::find(read_.begin(), read_.end(), kMsgSep);
if (eol != read_.end()) {
std::string msg(read_.begin(), eol);
read_.erase(read_.begin(), eol + 1);
req = ParseRequest(msg);
return true;
}
char buf[256];
while (true) {
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd_, &fds);
struct timeval timeout = {.tv_sec = 1};
int n;
CHECK((n = select(fd_ + 1, &fds, NULL, NULL, &timeout)) >= 0) << Errno();
if (n == 0) {
if (lock_fd_ >= 0 && !IsLockedFd(lock_fd_)) {
LOG(INFO) << "Lock on fd " << lock_fd_ << " is gone. Exiting.";
std::exit(0);
}
if (parent_pid_ >= 0 && kill(parent_pid_, 0)) {
LOG(INFO) << "Unable to send signal 0 to " << parent_pid_ << ". Exiting.";
std::exit(0);
}
req = {};
return false;
}
CHECK((n = read(fd_, buf, sizeof(buf))) >= 0) << Errno();
if (n == 0) {
LOG(INFO) << "EOF. Exiting.";
std::exit(0);
}
read_.insert(read_.end(), buf, buf + n);
int eol = std::find(buf, buf + n, kMsgSep) - buf;
if (eol != n) {
std::string msg(read_.begin(), read_.end() - (n - eol));
read_.erase(read_.begin(), read_.begin() + msg.size() + 1);
req = ParseRequest(msg);
return true;
}
}
}
} // namespace gitstatus

50
gitstatus/src/request.h Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_REQUEST_H_
#define ROMKATV_GITSTATUS_REQUEST_H_
#include <deque>
#include <ostream>
#include <string>
namespace gitstatus {
struct Request {
std::string id;
std::string dir;
bool from_dotgit = false;
bool diff = true;
};
std::ostream& operator<<(std::ostream& strm, const Request& req);
class RequestReader {
public:
RequestReader(int fd, int lock_fd, int parent_pid);
bool ReadRequest(Request& req);
private:
int fd_;
int lock_fd_;
int parent_pid_;
std::deque<char> read_;
};
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_REQUEST_H_

73
gitstatus/src/response.cc Normal file
View File

@@ -0,0 +1,73 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "response.h"
#include <cctype>
#include <cstring>
#include <iostream>
#include "check.h"
#include "serialization.h"
namespace gitstatus {
namespace {
constexpr char kUnreadable = '?';
void SafePrint(std::ostream& strm, StringView s) {
for (size_t i = 0; i != s.len; ++i) {
char c = s.ptr[i];
strm << (c > 127 || std::isprint(c) ? c : kUnreadable);
}
}
} // namespace
ResponseWriter::ResponseWriter(std::string request_id) : request_id_(std::move(request_id)) {
SafePrint(strm_, request_id_);
Print(1);
}
ResponseWriter::~ResponseWriter() {
if (!done_) {
strm_.str("");
SafePrint(strm_, request_id_);
Print("0");
Dump("without git status");
}
}
void ResponseWriter::Print(ssize_t val) {
strm_ << kFieldSep;
strm_ << val;
}
void ResponseWriter::Print(StringView val) {
strm_ << kFieldSep;
SafePrint(strm_, val);
}
void ResponseWriter::Dump(const char* log) {
CHECK(!done_);
done_ = true;
LOG(INFO) << "Replying " << log;
std::cout << strm_.str() << kMsgSep << std::flush;
}
} // namespace gitstatus

50
gitstatus/src/response.h Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_RESPONSE_H_
#define ROMKATV_GITSTATUS_RESPONSE_H_
#include <cstddef>
#include <cstdint>
#include <sstream>
#include <string>
#include "string_view.h"
namespace gitstatus {
class ResponseWriter {
public:
ResponseWriter(std::string request_id);
ResponseWriter(ResponseWriter&&) = delete;
~ResponseWriter();
void Print(ssize_t val);
void Print(StringView val);
void Print(const char* val) { Print(StringView(val)); }
void Dump(const char* log);
private:
bool done_ = false;
std::string request_id_;
std::ostringstream strm_;
};
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_RESPONSE_H_

View File

@@ -0,0 +1,56 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_SCOPE_GUARD_H_
#define ROMKATV_GITSTATUS_SCOPE_GUARD_H_
#include <utility>
#define ON_SCOPE_EXIT(capture...) \
auto GITSTATUS_INTERNAL_CAT(_gitstatus_scope_guard_, __COUNTER__) = \
::gitstatus::internal_scope_guard::ScopeGuardGenerator() = [capture]()
#define GITSTATUS_INTERNAL_CAT_I(x, y) x##y
#define GITSTATUS_INTERNAL_CAT(x, y) GITSTATUS_INTERNAL_CAT_I(x, y)
namespace gitstatus {
namespace internal_scope_guard {
void Undefined();
template <class F>
class ScopeGuard {
public:
explicit ScopeGuard(F f) : f_(std::move(f)) {}
~ScopeGuard() { std::move(f_)(); }
ScopeGuard(ScopeGuard&& other) : f_(std::move(other.f_)) { Undefined(); }
private:
F f_;
};
struct ScopeGuardGenerator {
template <class F>
ScopeGuard<F> operator=(F f) const {
return ScopeGuard<F>(std::move(f));
}
};
} // namespace internal_scope_guard
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_SCOPE_GUARD_H_

View File

@@ -0,0 +1,28 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_SERIALIZATION_H_
#define ROMKATV_GITSTATUS_SERIALIZATION_H_
namespace gitstatus {
constexpr char kFieldSep = 31; // ascii 31 is unit separator
constexpr char kMsgSep = 30; // ascii 30 is record separator
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_SERIALIZATION_H_

23
gitstatus/src/stat.h Normal file
View File

@@ -0,0 +1,23 @@
#ifndef ROMKATV_GITSTATUS_STAT_H_
#define ROMKATV_GITSTATUS_STAT_H_
#include <sys/stat.h>
namespace gitstatus {
inline const struct timespec& MTim(const struct stat& s) {
#ifdef __APPLE__
return s.st_mtimespec;
#else
return s.st_mtim;
#endif
}
inline bool StatEq(const struct stat& x, const struct stat& y) {
return MTim(x).tv_sec == MTim(y).tv_sec && MTim(x).tv_nsec == MTim(y).tv_nsec &&
x.st_size == y.st_size && x.st_ino == y.st_ino && x.st_mode == y.st_mode;
}
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_STAT_H_

151
gitstatus/src/string_cmp.h Normal file
View File

@@ -0,0 +1,151 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_STRING_CMP_H_
#define ROMKATV_GITSTATUS_STRING_CMP_H_
#include <string.h> // because there is no std::strcasecmp in C++
#include <algorithm>
#include <cctype>
#include <cstddef>
#include <cstring>
#include "string_view.h"
namespace gitstatus {
// WARNING: These routines assume no embedded null characters in StringView. Violations cause UB.
template <int kCaseSensitive = -1>
struct StrCmp;
template <>
struct StrCmp<0> {
int operator()(StringView x, StringView y) const {
size_t n = std::min(x.len, y.len);
int cmp = strncasecmp(x.ptr, y.ptr, n);
if (cmp) return cmp;
return static_cast<ssize_t>(x.len) - static_cast<ssize_t>(y.len);
}
int operator()(StringView x, const char* y) const {
for (const char *p = x.ptr, *e = p + x.len; p != e; ++p, ++y) {
if (int cmp = std::tolower(*p) - std::tolower(*y)) return cmp;
}
return 0 - *y;
}
int operator()(char x, char y) const { return std::tolower(x) - std::tolower(y); }
int operator()(const char* x, const char* y) const { return strcasecmp(x, y); }
int operator()(const char* x, StringView y) const { return -operator()(y, x); }
};
template <>
struct StrCmp<1> {
int operator()(StringView x, StringView y) const {
size_t n = std::min(x.len, y.len);
int cmp = std::memcmp(x.ptr, y.ptr, n);
if (cmp) return cmp;
return static_cast<ssize_t>(x.len) - static_cast<ssize_t>(y.len);
}
int operator()(StringView x, const char* y) const {
for (const char *p = x.ptr, *e = p + x.len; p != e; ++p, ++y) {
if (int cmp = *p - *y) return cmp;
}
return 0 - *y;
}
int operator()(char x, char y) const { return x - y; }
int operator()(const char* x, const char* y) const { return std::strcmp(x, y); }
int operator()(const char* x, StringView y) const { return -operator()(y, x); }
};
template <>
struct StrCmp<-1> {
explicit StrCmp(bool case_sensitive) : case_sensitive(case_sensitive) {}
template <class X, class Y>
int operator()(const X& x, const Y& y) const {
return case_sensitive ? StrCmp<1>()(x, y) : StrCmp<0>()(x, y);
}
bool case_sensitive;
};
template <int kCaseSensitive = -1>
struct StrLt : private StrCmp<kCaseSensitive> {
using StrCmp<kCaseSensitive>::StrCmp;
template <class X, class Y>
bool operator()(const X& x, const Y& y) const {
return StrCmp<kCaseSensitive>::operator()(x, y) < 0;
}
};
template <int kCaseSensitive = -1>
struct StrEq : private StrCmp<kCaseSensitive> {
using StrCmp<kCaseSensitive>::StrCmp;
template <class X, class Y>
bool operator()(const X& x, const Y& y) const {
return StrCmp<kCaseSensitive>::operator()(x, y) == 0;
}
bool operator()(const StringView& x, const StringView& y) const {
return x.len == y.len && StrCmp<kCaseSensitive>::operator()(x, y) == 0;
}
};
template <int kCaseSensitive = -1>
struct Str {
static_assert(kCaseSensitive == 0 || kCaseSensitive == 1, "");
static const bool case_sensitive = kCaseSensitive;
StrCmp<kCaseSensitive> Cmp;
StrLt<kCaseSensitive> Lt;
StrEq<kCaseSensitive> Eq;
};
template <int kCaseSensitive>
const bool Str<kCaseSensitive>::case_sensitive;
template <>
struct Str<-1> {
explicit Str(bool case_sensitive)
: case_sensitive(case_sensitive),
Cmp(case_sensitive),
Lt(case_sensitive),
Eq(case_sensitive) {}
bool case_sensitive;
StrCmp<-1> Cmp;
StrLt<-1> Lt;
StrEq<-1> Eq;
};
template <class Iter>
void StrSort(Iter begin, Iter end, bool case_sensitive) {
case_sensitive ? std::sort(begin, end, StrLt<true>()) : std::sort(begin, end, StrLt<false>());
}
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_STRING_CMP_H_

View File

@@ -0,0 +1,77 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_STRING_VIEW_H_
#define ROMKATV_GITSTATUS_STRING_VIEW_H_
#include <algorithm>
#include <cstddef>
#include <cstring>
#include <ostream>
#include <string>
namespace gitstatus {
// WARNING: StringView must not have embedded null characters. Violations cause UB.
struct StringView {
StringView() : StringView("") {}
// Requires: !memchr(s.data(), 0, s.size()).
//
// WARNING: The existence of this requirement and the fact that this constructor is implicit
// means it's dangerous to have std::string instances with embedded null characters anywhere
// in the program. If you have an std::string `s` with embedded nulls, an innocent-looking
// `F(s)` might perform an implicit conversion to StringView and land you squarely in the
// Undefined Behavior land.
StringView(const std::string& s) : StringView(s.c_str(), s.size()) {}
// Requires: !memchr(ptr, 0, len).
StringView(const char* ptr, size_t len) : ptr(ptr), len(len) {}
// Requires: end >= begin && !memchr(begin, 0, end - begin).
StringView(const char* begin, const char* end) : StringView(begin, end - begin) {}
// Requires: strchr(s, 0) == s + N.
template <size_t N>
StringView(const char (&s)[N]) : StringView(s, N - 1) {
static_assert(N, "");
}
// Explicit because it's the only constructor that isn't O(1).
// Are you sure you don't already known the strings's length?
explicit StringView(const char* ptr) : StringView(ptr, ptr ? std::strlen(ptr) : 0) {}
bool StartsWith(StringView prefix) const {
return len >= prefix.len && !std::memcmp(ptr, prefix.ptr, prefix.len);
}
bool EndsWith(StringView suffix) const {
return len >= suffix.len && !std::memcmp(ptr + (len - suffix.len), suffix.ptr, suffix.len);
}
const char* ptr;
size_t len;
};
inline std::ostream& operator<<(std::ostream& strm, StringView s) {
if (s.ptr) strm.write(s.ptr, s.len);
return strm;
}
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_STRING_VIEW_H_

71
gitstatus/src/strings.cc Normal file
View File

@@ -0,0 +1,71 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include <cassert>
#include "strings.h"
namespace gitstatus {
void CEscape(std::ostream& strm, const char* begin, const char* end) {
assert(!begin == !end);
if (!begin) return;
for (; begin != end; ++begin) {
const unsigned char c = *begin;
switch (c) {
case '\t':
strm << "\\t";
continue;
case '\n':
strm << "\\n";
continue;
case '\r':
strm << "\\r";
continue;
case '"':
strm << "\\\"";
continue;
case '\'':
strm << "\\'";
continue;
case '\\':
strm << "\\\\";
continue;
}
if (c > 31 && c < 127) {
strm << c;
continue;
}
strm << '\\';
strm << static_cast<char>('0' + ((c >> 6) & 7));
strm << static_cast<char>('0' + ((c >> 3) & 7));
strm << static_cast<char>('0' + ((c >> 0) & 7));
}
}
void Quote(std::ostream& strm, const char* begin, const char* end) {
assert(!begin == !end);
if (!begin) {
strm << "null";
return;
}
strm << '"';
CEscape(strm, begin, end);
strm << '"';
}
} // namespace gitstatus

37
gitstatus/src/strings.h Normal file
View File

@@ -0,0 +1,37 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_STRINGS_H_
#define ROMKATV_GITSTATUS_STRINGS_H_
#include <ostream>
namespace gitstatus {
// If the pointers are null, prints nothing.
//
// Requires: !begin == !end.
void CEscape(std::ostream& strm, const char* begin, const char* end);
// If the pointers are null, prints null without quotes.
//
// Requires: !begin == !end.
void Quote(std::ostream& strm, const char* begin, const char* end);
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_STRING_VIEW_H_

332
gitstatus/src/tag_db.cc Normal file
View File

@@ -0,0 +1,332 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "tag_db.h"
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <iterator>
#include <utility>
#include "check.h"
#include "dir.h"
#include "git.h"
#include "print.h"
#include "scope_guard.h"
#include "stat.h"
#include "string_cmp.h"
#include "thread_pool.h"
#include "timer.h"
namespace gitstatus {
namespace {
using namespace std::string_literals;
static constexpr char kTagPrefix[] = "refs/tags/";
constexpr int8_t kUnhex[256] = {
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 2
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 0, 0, 0, 0, 0, // 3
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 5
0, 10, 11, 12, 13, 14, 15, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 6
};
struct {
bool operator()(const Tag* x, const git_oid& y) const {
return std::memcmp(x->id.id, y.id, GIT_OID_RAWSZ) < 0;
}
bool operator()(const git_oid& x, const Tag* y) const {
return std::memcmp(x.id, y->id.id, GIT_OID_RAWSZ) < 0;
}
bool operator()(const Tag* x, const Tag* y) const {
return std::memcmp(x->id.id, y->id.id, GIT_OID_RAWSZ) < 0;
}
} constexpr ById = {};
struct {
bool operator()(const Tag* x, const char* y) const {
return std::strcmp(x->name, y) < 0;
}
bool operator()(const char* x, const Tag* y) const {
return std::strcmp(x, y->name) < 0;
}
bool operator()(const Tag* x, const Tag* y) const {
return std::strcmp(x->name, y->name) < 0;
}
} constexpr ByName = {};
void ParseOid(unsigned char* oid, const char* begin, const char* end) {
VERIFY(end >= begin + GIT_OID_HEXSZ);
for (size_t i = 0; i != GIT_OID_HEXSZ; i += 2) {
*oid++ = kUnhex[+begin[i]] << 4 | kUnhex[+begin[i + 1]];
}
}
const char* StripTag(const char* ref) {
for (size_t i = 0; i != sizeof(kTagPrefix) - 1; ++i) {
if (*ref++ != kTagPrefix[i]) return nullptr;
}
return ref;
}
git_refdb* RefDb(git_repository* repo) {
git_refdb* res;
VERIFY(!git_repository_refdb(&res, repo)) << GitError();
return res;
}
} // namespace
TagDb::TagDb(git_repository* repo)
: repo_(repo),
refdb_(RefDb(repo)),
pack_(&pack_arena_),
name2id_(&pack_arena_),
id2name_(&pack_arena_) {
CHECK(repo_ && refdb_);
}
TagDb::~TagDb() {
Wait();
git_refdb_free(refdb_);
}
std::string TagDb::TagForCommit(const git_oid& oid) {
ReadLooseTags();
UpdatePack();
std::string res;
std::string ref = "refs/tags/";
size_t prefix_len = ref.size();
for (const char* tag : loose_tags_) {
ref.resize(prefix_len);
ref += tag;
if (res < tag && TagHasTarget(ref.c_str(), &oid)) res = tag;
}
if ((std::unique_lock<std::mutex>(mutex_), id2name_dirty_)) {
for (auto it = name2id_.rbegin(); it != name2id_.rend(); ++it) {
if (!memcmp((*it)->id.id, oid.id, GIT_OID_RAWSZ) && !IsLooseTag((*it)->name)) {
if (res < (*it)->name) res = (*it)->name;
break;
}
}
} else {
auto r = std::equal_range(id2name_.begin(), id2name_.end(), oid, ById);
for (auto it = r.first; it != r.second; ++it) {
if (!IsLooseTag((*it)->name) && res < (*it)->name) res = (*it)->name;
}
}
return res;
}
void TagDb::ReadLooseTags() {
loose_tags_.clear();
loose_arena_.Reuse();
std::string dirname = git_repository_path(repo_) + "refs/tags"s;
int dir_fd = open(dirname.c_str(), O_RDONLY | O_DIRECTORY | O_CLOEXEC);
if (dir_fd < 0) return;
ON_SCOPE_EXIT(&) { CHECK(!close(dir_fd)) << Errno(); };
// TODO: recursively traverse directories so that the file refs/tags/foo/bar gets interpreted
// as the tag foo/bar. See https://github.com/romkatv/gitstatus/issues/254.
(void)ListDir(dir_fd, loose_arena_, loose_tags_, /* precompose_unicode = */ false,
/* case_sensitive = */ true);
}
void TagDb::UpdatePack() {
auto Reset = [&] {
auto Wipe = [](auto& x) {
x.clear();
x.shrink_to_fit();
};
Wait();
Wipe(pack_);
Wipe(name2id_);
Wipe(id2name_);
pack_arena_.Reuse();
std::memset(&pack_stat_, 0, sizeof(pack_stat_));
};
std::string pack_path = git_repository_path(repo_) + "packed-refs"s;
struct stat st;
if (stat(pack_path.c_str(), &st)) {
Reset();
return;
}
if (StatEq(pack_stat_, st)) return;
Reset();
try {
while (true) {
LOG(INFO) << "Parsing " << Print(pack_path);
int fd = open(pack_path.c_str(), O_RDONLY | O_CLOEXEC);
VERIFY(fd >= 0);
ON_SCOPE_EXIT(&) { CHECK(!close(fd)) << Errno(); };
pack_.resize(st.st_size + 1);
ssize_t n = read(fd, &pack_[0], st.st_size + 1);
VERIFY(n >= 0) << Errno();
VERIFY(!fstat(fd, &pack_stat_)) << Errno();
if (!StatEq(st, pack_stat_)) {
st = pack_stat_;
continue;
}
VERIFY(n == st.st_size);
pack_.pop_back();
break;
}
ParsePack();
} catch (const Exception&) {
Reset();
throw;
}
}
void TagDb::ParsePack() {
char* p = &pack_[0];
char* e = p + pack_.size();
// Usually packed-refs starts with the following line:
//
// # pack-refs with: peeled fully-peeled sorted
//
// However, some users can produce pack-refs without this line.
// See https://github.com/romkatv/powerlevel10k/issues/1428.
// I don't know how they do it. Without the header line we cannot
// assume that refs are sorted, which isn't a big deal because we
// can just sort them. What's worse is that refs cannot be assumed
// to be fully-peeled. We don't want to peel them, so we just drop
// all tags.
if (*p != '#') {
LOG(WARN) << "packed-refs doesn't have a header. Won't resolve tags.";
return;
}
char* eol = std::strchr(p, '\n');
if (!eol) return;
*eol = 0;
if (!std::strstr(p, " fully-peeled") || !std::strstr(p, " sorted")) {
LOG(WARN) << "packed-refs has unexpected header. Won't resolve tags.";
}
p = eol + 1;
name2id_.reserve(pack_.size() / 128);
id2name_.reserve(pack_.size() / 128);
std::vector<Tag*> idx;
idx.reserve(pack_.size() / 128);
while (p != e) {
Tag* tag = pack_arena_.Allocate<Tag>();
ParseOid(tag->id.id, p, e);
p += GIT_OID_HEXSZ;
VERIFY(*p++ == ' ');
const char* ref = p;
VERIFY(p = std::strchr(p, '\n'));
p[p[-1] == '\r' ? -1 : 0] = 0;
++p;
if (*p == '^') {
ParseOid(tag->id.id, p + 1, e);
p += GIT_OID_HEXSZ + 1;
if (p != e) {
VERIFY((p = std::strchr(p, '\n')));
++p;
}
}
tag->name = StripTag(ref);
if (!tag->name) continue;
name2id_.push_back(tag);
id2name_.push_back(tag);
}
if (!std::is_sorted(name2id_.begin(), name2id_.end(), ByName)) {
// "sorted" in the header of packed-refs promisses that this won't trigger.
std::sort(name2id_.begin(), name2id_.end(), ByName);
}
id2name_dirty_ = true;
GlobalThreadPool()->Schedule([this] {
std::sort(id2name_.begin(), id2name_.end(), ById);
std::unique_lock<std::mutex> lock(mutex_);
CHECK(id2name_dirty_);
id2name_dirty_ = false;
cv_.notify_one();
});
}
void TagDb::Wait() {
std::unique_lock<std::mutex> lock(mutex_);
while (id2name_dirty_) cv_.wait(lock);
}
bool TagDb::IsLooseTag(const char* name) const {
return std::binary_search(loose_tags_.begin(), loose_tags_.end(), name,
[](const char* a, const char* b) { return std::strcmp(a, b) < 0; });
}
bool TagDb::TagHasTarget(const char* name, const git_oid* target) const {
static constexpr size_t kMaxDerefCount = 10;
git_reference* ref;
if (git_refdb_lookup(&ref, refdb_, name)) return false;
ON_SCOPE_EXIT(&) { git_reference_free(ref); };
for (int i = 0; i != kMaxDerefCount && git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC; ++i) {
git_reference* dst;
const char* ref_name = git_reference_name(ref);
if (git_refdb_lookup(&dst, refdb_, ref_name)) {
const char* tag_name = StripTag(ref_name);
auto it = std::lower_bound(name2id_.begin(), name2id_.end(), tag_name, ByName);
return it != name2id_.end() && !strcmp((*it)->name, tag_name) && !IsLooseTag(tag_name) &&
git_oid_equal(&(*it)->id, target);
}
git_reference_free(ref);
ref = dst;
}
if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) return false;
const git_oid* oid = git_reference_target_peel(ref) ?: git_reference_target(ref);
if (git_oid_equal(oid, target)) return true;
for (int i = 0; i != kMaxDerefCount; ++i) {
git_tag* tag;
if (git_tag_lookup(&tag, repo_, oid)) return false;
ON_SCOPE_EXIT(&) { git_tag_free(tag); };
if (git_tag_target_type(tag) == GIT_OBJECT_COMMIT) {
return git_oid_equal(git_tag_target_id(tag), target);
}
oid = git_tag_target_id(tag);
}
return false;
}
} // namespace gitstatus

79
gitstatus/src/tag_db.h Normal file
View File

@@ -0,0 +1,79 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_TAG_DB_H_
#define ROMKATV_GITSTATUS_TAG_DB_H_
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <git2.h>
#include <condition_variable>
#include <cstring>
#include <mutex>
#include <string>
#include <vector>
#include "arena.h"
namespace gitstatus {
struct Tag {
const char* name;
git_oid id;
};
class TagDb {
public:
explicit TagDb(git_repository* repo);
TagDb(TagDb&&) = delete;
~TagDb();
std::string TagForCommit(const git_oid& oid);
private:
void ReadLooseTags();
void UpdatePack();
void ParsePack();
void Wait();
bool IsLooseTag(const char* name) const;
bool TagHasTarget(const char* name, const git_oid* target) const;
git_repository* const repo_;
git_refdb* const refdb_;
Arena pack_arena_;
struct stat pack_stat_ = {};
WithArena<std::string> pack_;
WithArena<std::vector<const Tag*>> name2id_;
WithArena<std::vector<const Tag*>> id2name_;
Arena loose_arena_;
std::vector<char*> loose_tags_;
std::mutex mutex_;
std::condition_variable cv_;
bool id2name_dirty_ = false;
};
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_TAG_DB_H_

View File

@@ -0,0 +1,87 @@
#include "thread_pool.h"
#include <cassert>
#include <utility>
#include "check.h"
#include "logging.h"
namespace gitstatus {
ThreadPool::ThreadPool(size_t num_threads) : num_inflight_(num_threads) {
for (size_t i = 0; i != num_threads; ++i) {
threads_.emplace_back([=]() { Loop(i + 1); });
}
}
ThreadPool::~ThreadPool() {
{
std::lock_guard<std::mutex> lock(mutex_);
exit_ = true;
}
cv_.notify_all();
sleeper_cv_.notify_one();
for (std::thread& t : threads_) t.join();
}
void ThreadPool::Schedule(Time t, std::function<void()> f) {
std::condition_variable* wake = nullptr;
{
std::unique_lock<std::mutex> lock(mutex_);
work_.push(Work{std::move(t), ++last_idx_, std::move(f)});
if (work_.top().idx == last_idx_) wake = have_sleeper_ ? &sleeper_cv_ : &cv_;
}
if (wake) wake->notify_one();
}
void ThreadPool::Loop(size_t tid) {
auto Next = [&]() -> std::function<void()> {
std::unique_lock<std::mutex> lock(mutex_);
--num_inflight_;
if (work_.empty() && num_inflight_ == 0) idle_cv_.notify_all();
while (true) {
if (exit_) return nullptr;
if (work_.empty()) {
cv_.wait(lock);
continue;
}
Time now = Clock::now();
const Work& top = work_.top();
if (top.t <= now) {
std::function<void()> res = std::move(top.f);
work_.pop();
++num_inflight_;
bool notify = !work_.empty() && !have_sleeper_;
lock.unlock();
if (notify) cv_.notify_one();
return res;
}
if (have_sleeper_) {
cv_.wait(lock);
continue;
}
have_sleeper_ = true;
sleeper_cv_.wait_until(lock, top.t);
assert(have_sleeper_);
have_sleeper_ = false;
}
};
while (std::function<void()> f = Next()) f();
}
void ThreadPool::Wait() {
std::unique_lock<std::mutex> lock(mutex_);
idle_cv_.wait(lock, [&] { return work_.empty() && num_inflight_ == 0; });
}
static ThreadPool* g_thread_pool = nullptr;
void InitGlobalThreadPool(size_t num_threads) {
CHECK(!g_thread_pool);
LOG(INFO) << "Spawning " << num_threads << " thread(s)";
g_thread_pool = new ThreadPool(num_threads);
}
ThreadPool* GlobalThreadPool() { return g_thread_pool; }
} // namespace gitstatus

View File

@@ -0,0 +1,74 @@
#ifndef ROMKATV_GITSTATUS_THREAD_POOL_H_
#define ROMKATV_GITSTATUS_THREAD_POOL_H_
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <functional>
#include <mutex>
#include <queue>
#include <thread>
#include <tuple>
#include <utility>
#include "time.h"
namespace gitstatus {
class ThreadPool {
public:
explicit ThreadPool(size_t num_threads);
ThreadPool(ThreadPool&&) = delete;
// Waits for the currently running functions to finish.
// Does NOT wait for the queue of functions to drain.
// If you want the latter, call Wait() manually.
~ThreadPool();
// Runs `f` on one of the threads at or after time `t`. Can be called
// from any thread. Can be called concurrently.
//
// Does not block.
void Schedule(Time t, std::function<void()> f);
void Schedule(std::function<void()> f) { Schedule(Clock::now(), std::move(f)); }
// Blocks until the work queue is empty and there are no currently
// running functions.
void Wait();
size_t num_threads() const { return threads_.size(); }
private:
struct Work {
bool operator<(const Work& w) const { return std::tie(w.t, w.idx) < std::tie(t, idx); }
Time t;
int64_t idx;
mutable std::function<void()> f;
};
void Loop(size_t tid);
int64_t last_idx_ = 0;
int64_t num_inflight_;
bool exit_ = false;
// Do we have a thread waiting on sleeper_cv_?
bool have_sleeper_ = false;
std::mutex mutex_;
// Any number of threads can wait on this condvar. Always without a timeout.
std::condition_variable cv_;
// At most one thread can wait on this condvar at a time. Always with a timeout.
std::condition_variable sleeper_cv_;
// Signalled when the work queue is empty and there is nothing inflight.
std::condition_variable idle_cv_;
std::priority_queue<Work> work_;
std::vector<std::thread> threads_;
};
void InitGlobalThreadPool(size_t num_threads);
ThreadPool* GlobalThreadPool();
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_THREAD_POOL_H_

14
gitstatus/src/time.h Normal file
View File

@@ -0,0 +1,14 @@
#ifndef ROMKATV_GITSTATUS_TIME_H_
#define ROMKATV_GITSTATUS_TIME_H_
#include <chrono>
namespace gitstatus {
using Clock = std::chrono::steady_clock;
using Time = Clock::time_point;
using Duration = Clock::duration;
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_TIME_H_

72
gitstatus/src/timer.cc Normal file
View File

@@ -0,0 +1,72 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#include "timer.h"
#include <sys/resource.h>
#include <sys/time.h>
#include <time.h>
#include <cmath>
#include <limits>
#include "check.h"
#include "logging.h"
namespace gitstatus {
namespace {
double CpuTimeMs() {
auto ToMs = [](const timeval& tv) { return 1e3 * tv.tv_sec + 1e-3 * tv.tv_usec; };
rusage usage = {};
CHECK(getrusage(RUSAGE_SELF, &usage) == 0) << Errno();
return ToMs(usage.ru_utime) + ToMs(usage.ru_stime);
}
double WallTimeMs() {
// An attempt to call clock_gettime on an ancient version of MacOS fails at runtime.
// It's possible to detect the presence of clock_gettime at runtime but I don't have
// an ancient MacOS to test the code. Hence this.
#ifdef __APPLE__
return std::numeric_limits<double>::quiet_NaN();
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return 1e3 * ts.tv_sec + 1e-6 * ts.tv_nsec;
#endif
}
} // namespace
void Timer::Start() {
cpu_ = CpuTimeMs();
wall_ = WallTimeMs();
}
void Timer::Report(const char* msg) {
double cpu = CpuTimeMs() - cpu_;
if (std::isnan(wall_)) {
LOG(INFO) << "Timing for: " << msg << ": " << cpu << "ms cpu";
} else {
double wall = WallTimeMs() - wall_;
LOG(INFO) << "Timing for: " << msg << ": " << cpu << "ms cpu, " << wall << "ms wall";
}
Start();
}
} // namespace gitstatus

36
gitstatus/src/timer.h Normal file
View File

@@ -0,0 +1,36 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_TIMER_H_
#define ROMKATV_GITSTATUS_TIMER_H_
namespace gitstatus {
class Timer {
public:
Timer() { Start(); }
void Start();
void Report(const char* msg);
private:
double cpu_;
double wall_;
};
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_TIMER_H_

27
gitstatus/src/tribool.h Normal file
View File

@@ -0,0 +1,27 @@
// Copyright 2019 Roman Perepelitsa.
//
// This file is part of GitStatus.
//
// GitStatus is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// GitStatus is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with GitStatus. If not, see <https://www.gnu.org/licenses/>.
#ifndef ROMKATV_GITSTATUS_TRIBOOL_H_
#define ROMKATV_GITSTATUS_TRIBOOL_H_
namespace gitstatus {
enum class Tribool : int { kFalse = 0, kTrue = 1, kUnknown = -1 };
} // namespace gitstatus
#endif // ROMKATV_GITSTATUS_TRIBOOL_H_

View File

View File

@@ -1,5 +1,7 @@
typeset -gr __p9k_wizard_columns=51
typeset -gr __p9k_wizard_lines=12
# Fewer than 47 columns will probably work. Haven't tried it.
typeset -gr __p9k_wizard_columns=47
# The bottleneck is ask_tails with nerd fonts. Everything else works fine with 12 lines.
typeset -gr __p9k_wizard_lines=14
typeset -gr __p9k_zd=${ZDOTDIR:-$HOME}
typeset -gr __p9k_zd_u=${${${(q)__p9k_zd}/#(#b)${(q)HOME}(|\/*)/'~'$match[1]}//\%/%%}
typeset -gr __p9k_zshrc=${${:-$__p9k_zd/.zshrc}:A}
@@ -19,10 +21,20 @@ function _p9k_can_configure() {
[[ -o multibyte ]] || { $0_error "multibyte option is not set"; return 1 }
[[ -e $__p9k_zd ]] || { $0_error "$__p9k_zd_u does not exist"; return 1 }
[[ -d $__p9k_zd ]] || { $0_error "$__p9k_zd_u is not a directory"; return 1 }
[[ -w $__p9k_zd ]] || { $0_error "$__p9k_zd_u is not writable"; return 1 }
[[ ! -d $__p9k_cfg_path ]] || { $0_error "$__p9k_cfg_path_u is a directory"; return 1 }
[[ ! -d $__p9k_zshrc ]] || { $0_error "$__p9k_zshrc_u is a directory"; return 1 }
local dir=${__p9k_cfg_path:h}
while [[ ! -e $dir && $dir != ${dir:h} ]]; do dir=${dir:h}; done
if [[ ! -d $dir ]]; then
$0_error "cannot create $__p9k_cfg_path_u because ${dir//\%/%%} is not a directory"
return 1
fi
if [[ ! -w $dir ]]; then
$0_error "cannot create $__p9k_cfg_path_u because ${dir//\%/%%} is readonly"
return 1
fi
[[ ! -e $__p9k_cfg_path || -f $__p9k_cfg_path || -h $__p9k_cfg_path ]] || {
$0_error "$__p9k_cfg_path_u is a special file"
return 1
@@ -44,7 +56,7 @@ function _p9k_can_configure() {
done
(( LINES >= __p9k_wizard_lines && COLUMNS >= __p9k_wizard_columns )) || {
$0_error "terminal size too small; must be at least $__p9k_wizard_columns x $__p9k_wizard_lines"
$0_error "terminal size too small; must be at least $__p9k_wizard_columns columns by $__p9k_wizard_lines lines"
return 1
}
[[ -t 0 && -t 1 ]] || { $0_error "no TTY"; return 2 }
@@ -59,11 +71,11 @@ function p9k_configure() {
_p9k_can_configure || return
(
set -- -f
source $__p9k_root_dir/internal/wizard.zsh
builtin source $__p9k_root_dir/internal/wizard.zsh
)
local ret=$?
case $ret in
0) source $__p9k_cfg_path; _p9k__force_must_init=1;;
0) builtin source $__p9k_cfg_path; _p9k__force_must_init=1;;
69) return 0;;
*) return $ret;;
esac

View File

@@ -1,6 +1,7 @@
typeset -gA icons
function _p9k_init_icons() {
[[ -n ${POWERLEVEL9K_MODE-} || ${langinfo[CODESET]} == (utf|UTF)(-|)8 ]] || local POWERLEVEL9K_MODE=ascii
[[ $_p9k__icon_mode == $POWERLEVEL9K_MODE/$POWERLEVEL9K_LEGACY_ICON_SPACING/$POWERLEVEL9K_ICON_PADDING ]] && return
typeset -g _p9k__icon_mode=$POWERLEVEL9K_MODE/$POWERLEVEL9K_LEGACY_ICON_SPACING/$POWERLEVEL9K_ICON_PADDING
@@ -67,6 +68,8 @@ function _p9k_init_icons() {
LINUX_SABAYON_ICON '\uE271'$s # 
LINUX_SLACKWARE_ICON '\uE271'$s # 
LINUX_VOID_ICON '\uE271'$s # 
LINUX_ARTIX_ICON '\uE271'$s # 
LINUX_RHEL_ICON '\uE271'$s # 
SUNOS_ICON '\U1F31E'$q # 🌞
HOME_ICON '\uE12C'$s # 
HOME_SUB_ICON '\uE18D'$s # 
@@ -130,6 +133,7 @@ function _p9k_init_icons() {
LUA_ICON 'lua'
PERL_ICON 'perl'
NNN_ICON 'nnn'
XPLR_ICON 'xplr'
TIMEWARRIOR_ICON 'tw'
TASKWARRIOR_ICON 'task'
NIX_SHELL_ICON 'nix'
@@ -140,6 +144,9 @@ function _p9k_init_icons() {
PHP_ICON 'php'
HASKELL_ICON 'hs'
PACKAGE_ICON 'pkg'
JULIA_ICON 'jl'
SCALA_ICON 'scala'
TOOLBOX_ICON '\u2B22' # ⬢
)
;;
'awesome-fontconfig')
@@ -196,6 +203,8 @@ function _p9k_init_icons() {
LINUX_SABAYON_ICON '\uF17C'$s # 
LINUX_SLACKWARE_ICON '\uF17C'$s # 
LINUX_VOID_ICON '\uF17C'$s # 
LINUX_ARTIX_ICON '\uF17C'$s # 
LINUX_RHEL_ICON '\uF17C'$s # 
SUNOS_ICON '\uF185 ' # 
HOME_ICON '\uF015'$s # 
HOME_SUB_ICON '\uF07C'$s # 
@@ -234,7 +243,7 @@ function _p9k_init_icons() {
NORDVPN_ICON '\UF023' # 
EXECUTION_TIME_ICON '\uF253'$s # 
SSH_ICON 'ssh'
VPN_ICON '\uF023'
VPN_ICON '\uF023'
KUBERNETES_ICON '\U2388' # ⎈
DROPBOX_ICON '\UF16B'$s # 
DATE_ICON '\uF073 ' # 
@@ -255,6 +264,7 @@ function _p9k_init_icons() {
LUA_ICON 'lua'
PERL_ICON 'perl'
NNN_ICON 'nnn'
XPLR_ICON 'xplr'
TIMEWARRIOR_ICON 'tw'
TASKWARRIOR_ICON 'task'
NIX_SHELL_ICON 'nix'
@@ -265,6 +275,9 @@ function _p9k_init_icons() {
PHP_ICON 'php'
HASKELL_ICON 'hs'
PACKAGE_ICON 'pkg'
JULIA_ICON 'jl'
SCALA_ICON 'scala'
TOOLBOX_ICON '\u2B22' # ⬢
)
;;
'awesome-mapped-fontconfig')
@@ -326,6 +339,8 @@ function _p9k_init_icons() {
LINUX_SABAYON_ICON "${CODEPOINT_OF_AWESOME_LINUX:+\\u$CODEPOINT_OF_AWESOME_LINUX$s}"
LINUX_SLACKWARE_ICON "${CODEPOINT_OF_AWESOME_LINUX:+\\u$CODEPOINT_OF_AWESOME_LINUX$s}"
LINUX_VOID_ICON "${CODEPOINT_OF_AWESOME_LINUX:+\\u$CODEPOINT_OF_AWESOME_LINUX$s}"
LINUX_ARTIX_ICON "${CODEPOINT_OF_AWESOME_LINUX:+\\u$CODEPOINT_OF_AWESOME_LINUX$s}"
LINUX_RHEL_ICON "${CODEPOINT_OF_AWESOME_LINUX:+\\u$CODEPOINT_OF_AWESOME_LINUX$s}"
SUNOS_ICON "${CODEPOINT_OF_AWESOME_SUN_O:+\\u$CODEPOINT_OF_AWESOME_SUN_O }"
HOME_ICON "${CODEPOINT_OF_AWESOME_HOME:+\\u$CODEPOINT_OF_AWESOME_HOME$s}"
HOME_SUB_ICON "${CODEPOINT_OF_AWESOME_FOLDER_OPEN:+\\u$CODEPOINT_OF_AWESOME_FOLDER_OPEN$s}"
@@ -383,6 +398,7 @@ function _p9k_init_icons() {
LUA_ICON 'lua'
PERL_ICON 'perl'
NNN_ICON 'nnn'
XPLR_ICON 'xplr'
TIMEWARRIOR_ICON 'tw'
TASKWARRIOR_ICON 'task'
NIX_SHELL_ICON 'nix'
@@ -393,6 +409,9 @@ function _p9k_init_icons() {
PHP_ICON 'php'
HASKELL_ICON 'hs'
PACKAGE_ICON 'pkg'
JULIA_ICON 'jl'
SCALA_ICON 'scala'
TOOLBOX_ICON '\u2B22' # ⬢
)
;;
'nerdfont-complete'|'nerdfont-fontconfig')
@@ -448,14 +467,16 @@ function _p9k_init_icons() {
LINUX_SABAYON_ICON '\uF317'$s # 
LINUX_SLACKWARE_ICON '\uF319'$s # 
LINUX_VOID_ICON '\uF17C' # 
LINUX_ARTIX_ICON '\uF17C' # 
LINUX_UBUNTU_ICON '\uF31b'$s # 
LINUX_RHEL_ICON '\uF316'$s # 
LINUX_ICON '\uF17C' # 
SUNOS_ICON '\uF185 ' # 
HOME_ICON '\uF015'$s # 
HOME_SUB_ICON '\uF07C'$s # 
FOLDER_ICON '\uF115'$s # 
ETC_ICON '\uF013'$s # 
NETWORK_ICON '\uFBF1'$s #
NETWORK_ICON '\uF50D'$s #
LOAD_ICON '\uF080 ' # 
SWAP_ICON '\uF464'$s # 
RAM_ICON '\uF0E4'$s # 
@@ -509,6 +530,7 @@ function _p9k_init_icons() {
LUA_ICON '\uE620' # 
PERL_ICON '\uE769' # 
NNN_ICON 'nnn'
XPLR_ICON 'xplr'
TIMEWARRIOR_ICON '\uF49B' # 
TASKWARRIOR_ICON '\uF4A0 ' # 
NIX_SHELL_ICON '\uF313 ' # 
@@ -519,6 +541,9 @@ function _p9k_init_icons() {
PHP_ICON '\uE608' # 
HASKELL_ICON '\uE61F' # 
PACKAGE_ICON '\uF8D6' # 
JULIA_ICON '\uE624' # 
SCALA_ICON '\uE737' # 
TOOLBOX_ICON '\uE20F'$s # 
)
;;
ascii)
@@ -573,6 +598,8 @@ function _p9k_init_icons() {
LINUX_SABAYON_ICON 'sabayon'
LINUX_SLACKWARE_ICON 'slack'
LINUX_VOID_ICON 'void'
LINUX_ARTIX_ICON 'artix'
LINUX_RHEL_ICON 'rhel'
SUNOS_ICON 'sunos'
HOME_ICON ''
HOME_SUB_ICON ''
@@ -632,6 +659,7 @@ function _p9k_init_icons() {
LUA_ICON 'lua'
PERL_ICON 'perl'
NNN_ICON 'nnn'
XPLR_ICON 'xplr'
TIMEWARRIOR_ICON 'tw'
TASKWARRIOR_ICON 'task'
NIX_SHELL_ICON 'nix'
@@ -642,6 +670,9 @@ function _p9k_init_icons() {
PHP_ICON 'php'
HASKELL_ICON 'hs'
PACKAGE_ICON 'pkg'
JULIA_ICON 'jl'
SCALA_ICON 'scala'
TOOLBOX_ICON 'toolbox'
)
;;
*)
@@ -664,7 +695,7 @@ function _p9k_init_icons() {
TEST_ICON ''
TODO_ICON '\u2206' # ∆
BATTERY_ICON '\U1F50B' # 🔋
DISK_ICON 'hdd'
DISK_ICON 'hdd'
OK_ICON '\u2714' # ✔
FAIL_ICON '\u2718' # ✘
SYMFONY_ICON 'SF'
@@ -698,6 +729,8 @@ function _p9k_init_icons() {
LINUX_SABAYON_ICON 'Sab'
LINUX_SLACKWARE_ICON 'Sla'
LINUX_VOID_ICON 'Vo'
LINUX_ARTIX_ICON 'Art'
LINUX_RHEL_ICON 'RH'
SUNOS_ICON 'Sun'
HOME_ICON ''
HOME_SUB_ICON ''
@@ -757,6 +790,7 @@ function _p9k_init_icons() {
LUA_ICON 'lua'
PERL_ICON 'perl'
NNN_ICON 'nnn'
XPLR_ICON 'xplr'
TIMEWARRIOR_ICON 'tw'
TASKWARRIOR_ICON 'task'
NIX_SHELL_ICON 'nix'
@@ -767,6 +801,9 @@ function _p9k_init_icons() {
PHP_ICON 'php'
HASKELL_ICON 'hs'
PACKAGE_ICON 'pkg'
JULIA_ICON 'jl'
SCALA_ICON 'scala'
TOOLBOX_ICON '\u2B22' # ⬢
)
;;
esac
@@ -791,6 +828,7 @@ function _p9k_init_icons() {
icons[LEFT_SEGMENT_END_SEPARATOR]+=' '
icons[MULTILINE_LAST_PROMPT_PREFIX]+=' '
icons[VCS_TAG_ICON]+=' '
icons[VCS_BOOKMARK_ICON]+=' '
icons[VCS_COMMIT_ICON]+=' '
icons[VCS_BRANCH_ICON]+=' '
icons[VCS_REMOTE_BRANCH_ICON]+=' '

View File

@@ -76,16 +76,6 @@ emulate zsh -o prompt_percent -c 'print -P "%F{#ff0000}red%F{green}%B bold green
---
bug: open a new tab and hit ctrl-p. an empty line will appear before prompt.
---
take a look at https://github.com/skywind3000/z.lua. it claims to have fzf support. would be nice
if alt-down showed two groups -- one for subdirs and another for directory history (sorted by
frequency of use? by last use? three sections? more key bindings?).
---
add `p10k explain` that prints something like this:
```text
@@ -163,7 +153,8 @@ Prompt connection should have matching options.
Add `POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_MIRROR_SEPARATOR`. If set, left segments get separated with
`POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR` followed by `POWERLEVEL9K_LEFT_SEGMENT_MIRROR_SEPARATOR`.
Each is drawn without background. The first with the foreground of left segment, the second with
the background of right segment. To insert space in between, embed it in one of these parameters.
the background of right segment. To insert space in between, embed it in
`POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_MIRROR_SEPARATOR`.
`POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR` is unused.
---
@@ -177,41 +168,6 @@ two filled separators similar to heads and tail. Figure out how to present this
---
Get rid of `saved_columns` in the wizard and always present all options as if horizontal space was
unlimited. `print_prompt` should print something like this if prompt is too wide:
Terminal window too narrow to display prompt.
Make it wider and press ENTER to refresh.
Terminal dimensions will need to be checked more often.
Try getting rid of the "press ENTER" requirement by trapping `SIGWINCH`. Might need to run `read -k`
with timeout in a loop.
Print all text with a helper function that keeps track of the number of lines remaining on screen.
`print_prompt` will then be able to show a similar message for Terminal window being too short. This
makes sense only for two-line prompts. This is probably OK.
If `print_prompt` can be told in advance how many prompts we are going to display, it might be able
to insert or avoid inserting `\n` in between, depending on terminal height. There is one screen
where one prompt is a one-liner while another is a two-liner. This is fine because there are only
two options. `print_prompt` can assume that all options will use the current prompt height.
Don't use `print_prompt` directly. Create `ask_prompt` and use it like this:
```zsh
ask_prompt \
1 "No frame" "left_frame=0 right_frame=0" \
2 "Left frame" "left_frame=1 right_frame=0" \
...
```
There are two prompt questions that don't fit this pattern: `ask_empty_line` and
`ask_transient_prompt`. The first is easy to adapt (`print_prompt` can print prompt twice if
`empty_line=1`) but the second will probably have to be hand-coded.
---
Optimize auto-wizard check.
```text
@@ -221,3 +177,21 @@ user=0.21s system=0.05s cpu=99% total=0.264
time ( repeat 1000 [[ -z "${parameters[(I)POWERLEVEL9K_*]}" ]] )
user=0.17s system=0.00s cpu=99% total=0.175
```
---
Add the equivalent of `P9K_PYTHON_VERSION` to all `*env` segments where it makes sense.
---
Define `P9K_ICON` on initialization. Fill it with `$icon`. Duplicate every key that ends in `_ICON`.
Respect `POWERLEVEL9K_VCS_STASH_ICON` overrides but not anything with segment name or state.
Define `POWERLEVEL9K_VCS_*` parameters in config templates for all symbols used in
`my_git_formatter`. Add missing entries to `icons`. Use `$P9K_ICON[...]` within `my_git_formatter`.
Add a screen to the wizard to choose between clear and circled icons.
---
Add a screen to the wizard asking whether to set `POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~'`.
Show it only if there is `$HOME/.git`. By default this parameter should be commented out.

File diff suppressed because it is too large Load Diff

View File

@@ -152,7 +152,7 @@ function _p9k_parse_buffer() {
local -r var="\$$id|\${$id}|\"\$$id\"|\"\${$id}\""
local -i e ic c=${2:-'1 << 62'}
local skip n s r state cmd prev
local skip n s r state token cmd prev
local -a aln alp alf v
if [[ -o interactive_comments ]]; then

247
internal/wizard.zsh Executable file → Normal file
View File

@@ -23,6 +23,11 @@ else
function restore_screen() {}
fi
local -i in_z4h_wizard=0
[[ $force == 0 && $+functions[z4h] == 1 && -n $Z4H && -e $Z4H/welcome ]] && in_z4h_wizard=1
local -i success=0
{ # always
local -ri force
@@ -105,20 +110,19 @@ local -ra rainbow_right=(
)
function prompt_length() {
local COLUMNS=1024
local -i COLUMNS=1024
local -i x y=$#1 m
if (( y )); then
while (( ${${(%):-$1%$y(l.1.0)}[-1]} )); do
x=y
(( y *= 2 ));
(( y *= 2 ))
done
local xy
while (( y > x + 1 )); do
m=$(( x + (y - x) / 2 ))
typeset ${${(%):-$1%$m(l.x.y)}[-1]}=$m
(( m = x + (y - x) / 2 ))
(( ${${(%):-$1%$m(l.x.y)}[-1]} = m ))
done
fi
REPLY=$x
typeset -g REPLY=$x
}
function print_prompt() {
@@ -234,7 +238,10 @@ function show_cursor() {
function consume_input() {
local key
while read -t0 key; do true; done
while true; do
[[ -t 2 ]]
read -t0 -k key || break
done 2>/dev/null
}
function quit() {
@@ -271,10 +278,14 @@ function quit() {
print -P " %2Fp10k%f %Bconfigure%b"
print -P ""
fi
function quit() {}
stty echo 2>/dev/null
show_cursor
exit 1
}
local screen_widgets=()
local -i max_priority
local -i prompt_idx
local choice
@@ -283,6 +294,7 @@ function add_widget() {
shift
local render="${(j: :)${(@q)*}}"
screen_widgets+=("$priority" "$render")
(( priority <= max_priority )) || max_priority=priority
}
function render_screen_pass() {
@@ -304,7 +316,6 @@ functions -M get_columns 0 0
function render_screen() {
{
hide_cursor
stty -echo 2>/dev/null
while true; do
while true; do
typeset -gi wizard_columns='get_columns()'
@@ -322,7 +333,7 @@ function render_screen() {
else
break
fi
while (( (COLUMNS > 88 ? 88 : COLUMNS) == wizard_columns && LINES == wizard_lines )); do
while (( get_columns() == wizard_columns && LINES == wizard_lines )); do
sleep 1
done
done
@@ -351,12 +362,11 @@ function render_screen() {
flowing -c %1FNot enough vertical space.%f
print
flowing Make terminal window %Btaller%b or press %BCtrl-C%b to abort Powerlevel10k configuration wizard.
while (( (COLUMNS > 88 ? 88 : COLUMNS) == wizard_columns && LINES == wizard_lines )); do
while (( get_columns() == wizard_columns && LINES == wizard_lines )); do
sleep 1
done
done
} always {
stty echo 2>/dev/null
show_cursor
}
}
@@ -385,6 +395,7 @@ function ask() {
local -i lines columns wizard_lines wizard_columns
add_widget 0 print -P "(q) Quit and do nothing."
add_widget 0 print
add_widget $((max_priority + 1))
add_widget 0 print -P "%BChoice [${choices}q]: %b"
while true; do
=true
@@ -395,11 +406,13 @@ function ask() {
fi
typeset -g choice=
if read -t1 -k choice; then
choice=${(L)choice}
if [[ $choice == q ]]; then
quit
fi
if [[ $choices == *$choice* ]]; then
screen_widgets=()
max_priority=0
prompt_idx=0
return
fi
@@ -411,7 +424,11 @@ local -i greeting_printed=0
function print_greeting() {
(( greeting_printed )) && return
if (( force )); then
if (( in_z4h_wizard )); then
flowing -c %3FZsh for Humans%f uses %4FPowerlevel10k%f to print command \
line prompt. This wizard will ask you a few questions and configure \
prompt for you.
elif (( force )); then
flowing -c This is %4FPowerlevel10k configuration wizard%f. \
It will ask you a few questions and configure your prompt.
else
@@ -497,41 +514,42 @@ function install_font() {
clear
case $terminal in
Termux)
mkdir -p ~/.termux || quit -c
command mkdir -p -- ~/.termux || quit -c
run_command "Downloading %BMesloLGS NF Regular.ttf%b" \
curl -fsSL -o ~/.termux/font.ttf "$font_base_url/MesloLGS%20NF%20Regular.ttf"
run_command "Reloading %BTermux%b settings" termux-reload-settings
;;
iTerm2)
mkdir -p ~/Library/Fonts || quit -c
command mkdir -p -- ~/Library/Fonts || quit -c
local style
for style in Regular Bold Italic 'Bold Italic'; do
local file="MesloLGS NF ${style}.ttf"
run_command "Downloading %B$file%b" \
curl -fsSL -o ~/Library/Fonts/$file.tmp "$font_base_url/${file// /%20}"
zf_mv -f -- ~/Library/Fonts/$file{.tmp,} || quit -c
command mv -f -- ~/Library/Fonts/$file{.tmp,} || quit -c
done
print -nP -- "Changing %BiTerm2%b settings ..."
local size=$iterm2_font_size
[[ $size == 12 ]] && size=13
local k t v settings=(
'"Normal Font"' string '"MesloLGS-NF-Regular '$size'"'
'"Terminal Type"' string '"xterm-256color"'
'"Horizontal Spacing"' real 1
'"Vertical Spacing"' real 1
'"Minimum Contrast"' real 0
'"Use Bold Font"' bool 1
'"Use Bright Bold"' bool 1
'"Use Italic Font"' bool 1
'"ASCII Anti Aliased"' bool 1
'"Non-ASCII Anti Aliased"' bool 1
'"Use Non-ASCII Font"' bool 0
'"Ambiguous Double Width"' bool 0
'"Draw Powerline Glyphs"' bool 1
'"Normal Font"' string '"MesloLGS-NF-Regular '$size'"'
'"Terminal Type"' string '"xterm-256color"'
'"Horizontal Spacing"' real 1
'"Vertical Spacing"' real 1
'"Minimum Contrast"' real 0
'"Use Bold Font"' bool 1
'"Use Bright Bold"' bool 1
'"Use Italic Font"' bool 1
'"ASCII Anti Aliased"' bool 1
'"Non-ASCII Anti Aliased"' bool 1
'"Use Non-ASCII Font"' bool 0
'"Ambiguous Double Width"' bool 0
'"Draw Powerline Glyphs"' bool 1
'"Only The Default BG Color Uses Transparency"' bool 1
)
for k t v in $settings; do
/usr/libexec/PlistBuddy -c "Set :\"New Bookmarks\":0:$k $v" \
~/Library/Preferences/com.googlecode.iterm2.plist && continue
~/Library/Preferences/com.googlecode.iterm2.plist 2>/dev/null && continue
run_command "" /usr/libexec/PlistBuddy -c \
"Add :\"New Bookmarks\":0:$k $t $v" ~/Library/Preferences/com.googlecode.iterm2.plist
done
@@ -541,28 +559,31 @@ function install_font() {
sleep 3
print -P " %2FOK%f"
sleep 1
restore_screen
clear
hide_cursor
print
flowing +c "%2FMeslo Nerd Font%f" successfully installed.
print -P ""
flowing +c Please "%Brestart iTerm2%b" for the changes to take effect.
print -P ""
while true; do
() {
local out
out=$(/usr/bin/defaults read 'Apple Global Domain' NSQuitAlwaysKeepsWindows 2>/dev/null) || return
[[ $out == 1 ]] || return
out="$(iterm_get OpenNoWindowsAtStartup 2>/dev/null)" || return
[[ $out == false ]]
}
if (( $? )); then
flowing +c Please "%Brestart iTerm2%b" for the changes to take effect.
print -P ""
flowing +c -i 5 " 1. Click" "%BiTerm2 → Quit iTerm2%b" or press "%B⌘ Q%b."
flowing +c -i 5 " 2. Open %BiTerm2%b."
print -P ""
local key=
read -k key${(%):-"?%BWill you restart iTerm2 before proceeding? [yN]: %b"} || quit -c
if [[ $key = (y|Y) ]]; then
print -P ""
print -P ""
exit 69
fi
print -P ""
print -P ""
flowing +c "It's" important to "%Brestart iTerm2%b" for the changes to take effect.
print -P ""
done
flowing +c "It's" important to "%Brestart iTerm2%b" by following the instructions above. \
"It's" "%Bnot enough%b" to close iTerm2 by clicking on the red circle. You must \
click "%BiTerm2 → Quit iTerm2%b" or press "%B⌘ Q%b."
else
flowing +c Please "%Brestart your computer%b" for the changes to take effect.
fi
while true; do sleep 60 2>/dev/null; done
;;
esac
@@ -661,13 +682,13 @@ function ask_diamond() {
add_widget 0 print
add_widget 0 flowing -c -- "---> \uE0B2\uE0B0 <---"
add_widget 0 print
add_widget 1
add_widget 3
add_widget 0 print -P "%B(y) Yes.%b"
add_widget 0 print
add_widget 1
add_widget 0 print -P "%B(n) No.%b"
add_widget 0 print
add_widget 1
add_widget 2
if (( can_install_font )); then
extra+=r
add_widget 0 print -P "(r) Restart from the beginning."
@@ -689,10 +710,13 @@ function ask_lock() {
add_widget 0 print
add_widget 0 flowing -c -- "---> $1 <---"
add_widget 0 print
add_widget 3
add_widget 0 print -P "%B(y) Yes.%b"
add_widget 0 print
add_widget 1
add_widget 0 print -P "%B(n) No.%b"
add_widget 0 print
add_widget 2
add_widget 0 print -P "(r) Restart from the beginning."
ask ynr
case $choice in
@@ -704,16 +728,19 @@ function ask_lock() {
}
function ask_python() {
add_widget 0 add_widget 0 flowing -c %BDoes this look like a "%b%2FPython logo%f%B?%b"
add_widget 0 add_widget 0 flowing -c reference: "$(href https://fontawesome.com/icons/python)"
add_widget 0 add_widget 0 print -P ""
add_widget 0 add_widget 0 flowing -c -- "---> \uE63C <---"
add_widget 0 add_widget 0 print -P ""
add_widget 0 add_widget 0 print -P "%B(y) Yes.%b"
add_widget 0 add_widget 0 print -P ""
add_widget 0 add_widget 0 print -P "%B(n) No.%b"
add_widget 0 add_widget 0 print -P ""
add_widget 0 add_widget 0 print -P "(r) Restart from the beginning."
add_widget 0 flowing -c %BDoes this look like a "%b%2FPython logo%f%B?%b"
add_widget 0 flowing -c reference: "$(href https://fontawesome.com/icons/python)"
add_widget 0 print -P ""
add_widget 0 flowing -c -- "---> \uE63C <---"
add_widget 0 print -P ""
add_widget 3
add_widget 0 print -P "%B(y) Yes.%b"
add_widget 0 print -P ""
add_widget 1
add_widget 0 print -P "%B(n) No.%b"
add_widget 0 print -P ""
add_widget 2
add_widget 0 print -P "(r) Restart from the beginning."
ask ynr
case $choice in
r) return 1;;
@@ -728,10 +755,13 @@ function ask_arrow() {
add_widget 0 print -P ""
add_widget 0 flowing -c -- "---> \u276F\u276E <---"
add_widget 0 print -P ""
add_widget 3
add_widget 0 print -P "%B(y) Yes.%b"
add_widget 0 print -P ""
add_widget 1
add_widget 0 print -P "%B(n) No.%b"
add_widget 0 print -P ""
add_widget 2
add_widget 0 print -P "(r) Restart from the beginning."
ask ynr
case $choice in
@@ -748,10 +778,13 @@ function ask_debian() {
add_widget 0 print -P ""
add_widget 0 flowing -c -- "---> \uF306 <---"
add_widget 0 print -P ""
add_widget 3
add_widget 0 print -P "%B(y) Yes.%b"
add_widget 0 print -P ""
add_widget 1
add_widget 0 print -P "%B(n) No.%b"
add_widget 0 print -P ""
add_widget 2
add_widget 0 print -P "(r) Restart from the beginning."
ask ynr
case $choice in
@@ -782,10 +815,13 @@ function ask_icon_padding() {
add_widget 0 print -P ""
add_widget 0 flowing -c -- "---> $text <---"
add_widget 0 print -P ""
add_widget 3
add_widget 0 flowing +c -i 5 "%B(y) Yes." Icons are very close to the crosses but there is "%b%2Fno overlap%f%B.%b"
add_widget 0 print -P ""
add_widget 1
add_widget 0 flowing +c -i 5 "%B(n) No." Some icons "%b%2Foverlap%f%B" neighbouring crosses.%b
add_widget 0 print -P ""
add_widget 2
add_widget 0 print -P "(r) Restart from the beginning."
ask ynr
case $choice in
@@ -954,7 +990,7 @@ function ask_color() {
r) return 1;;
[1-4]) color=$choice;;
esac
options+=${(L)color_name[color]}
options+=${${(L)color_name[color]}//ı/i}
return 0
}
@@ -981,7 +1017,7 @@ function ask_ornaments_color() {
r) return 1;;
[1-4]) color=$choice;;
esac
options+=${(L)color_name[color]}-ornaments
options+=${${(L)color_name[color]}//ı/i}-ornaments
return 0
}
@@ -1021,7 +1057,7 @@ function ask_use_rprompt() {
case $choice in
r) return 1;;
1) ;;
2) pure_use_rprompt=; options+=rpromt;;
2) pure_use_rprompt=; options+=rprompt;;
esac
return 0
}
@@ -1034,7 +1070,7 @@ function os_icon_name() {
case $uname in
SunOS) echo SUNOS_ICON;;
Darwin) echo APPLE_ICON;;
CYGWIN_NT-* | MSYS_NT-*) echo WINDOWS_ICON;;
CYGWIN_NT-*|MSYS_NT-*|MINGW64_NT-*|MINGW32_NT-*) echo WINDOWS_ICON;;
FreeBSD|OpenBSD|DragonFly) echo FREEBSD_ICON;;
Linux)
local os_release_id
@@ -1042,6 +1078,8 @@ function os_icon_name() {
local lines=(${(f)"$(</etc/os-release)"})
lines=(${(@M)lines:#ID=*})
(( $#lines == 1 )) && os_release_id=${lines[1]#ID=}
elif [[ -e /etc/artix-release ]]; then
os_release_id=artix
fi
case $os_release_id in
*arch*) echo LINUX_ARCH_ICON;;
@@ -1064,6 +1102,8 @@ function os_icon_name() {
*devuan*) echo LINUX_DEVUAN_ICON;;
*manjaro*) echo LINUX_MANJARO_ICON;;
*void*) echo LINUX_VOID_ICON;;
*artix*) echo LINUX_ARTIX_ICON;;
*rhel*) echo LINUX_RHEL_ICON;;
*) echo LINUX_ICON;;
esac
;;
@@ -1384,16 +1424,16 @@ function ask_empty_line() {
add_widget 0 print -P "%B(1) Compact.%b"
add_widget 0 print
add_widget 1
add_widget 0 print_prompt
add_widget 0 print_prompt
add_prompt_n
add_prompt_n
add_widget 0 print
add_widget 2
add_widget 0 print -P "%B(2) Sparse.%b"
add_widget 0 print
add_widget 1
add_widget 0 print_prompt
add_prompt_n
add_widget 0 print
add_widget 0 print_prompt
add_prompt_n
add_widget 0 print
add_widget 2
add_widget 0 print -P "(r) Restart from the beginning."
@@ -1416,34 +1456,37 @@ function print_instant_prompt_link() {
function ask_instant_prompt() {
if ! is-at-least 5.4; then
instant_prompt=off
options+=instant_prompt=auto-off
return 0
fi
if (( $+functions[z4h] )); then
instant_prompt=quiet
options+=instant_prompt=auto-quiet
return
fi
add_widget 0 flowing -c "%BInstant Prompt Mode%b"
add_widget 0 print_instant_prompt_link
add_widget 1
add_widget 0 print
add_widget 2
add_widget 0 flowing +c -i 5 "%B(1) Off.%b" Disable instant prompt. Choose this if you\'ve \
tried instant prompt and found it incompatible with your zsh configuration files.
add_widget 0 flowing +c -i 5 "%B(1) Verbose (recommended).%b"
add_widget 0 print
add_widget 1
add_widget 0 flowing +c -i 5 "%B(2) Quiet.%b" Enable instant prompt and %Bdon\'t print \
warnings%b when detecting console output during zsh initialization. Choose this if you\'ve \
read and understood instant prompt documentation.
add_widget 0 flowing +c -i 5 "%B(2) Quiet.%b" Choose this if you\'ve read and understood \
instant prompt documentation.
add_widget 0 print
add_widget 1
add_widget 0 flowing +c -i 5 "%B(3) Verbose.%b" Enable instant prompt and %Bprint a warning%b \
when detecting console output during zsh initialization. %BChoose this if you\'ve never tried \
instant prompt, haven\'t seen the warning, or if you are unsure what this all means%b.
add_widget 0 flowing +c -i 5 "%B(3) Off.%b" Choose this if you\'ve tried instant prompt \
and found it incompatible with your zsh configuration files.
add_widget 0 print
add_widget 2
add_widget 0 print -P "(r) Restart from the beginning."
ask 123r
case $choice in
r) return 1;;
1) instant_prompt=off; options+=instant_prompt=off;;
1) instant_prompt=verbose; options+=instant_prompt=verbose;;
2) instant_prompt=quiet; options+=instant_prompt=quiet;;
3) instant_prompt=verbose; options+=instant_prompt=verbose;;
3) instant_prompt=off; options+=instant_prompt=off;;
esac
return 0
}
@@ -1546,7 +1589,9 @@ function ask_zshrc_edit() {
local h7='$ZDOTDIR/.p10k.zsh'
local h8='"$ZDOTDIR/.p10k.zsh"'
local h9='"$ZDOTDIR"/.p10k.zsh'
if [[ -n ${(@M)lines:#(#b)[^#]#([^[:IDENT:]]|)source[[:space:]]##($f1|$f2|$f3|$f4|$g1|$h0|$h1|$h2|$h3|$h4|$h5|$h6|$h7|$h8|$h9)(|[[:space:]]*|'#'*)} ]]; then
local h10='$POWERLEVEL9K_CONFIG_FILE'
local h11='"$POWERLEVEL9K_CONFIG_FILE"'
if [[ -n ${(@M)lines:#(#b)[^#]#([^[:IDENT:]]|)source[[:space:]]##($f1|$f2|$f3|$f4|$g1|$h0|$h1|$h2|$h3|$h4|$h5|$h6|$h7|$h8|$h9|$h10|$h11)(|[[:space:]]*|'#'*)} ]]; then
zshrc_has_cfg=1
fi
local pre='${XDG_CACHE_HOME:-$HOME/.cache}/p10k-instant-prompt-${(%):-%n}.zsh'
@@ -1560,7 +1605,11 @@ function ask_zshrc_edit() {
add_widget 0 print -P ""
add_widget 1
local modifiable=y
if [[ -e $__p9k_zshrc && ! -w $__p9k_zshrc ]]; then
if [[ ! -w $__p9k_zd ]]; then
modifiable=
add_widget 0 flowing -c %3FWARNING:%f %2F${__p9k_zd_u//\\/\\\\}%f %3Fis readonly.%f
add_widget 0 print -P ""
elif [[ -e $__p9k_zshrc && ! -w $__p9k_zshrc ]]; then
local -a stat
zstat -A stat +uid -- $__p9k_zshrc || quit -c
if (( stat[1] == EUID )); then
@@ -1641,8 +1690,10 @@ function generate_config() {
fi
if [[ $POWERLEVEL9K_MODE == (compatible|powerline) ]]; then
uncomment 'typeset -g POWERLEVEL9K_DIR_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION'
sub DIR_NOT_WRITABLE_VISUAL_IDENTIFIER_EXPANSION "'∅'"
uncomment 'typeset -g POWERLEVEL9K_LOCK_ICON'
sub LOCK_ICON "'∅'"
uncomment 'typeset -g POWERLEVEL9K_NORDVPN_VISUAL_IDENTIFIER_EXPANSION'
sub NORDVPN_VISUAL_IDENTIFIER_EXPANSION "'nord'"
uncomment 'typeset -g POWERLEVEL9K_RANGER_VISUAL_IDENTIFIER_EXPANSION'
sub RANGER_VISUAL_IDENTIFIER_EXPANSION "'▲'"
uncomment 'typeset -g POWERLEVEL9K_KUBECONTEXT_DEFAULT_VISUAL_IDENTIFIER_EXPANSION'
@@ -1717,6 +1768,7 @@ function generate_config() {
uncomment 'typeset -g POWERLEVEL9K_CONTEXT_PREFIX'
uncomment 'typeset -g POWERLEVEL9K_KUBECONTEXT_PREFIX'
uncomment 'typeset -g POWERLEVEL9K_TIME_PREFIX'
uncomment 'typeset -g POWERLEVEL9K_TOOLBOX_PREFIX'
if [[ $style == (lean|classic) ]]; then
[[ $style == classic ]] && local fg="%$prefix_color[$color]F" || local fg="%f"
sub VCS_PREFIX "'${fg}on '"
@@ -1724,6 +1776,7 @@ function generate_config() {
sub CONTEXT_PREFIX "'${fg}with '"
sub KUBECONTEXT_PREFIX "'${fg}at '"
sub TIME_PREFIX "'${fg}at '"
sub TOOLBOX_PREFIX "'${fg}in '"
fi
fi
@@ -1843,6 +1896,8 @@ function generate_config() {
header+=$line
header+=$'.\n# Type `p10k configure` to generate another config.\n#'
command mkdir -p -- ${__p9k_cfg_path:h} || return
if [[ -e $__p9k_cfg_path ]]; then
unlink $__p9k_cfg_path || return
fi
@@ -1882,7 +1937,7 @@ fi" || return
[[ ! -f ${(%)__p9k_cfg_path_u} ]] || source ${(%)__p9k_cfg_path_u}" || return
fi
(( writable )) || chmod u-w -- $tmp || return
zf_mv -f -- $tmp $__p9k_zshrc || return
command mv -f -- $tmp $__p9k_zshrc || return
} always {
zf_rm -f -- $tmp
}
@@ -1911,6 +1966,8 @@ else
local -ir has_truecolor=0
fi
stty -echo 2>/dev/null
while true; do
local instant_prompt=verbose zshrc_content= zshrc_backup= zshrc_backup_u=
local -i zshrc_has_cfg=0 zshrc_has_instant_prompt=0 write_zshrc=0
@@ -1932,7 +1989,7 @@ while true; do
unset pure_use_rprompt
if [[ $langinfo[CODESET] == (utf|UTF)(-|)8 ]]; then
if [[ $TERM != (dumb|linux) && $langinfo[CODESET] == (utf|UTF)(-|)8 ]]; then
ask_font || continue
ask_diamond || continue
if [[ $AWESOME_GLYPHS_LOADED == 1 ]]; then
@@ -2032,24 +2089,32 @@ while true; do
done
restore_screen
print
flowing +c New config: "%B${__p9k_cfg_path_u//\\/\\\\}%b."
if [[ -n $config_backup ]]; then
flowing +c Backup of the old config: "%B${config_backup_u//\\/\\\\}%b."
fi
if [[ -n $zshrc_backup ]]; then
flowing +c Backup of "%B${__p9k_zshrc_u//\\/\\\\}%b:" "%B${zshrc_backup_u//\\/\\\\}%b."
if (( !in_z4h_wizard )); then
print
flowing +c New config: "%B${__p9k_cfg_path_u//\\/\\\\}%b."
if [[ -n $config_backup ]]; then
flowing +c Backup of the old config: "%B${config_backup_u//\\/\\\\}%b."
fi
if [[ -n $zshrc_backup ]]; then
flowing +c Backup of "%B${__p9k_zshrc_u//\\/\\\\}%b:" "%B${zshrc_backup_u//\\/\\\\}%b."
fi
fi
generate_config || return
change_zshrc || return
print -rP ""
flowing +c File feature requests and bug reports at "$(href https://github.com/romkatv/powerlevel10k/issues)"
print -rP ""
if (( !in_z4h_wizard )); then
print -rP ""
flowing +c File feature requests and bug reports at "$(href https://github.com/romkatv/powerlevel10k/issues)"
print -rP ""
fi
success=1
} always {
(( success )) || quit
consume_input
stty echo 2>/dev/null
show_cursor

View File

@@ -89,12 +89,15 @@ function _p9k_worker_invoke() {
}
function _p9k_worker_cleanup() {
eval "$__p9k_intro"
# __p9k_intro bugs out here in some cases for some reason.
emulate -L zsh
[[ $_p9k__worker_shell_pid == $sysparams[pid] ]] && _p9k_worker_stop
return 0
}
function _p9k_worker_stop() {
# See comments in _p9k_worker_cleanup.
emulate -L zsh
add-zsh-hook -D zshexit _p9k_worker_cleanup
[[ -n $_p9k__worker_resp_fd ]] && zle -F $_p9k__worker_resp_fd
[[ -n $_p9k__worker_resp_fd ]] && exec {_p9k__worker_resp_fd}>&-
@@ -181,12 +184,14 @@ function _p9k_worker_start() {
_p9k__worker_file_prefix=${TMPDIR:-/tmp}/p10k.worker.$EUID.$sysparams[pid].$EPOCHSECONDS
sysopen -r -o cloexec -u _p9k__worker_resp_fd <(
exec 0</dev/null
if [[ -n $_POWERLEVEL9K_WORKER_LOG_LEVEL ]]; then
exec 2>$_p9k__worker_file_prefix.log
setopt xtrace
else
exec 2>/dev/null
fi
builtin cd -q / || return
zmodload zsh/zselect || return
! { zselect -t0 || (( $? != 1 )) } || return
local _p9k_worker_pgid=$sysparams[pid]

View File

@@ -19,8 +19,11 @@
(( $+__p9k_root_dir )) || typeset -gr __p9k_root_dir=${POWERLEVEL9K_INSTALLATION_DIR:-${${(%):-%x}:A:h}}
(( $+__p9k_intro )) || {
# Note: leading spaces before `local` are important. Otherwise Antigen will remove `local` (!!!).
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
# Leading spaces before `local` are important. Otherwise Antigen will remove `local` (!!!).
# __p9k_trapint is to work around bugs in zsh: https://www.zsh.org/mla/workers/2020/msg00612.html.
# Likewise for `trap ":"` instead of the plain `trap ""`.
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
(( $+__p9k_trapped )) || { local -i __p9k_trapped; trap : INT; trap "trap ${(q)__p9k_trapint:--} INT" EXIT }
local -a match mbegin mend
local -i MBEGIN MEND OPTIND
local MATCH OPTARG IFS=$'\'' \t\n\0'\'
@@ -54,11 +57,11 @@ function _p9k_init_locale() {
if [[ $__p9k_dump_file != $__p9k_instant_prompt_dump_file ]] && (( ! $+functions[_p9k_preinit] )) && source $__p9k_dump_file 2>/dev/null && (( $+functions[_p9k_preinit] )); then
_p9k_preinit
fi
typeset -gr __p9k_sourced=10
typeset -gr __p9k_sourced=13
if [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]]; then
if [[ -w $__p9k_root_dir && -w $__p9k_root_dir/internal && -w $__p9k_root_dir/gitstatus ]]; then
local f
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}; do
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
[[ $f.zwc -nt $f ]] && continue
zmodload -F zsh/files b:zf_mv b:zf_rm
local tmp=$f.tmp.$$.zwc
@@ -71,7 +74,7 @@ function _p9k_init_locale() {
done
fi
fi
source $__p9k_root_dir/internal/p10k.zsh || true
builtin source $__p9k_root_dir/internal/p10k.zsh || true
}
(( $+__p9k_instant_prompt_active )) && unsetopt prompt_cr prompt_sp || setopt prompt_cr prompt_sp