1
0
mirror of https://gitlab.com/dbrw/ansible-setup.git synced 2025-08-03 21:55:41 +07:00

initial commit

This commit is contained in:
dbrw 2022-06-29 17:19:22 +07:00
commit 787bb4563f
181 changed files with 36597 additions and 0 deletions

211
README.md Normal file
View File

@ -0,0 +1,211 @@
- [Ansible Arch Linux Setup Project](#ansible-arch-linux-setup-project)
- [Quick Start](#quick-start)
- [Step 1: Clone this repository](#step-1-clone-this-repository)
- [Step 2: Install Ansible](#step-2-install-ansible)
- [Step 3: Update your system (optional)](#step-3-update-your-system-optional)
- [Step 4: Update the username variable (critical)](#step-4-update-the-username-variable-critical)
- [Step 5: Run the Playbook](#step-5-run-the-playbook)
- [Step 6: Reboot](#step-6-reboot)
- [What does it do for me?](#what-does-it-do-for-me)
- [Main Apps and Configs](#main-apps-and-configs)
- [zsh with powerline10k, lsd, and Oh My Zsh!](#zsh-with-powerline10k-lsd-and-oh-my-zsh)
- [Kitty (Terminal)](#kitty-terminal)
- [KDE Plasma](#kde-plasma)
- [SpaceVim](#spacevim)
- [LibreOffice (Fresh)](#libreoffice-fresh)
- [Others](#others)
- [What does it *not* do for me?](#what-does-it-not-do-for-me)
- [Customization](#customization)
- [1. Variables](#1-variables)
- [Dotfiles](#dotfiles)
- [2. YML Files](#2-yml-files)
- [WARNING](#warning)
- [FAQ](#faq)
- [Does this work with [insert distro here]?](#does-this-work-with-insert-distro-here)
- [Will this break my machine?](#will-this-break-my-machine)
- [It failed at some step, why?](#it-failed-at-some-step-why)
- [Can I safely rerun the playbook?](#can-i-safely-rerun-the-playbook)
- [My Plasma desktop doesn't look like the screenshot! Why?](#my-plasma-desktop-doesnt-look-like-the-screenshot-why)
- [I made it better, do you take pull requests?](#i-made-it-better-do-you-take-pull-requests)
- [I don't want KDE Plasma, can I skip it?](#i-dont-want-kde-plasma-can-i-skip-it)
- [I don't like Vivaldi, can I change it?](#i-dont-like-vivaldi-can-i-change-it)
- [How do I include my own dotfiles?](#how-do-i-include-my-own-dotfiles)
- [I have a question, how can I contact you?](#i-have-a-question-how-can-i-contact-you)
- [How can I support the project?](#how-can-i-support-the-project)
- [Where is the Code of Conduct?](#where-is-the-code-of-conduct)
# Ansible Arch Linux Setup Project
The Ansible Arch setup project aims to make your life easier when setting up a new environment with Arch Linux by automating a number of configurations and installations. Additionally, the project can assist in keeping your existing environment consistent and updated the way you like it!
You can run the playbook as often or as many times as you'd like to keep your desktop environment(s) sync'd and configured the way you want. Ansible is *idempotent*, so it's built for re-running without breaking anything.
![run1.png](images/run1.png)
*Pro Tip: if you want those lovely cows instead of the boring standard Ansible output, install cowsay before running the playbook*
![run2.png](images/run2.png)
## Quick Start
I highly recommend you continue reading the sections below before proceeding with the quick start, but if you just want to get up and running, below are the **minimum** steps required.
It's assumed the first time you run the playbook you're doing it on a fairly minimal Arch installation, though it's certainly not required. The project has been tested thoroughly on a minimal Manjaro XFCE installation as well as ArcoLinux, but should work fine on any Arch or Arch based distro.
### Step 1: Clone this repository
`git clone --recurse-submodules https://github.com/linuxpiper/ansible-arch-setup.git`
***NOTE: You must use --recurse-submodules for this to work***
### Step 2: Install Ansible
`sudo pacman -Sy ansible`
### Step 3: Update your system (optional)
`sudo pacman -Syu`
If you are running this project on a brand new, fresh Arch install, you can go ahead and update your system via pacman prior to running the playbook. You don't have to, but expect that the playbook will take a little longer when running for the first time since it will do the updates.
### Step 4: Update the username variable (critical)
Open up `roles/common/vars/main.yml` and change the `username` value to the appropriate user name for your system.
You absolutely should review all of the vars config file and modify it to your liking, however changing the `username` variable to match the username you are running this project for is the minimum you need to do.
If you want to use your own dotfiles, change the `dotfiles.url` value in the same config file to point to a git repo of your choosing.
### Step 5: Run the Playbook
While in the `roles` directory, execute:
`ansible-playbook -K main.yml`
The `-K` parameter is equivilent to `--ask-become-pass` and is used to collect your password for items requiring `become` permissions.
The playbook can take anywhere from 1 to 10 minutes or so depending on the speed of your machine and whether you chose to follow **Step 3**. It won't get stuck forever; be patient and wait for it to finish.
**Pro Tip:** *While you're waiting for the playbook to finish, think of all the things you **did not** have to do and praise yourself for using Linux. Maybe go grab a beer or something to celebrate your awesomeness.*
### Step 6: Reboot
Yay!
## What does it do for me?
A lot! We'll get to the specifics, but what's more important is that even if you haven't used Ansible, you can easily follow along in the config files (.yml files) and tailor it to your liking. You don't need to be a programmer, devops engineer, or rocket scientist...it's really easy to customize.
Any time you set up a new machine running Arch or any flavor of Arch you can simply run this playbook to get your new machine up and running the way you like it.
### Main Apps and Configs
#### zsh with powerline10k, lsd, and Oh My Zsh!
![zsh.png](images/zsh.png)
Level up your terminal with this zsh configuration! Informative powerline, beautiful theme colors, and incredibly productive. Also you look like a badass.
We also include a directory navigator you can use while typing at the command line.
![zsh2.png](images/zsh2.png)
#### Kitty (Terminal)
![kitty.png](images/kitty.png)
Included is the Kitty terminal which is a super fast, GPU accelerated terminal. It's preconfigured and optimized for zsh including zsh completions and colorization.
#### KDE Plasma
![kde.png](images/kde.png)
*desktop appearances will vary*
KDE Plasma is the most beautiful, productive desktop on the planet! A full KDE Plasma environment is set up and configured for you.
#### SpaceVim
![vim.png](images/vim.png)
Spacevim is the most productive, beautiful customization of vim that you will ever come across.
[Read more about SpaceVim](https://spacevim.org)
#### LibreOffice (Fresh)
![libre.jpeg](images/libre.jpeg)
LibreOffice is the de facto open source office suite. The latest and greatest is installed and kept fresh.
#### Others
No less important are all the other great apps that are deployed as part of the project, such as:
- VLC
- tmux
- Terminal browsers (elinks and w3m)
- Your choice of graphical web browser (Vivaldi (default), Firefox, Chromium, etc.)
- yay for aur support
- micro text editor
- and more!
## What does it *not* do for me?
This playbook will not currently configure display drivers, kernels, specific mirrors, user groups, or other core tasks that could potentially nuke your system. I may add this as an additional, separate role later on but for now I've taken a fairly conservative route.
## Customization
There are two key areas for customizing your deployment.
#### 1. Variables
The file `roles/common/vars/main.yml` contains all of the main configuration variables to quickly customize what to install and how.
![main.yml.png](images/main.yml.png)
Simply change any variable to False if you'd like to skip that part of installation.
##### Dotfiles
Your dotfiles can be pulled each time you run the ansible playbook by simply pointing the `dotfiles.url` key to a valid git URL.
Dotfiles are synced / managed using [rcm](https://github.com/thoughtbot/rcm) which comes from the AUR. If you disable AUR in the vars file (`use_aur`) you cannot pull your dotfiles without tweaking the `dotfiles.yml` file to exclude rcm.
#### 2. YML Files
Most yml files that customize app configuration can be found in `roles/common/tasks`. You can edit any existing yml file, or add your own and include it in the `main.yml` playbook.
## WARNING
*This project is in its early stages and is being refactored and optimized using Ansible and Linux best practices. Whilst it should be safe enough, it's up to you to review the playbook and packages being installed and configured before running it on your machine.*
## FAQ
#### Does this work with [insert distro here]?
Right now, only Arch Linux and Arch Linux derivatives are supported.
#### Will this break my machine?
It shouldn't, it will most likely make it awesome.
#### It failed at some step, why?
You could be missing a dependency or maybe you already have another version of the same package installed (e.g. micro-bin instead of micro-git). You can comment out that item from the yml file and re-run the playbook.
Feel free to open an issue on Github if you need a hand.
#### Can I safely rerun the playbook?
As often as you'd like. Just keep in mind that if you removed some packages after previously running the playbook, they will be reinstalled.
#### My Plasma desktop doesn't look like the screenshot! Why?
KDE Plasma has a million different config files spread out all over the place, and while I am considering adding a role to make the final plasma desktop look like mine, it's a huge amount of work to do it properly.
It's actually easier to just customize it how you want - that's one of the biggest benefits of KDE Plasma.
#### I made it better, do you take pull requests?
Please and thank you! :)
#### I don't want KDE Plasma, can I skip it?
Sure - just set `install_kde` to `False` in the `/roles/common/vars/main.yml` file.
#### I don't like Vivaldi, can I change it?
You bet - open up `/roles/common/vars/main.yml` and change `browser: vivaldi` to one of the following values: `firefox`, `brave`, or `chromium`.
#### How do I include my own dotfiles?
It's so easy! Open up `/roles/common/vars/main.yml` and look for the `dotfiles:` section. Change the `url` to point to your own git repository. It doesn't have to be on Github, just publicly accessible.
#### I have a question, how can I contact you?
The best place to ask questions is to just open an issue in the Github repo. However you can email me at linuxpiper@gmail.com
#### How can I support the project?
This project is useless without all of the great free and open software it installs and configures. Donate to one of them!
#### Where is the Code of Conduct?
We don't do that stuff here. Be a grown up.

BIN
images/kde.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

BIN
images/kitty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

BIN
images/libre.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

BIN
images/main.yml.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
images/run1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
images/run2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

BIN
images/vim.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

BIN
images/zsh.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

BIN
images/zsh2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

2
roles/ansible.cfg Normal file
View File

@ -0,0 +1,2 @@
[defaults]
library = ./library/ansible-aur

View File

@ -0,0 +1,18 @@
---
# Custom vim plugins
custom_vim_plugins: |
[[custom_plugins]]
name = 'hashivim/vim-terraform'
merged = 0
[[custom_plugins]]
name = 'hashivim/vim-vagrant'
merged = 0
[[custom_plugins]]
name = 'puppetlabs/puppet-syntax-vim'
merged = 0
[[custom_plugins]]
name = 'fatih/vim-go'
merged = 0

View File

@ -0,0 +1,10 @@
Section "InputClass"
Identifier "touchpad"
Driver "libinput"
MatchIsTouchpad "on"
Option "Tapping" "on"
Option "NaturalScrolling" "on"
Option "HorizontalScrolling" "on"
Option "TappingButtonMap" "lmr"
Option "ScrollMethod" "twofinger"
EndSection

View File

@ -0,0 +1 @@
vm.swappiness=5

View File

@ -0,0 +1,14 @@
TIMING:
TAP_MILLISEC: 200
DOUBLE_TAP_MILLISEC: 150
MAPPINGS:
- KEY: KEY_RIGHTALT
TAP: KEY_BACKSPACE
HOLD: KEY_RIGHTMETA
- KEY: KEY_LEFTSHIFT
TAP: [ KEY_LEFTSHIFT, KEY_MINUS , ]
HOLD: KEY_LEFTSHIFT
- KEY: KEY_RIGHTSHIFT
TAP: [ KEY_RIGHTSHIFT, KEY_EQUAL , ]
HOLD: KEY_RIGHTSHIFT

View File

@ -0,0 +1,249 @@
/* See LICENSE file for copyright and license details. */
#include <X11/XF86keysym.h>
/* appearance */
static unsigned int borderpx = 2; /* border pixel of windows */
static unsigned int snap = 32; /* snap pixel */
static unsigned int systraypinning = 0; /* 0: sloppy systray follows selected monitor, >0: pin systray to monitor X */
static unsigned int systrayspacing = 2; /* systray spacing */
static int systraypinningfailfirst = 1; /* 1: if pinning fails, display systray on the first monitor, False: display systray on the last monitor*/
static int showsystray = 1; /* 0 means no systray */
static unsigned int gappih = 10; /* horiz inner gap between windows */
static unsigned int gappiv = 10; /* vert inner gap between windows */
static unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */
static unsigned int gappov = 10; /* vert outer gap between windows and screen edge */
static int smartgaps = 1; /* 1 means no outer gap when there is only one window */
static int showbar = 1; /* 0 means no bar */
static int topbar = 1; /* 0 means bottom bar */
static char *fonts[] = { "FantasqueSansMono Nerd Font:style=Regular:size=11" };
static char dmenufont[] = "monospace:size=10";
static char normbgcolor[] = "#fbf1c7";
static char normbordercolor[] = "#d5c4a1";
static char normfgcolor[] = "#3c3836";
static char selfgcolor[] = "#fbf1c7";
static char selbordercolor[] = "#b16286";
static char selbgcolor[] = "#8f3f71";
static char statusbgcolor[] = "#ebdbb2";
static char statusfgcolor[] = "#7c6f64";
static char *colors[][3] = {
/* fg bg border */
[SchemeNorm] = { normfgcolor, normbgcolor, normbordercolor },
[SchemeSel] = { selfgcolor, selbgcolor, selbordercolor },
[SchemeStatus] = { statusfgcolor, statusbgcolor, normbordercolor }, // Statusbar right {text,background,not used but cannot be empty}
[SchemeTagsSel] = { selfgcolor, selbgcolor, selbordercolor }, // Tagbar left selected {text,background,not used but cannot be empty}
[SchemeTagsNorm] = { normfgcolor, normbgcolor, normbordercolor }, // Tagbar left unselected {text,background,not used but cannot be empty}
[SchemeInfoSel] = { selbgcolor, normbgcolor, selbordercolor }, // infobar middle selected {text,background,not used but cannot be empty}
[SchemeInfoNorm] = { normfgcolor, normbgcolor, normbordercolor }, // infobar middle unselected {text,background,not used but cannot be empty}
};
/* tagging */
// static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; // for non glyph tag name
/* file editor term image video record misc misc browser */
static const char *tags[] = { "\ue5ff", "\uf44f", "\uf120", "\uf7e8", "\uf03d", "\uf86b", "\uf8b5", "\uf8b8", "\ufa9e" }; // nerd fot based glyph
static const char *tagsalt[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
static const Rule rules[] = {
/* xprop(1):
* WM_CLASS(STRING) = instance, class
* WM_NAME(STRING) = title
*/
/* class instance title tags mask isfloating monitor */
{ "Gimp", NULL, NULL, 1 << 3, 0, -1 },
{ "zoom ", NULL, NULL, 1 << 4, 1, -1 },
{ "firefox", NULL, NULL, 1 << 8, 0, -1 },
{ "st", NULL, NULL, 1 << 2, 0, -1 },
{ "kitty", NULL, NULL, 1 << 2, 0, -1 },
{ "Emacs", NULL, NULL, 1 << 1, 0, -1 },
{ "Pcmanfm", NULL, NULL, 1, 0, -1 },
{ "Xephyr", NULL, NULL, 0, 1, -1 },
{ "Chromium", NULL, "Zotero Item Selector", 0, 1, -1 },
{ "Pavucontrol", NULL, NULL, 0, 1, -1 },
{ "easyeffects", "easyeffects", NULL, 0, 1, -1 },
{ "Zotero", "Toplevel", NULL, 0, 1, -1 },
{ "NoiseTorch", NULL, NULL, 0, 1, -1 },
{ "Lxappearance", "lxappearance", NULL, 0, 1, -1 },
};
/* layout(s) */
static float mfact = 0.65; /* factor of master area size [0.05..0.95] */
static int nmaster = 1; /* number of clients in master area */
static int resizehints = 0; /* 1 means respect size hints in tiled resizals */
static int decorhints = 1; /* 1 means respect decoration hints */
#define FORCE_VSPLIT 1 /* nrowgrid layout: force two clients to always split vertically */
#include "vanitygaps.c"
static const Layout layouts[] = {
/* symbol arrange function */
{ ":::", gaplessgrid },
{ "[]=", tile }, /* first entry is default */
{ "[M]", monocle },
{ "[@]", spiral },
{ "[\\]", dwindle },
{ "H[]", deck },
{ "TTT", bstack },
{ "===", bstackhoriz },
{ "HHH", grid },
{ "###", nrowgrid },
{ "---", horizgrid },
{ "|M|", centeredmaster },
{ ">M>", centeredfloatingmaster },
{ "><>", NULL }, /* no layout function means floating behavior */
{ NULL, NULL },
};
/* key definitions */
#define MODKEY Mod4Mask
#define TAGKEYS(KEY,TAG) \
{ MODKEY, KEY, view, {.ui = 1 << TAG} }, \
{ MODKEY|ControlMask, KEY, toggleview, {.ui = 1 << TAG} }, \
{ MODKEY|ShiftMask, KEY, tag, {.ui = 1 << TAG} }, \
{ MODKEY|ControlMask|ShiftMask, KEY, toggletag, {.ui = 1 << TAG} },
#define HOLDKEY XK_Super_L // replace 0 with the keysym to activate holdbar, XK_Super_L is left super key
/* helper for spawning shell commands in the pre dwm-5.0 fashion */
#define SHCMD(cmd) { .v = (const char*[]){ "/bin/sh", "-c", cmd, NULL } }
/* commands */
static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */
static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", normbgcolor, "-nf", normfgcolor, "-sb", selbordercolor, "-sf", selfgcolor, NULL };
static const char *launchercmd[] = { "rofi", "-show", "combi", NULL };
static const char *termcmd[] = { "kitty", NULL };
static const char *browsercmd[] = { "firefox", NULL, NULL, NULL, "Firefox" };
static const char *filemancmd[] = { "pcmanfm", NULL, NULL, NULL, "Pcmanfm" };
static const char *lockcmd[] = { "lockscreen", NULL};
static const char *volinccmd[] = { "vol", "up"};
static const char *voldeccmd[] = { "vol", "down"};
static const char *volmutecmd[] = { "vol", "mute"};
static const char *briinccmd[] = { "brightness", "up"};
static const char *brideccmd[] = { "brightness", "down"};
#include "focusurgent.c"
/*
* Xresources preferences to load at startup
*/
ResourcePref resources[] = {
{ "normbgcolor", STRING, &normbgcolor },
{ "normbordercolor", STRING, &normbordercolor },
{ "normfgcolor", STRING, &normfgcolor },
{ "statusfgcolor", STRING, &statusfgcolor },
{ "statusbgcolor", STRING, &statusbgcolor },
{ "selbgcolor", STRING, &selbgcolor },
{ "selbordercolor", STRING, &selbordercolor },
{ "selfgcolor", STRING, &selfgcolor },
{ "borderpx", INTEGER, &borderpx },
{ "snap", INTEGER, &snap },
{ "showbar", INTEGER, &showbar },
{ "topbar", INTEGER, &topbar },
{ "nmaster", INTEGER, &nmaster },
{ "resizehints", INTEGER, &resizehints },
{ "mfact", FLOAT, &mfact },
};
static Key keys[] = {
/* modifier key function argument */
{ Mod1Mask, XK_space, spawn, {.v = launchercmd } },
{ MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } },
{ Mod1Mask, XK_Up, spawn, {.v = volinccmd } },
{ Mod1Mask, XK_Down, spawn, {.v = voldeccmd } },
{ 0, XF86XK_AudioRaiseVolume, spawn, {.v = volinccmd } },
{ 0, XF86XK_AudioLowerVolume, spawn, {.v = voldeccmd } },
{ 0, XF86XK_MonBrightnessUp, spawn, {.v = briinccmd } },
{ 0, XF86XK_MonBrightnessDown, spawn, {.v = brideccmd } },
{ Mod1Mask, XK_m, spawn, {.v = volmutecmd } },
{ MODKEY, XK_v, runorraise, {.v = browsercmd } },
{ MODKEY, XK_e, runorraise, {.v = filemancmd } },
{ MODKEY, XK_b, togglebar, {0} },
{ MODKEY, XK_j, focusstack, {.i = +1 } },
{ MODKEY, XK_k, focusstack, {.i = -1 } },
{ MODKEY, XK_i, incnmaster, {.i = +1 } },
{ MODKEY, XK_d, incnmaster, {.i = -1 } },
{ MODKEY, XK_h, setmfact, {.f = -0.05} },
{ MODKEY, XK_l, setmfact, {.f = +0.05} },
{ MODKEY, XK_Return, zoom, {0} },
{ MODKEY|Mod1Mask, XK_u, incrgaps, {.i = +1 } },
{ MODKEY|Mod1Mask|ShiftMask, XK_u, incrgaps, {.i = -1 } },
{ MODKEY|Mod1Mask, XK_i, incrigaps, {.i = +1 } },
{ MODKEY|Mod1Mask|ShiftMask, XK_i, incrigaps, {.i = -1 } },
{ MODKEY|Mod1Mask, XK_o, incrogaps, {.i = +1 } },
{ MODKEY|Mod1Mask|ShiftMask, XK_o, incrogaps, {.i = -1 } },
{ MODKEY|Mod1Mask, XK_6, incrihgaps, {.i = +1 } },
{ MODKEY|Mod1Mask|ShiftMask, XK_6, incrihgaps, {.i = -1 } },
{ MODKEY|Mod1Mask, XK_7, incrivgaps, {.i = +1 } },
{ MODKEY|Mod1Mask|ShiftMask, XK_7, incrivgaps, {.i = -1 } },
{ MODKEY|Mod1Mask, XK_8, incrohgaps, {.i = +1 } },
{ MODKEY|Mod1Mask|ShiftMask, XK_8, incrohgaps, {.i = -1 } },
{ MODKEY|Mod1Mask, XK_9, incrovgaps, {.i = +1 } },
{ MODKEY|Mod1Mask|ShiftMask, XK_9, incrovgaps, {.i = -1 } },
{ MODKEY|Mod1Mask, XK_0, togglegaps, {0} },
{ MODKEY|Mod1Mask|ShiftMask, XK_0, defaultgaps, {0} },
{ MODKEY, XK_Tab, view, {0} },
{ MODKEY|ShiftMask, XK_c, killclient, {0} },
{ MODKEY, XK_t, setlayout, {.v = &layouts[0]} },
{ MODKEY, XK_f, setlayout, {.v = &layouts[1]} },
{ MODKEY, XK_m, setlayout, {.v = &layouts[2]} },
{ MODKEY|ControlMask, XK_comma, cyclelayout, {.i = -1 } },
{ MODKEY|ControlMask, XK_period, cyclelayout, {.i = +1 } },
{ MODKEY, XK_space, setlayout, {0} },
{ MODKEY|ShiftMask, XK_space, togglefloating, {0} },
{ MODKEY, XK_Down, moveresize, {.v = "0x 25y 0w 0h" } },
{ MODKEY, XK_Up, moveresize, {.v = "0x -25y 0w 0h" } },
{ MODKEY, XK_Right, moveresize, {.v = "25x 0y 0w 0h" } },
{ MODKEY, XK_Left, moveresize, {.v = "-25x 0y 0w 0h" } },
{ MODKEY|ShiftMask, XK_Down, moveresize, {.v = "0x 0y 0w 25h" } },
{ MODKEY|ShiftMask, XK_Up, moveresize, {.v = "0x 0y 0w -25h" } },
{ MODKEY|ShiftMask, XK_Right, moveresize, {.v = "0x 0y 25w 0h" } },
{ MODKEY|ShiftMask, XK_Left, moveresize, {.v = "0x 0y -25w 0h" } },
{ MODKEY|ControlMask, XK_Up, moveresizeedge, {.v = "t"} },
{ MODKEY|ControlMask, XK_Down, moveresizeedge, {.v = "b"} },
{ MODKEY|ControlMask, XK_Left, moveresizeedge, {.v = "l"} },
{ MODKEY|ControlMask, XK_Right, moveresizeedge, {.v = "r"} },
{ MODKEY|ControlMask|ShiftMask, XK_Up, moveresizeedge, {.v = "T"} },
{ MODKEY|ControlMask|ShiftMask, XK_Down, moveresizeedge, {.v = "B"} },
{ MODKEY|ControlMask|ShiftMask, XK_Left, moveresizeedge, {.v = "L"} },
{ MODKEY|ControlMask|ShiftMask, XK_Right, moveresizeedge, {.v = "R"} },
{ MODKEY, XK_0, view, {.ui = ~0 } },
{ MODKEY|ShiftMask, XK_0, tag, {.ui = ~0 } },
{ MODKEY|ShiftMask, XK_j, pushdown, {0} },
{ MODKEY|ShiftMask, XK_k, pushup, {0} },
{ MODKEY, XK_comma, focusmon, {.i = -1 } },
{ MODKEY, XK_period, focusmon, {.i = +1 } },
{ MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } },
{ MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } },
{ MODKEY, XK_n, togglealttag, {0} },
{ MODKEY|ShiftMask, XK_q, quit, {1} },
{ MODKEY, XK_u, focusurgent, {0} },
{ 0, HOLDKEY, holdbar, {0} },
{ MODKEY|ControlMask|ShiftMask, XK_q, quit, {0} },
{ MODKEY, XK_q, spawn, {.v = lockcmd} },
TAGKEYS( XK_1, 0)
TAGKEYS( XK_2, 1)
TAGKEYS( XK_3, 2)
TAGKEYS( XK_4, 3)
TAGKEYS( XK_5, 4)
TAGKEYS( XK_6, 5)
TAGKEYS( XK_7, 6)
TAGKEYS( XK_8, 7)
TAGKEYS( XK_9, 8)
};
/* button definitions */
/* click can be ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, or ClkRootWin */
static Button buttons[] = {
/* click event mask button function argument */
{ ClkLtSymbol, 0, Button1, setlayout, {0} },
{ ClkLtSymbol, 0, Button3, setlayout, {.v = &layouts[2]} },
{ ClkWinTitle, 0, Button2, zoom, {0} },
{ ClkStatusText, 0, Button2, spawn, {.v = termcmd } },
{ ClkClientWin, MODKEY, Button1, movemouse, {0} },
{ ClkClientWin, MODKEY, Button2, togglefloating, {0} },
{ ClkClientWin, MODKEY, Button3, resizemouse, {0} },
{ ClkTagBar, 0, Button1, view, {0} },
{ ClkTagBar, 0, Button3, toggleview, {0} },
{ ClkTagBar, MODKEY, Button1, tag, {0} },
{ ClkTagBar, MODKEY, Button3, toggletag, {0} },
};
/* quitting */
static const int EMPTY_WINDOW_COUNT = 5;

View File

@ -0,0 +1,2 @@
[General]
EnableNetworkConfiguration=True

View File

@ -0,0 +1,7 @@
polkit.addRule(function(action, subject) {
if ((action.id == "org.freedesktop.udisks2.filesystem-mount-system" &&
subject.local && subject.active && subject.isInGroup("storage")))
{
return polkit.Result.YES;
}
});

View File

@ -0,0 +1,5 @@
---
- JOB: "intercept -g $DEVNODE | caps2esc | dual-function-keys -c /etc/interception/dual-function-keys/default-mapping.yaml | uinput -d $DEVNODE"
DEVICE:
EVENTS:
EV_KEY: [KEY_CAPSLOCK, KEY_SPACE, KEY_RIGHTALT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT]

View File

@ -0,0 +1,2 @@
#include ".Xresources.d/xft"
#include ".Xresources.d/dwm"

View File

@ -0,0 +1,15 @@
!dwm ---------------------------------------------------------------------------
dwm.normbgcolor: #44475a
dwm.normfgcolor: #bd93f9
dwm.normbordercolor: #44475a
dwm.selbgcolor: #6272a4
dwm.selfgcolor: #ebdbb2
dwm.selbordercolor: #6272a4
dwm.statusbgcolor: #44475a
dwm.statusfgcolor: #bd93f9
dwm.borderpx: 6
dwm.showbar: 1

View File

@ -0,0 +1,7 @@
Xft.antialias: true
Xft.autohint: false
Xft.embeddedbitmap: false
Xft.hinting: true
Xft.hintstyle: hintslight
Xft.lcdfilter: lcddefault
Xft.rgba: rgb

View File

@ -0,0 +1,28 @@
output DP1
off
output DP2
off
output DP2-2
off
output HDMI1
off
output HDMI2
off
output VIRTUAL1
off
output DP2-1
crtc 1
mode 1920x1080
pos 0x0
rate 60.00
output DP2-3
crtc 2
mode 1366x768
pos 1920x0
rate 60.00
output eDP1
crtc 0
mode 1366x768
pos 1920x768
primary
rate 59.99

View File

@ -0,0 +1,3 @@
DP2-1 00ffffffffffff000d083000010000000f1e0103807341780acf74a3574cb02309484c21080081804540614095000101010101010101023a801871382d40582c4500c48e2100001e662150b051001b3040703600c48e2100001e000000fc00434854560a2020202020202020000000fd00324b1e5017000a2020202020200110020325f24d010304050790121314169f2022260907070917008301000067030c002000b82d8c0ad08a20e02d10103e9600c48e210000188c0ad090204031200c405500c48e21000019011d00bc52d01e20b8285540c48e2100001f011d80d0721c1620102c2580c48e2100009f00000000000000000000000000000000000091
DP2-3 00ffffffffffff001e6d8f3e9df202000517010368221378ea00259e5752992915505423080031404540614081c00101010101010101201c5686500020300e38130058c21000001e000000fd00383f1e3508000a202020202020000000fc003136454e33330a202020202020000000ff00333035494e4b4835503138310a000a
eDP1 00ffffffffffff0026cfe3040000000000160103801c10780a123091565392281e5054000000010101010101010101010101010101010d1e56c25000363030203c00159c10000019000000000000000000000000000000000000000000fe00496e666f566973696f6e0a2020000000fe004d3132354e574e31205230200a005c

View File

@ -0,0 +1,34 @@
output DP1
off
output DP2
off
output DP2-2
off
output DP2-3
off
output HDMI1
off
output HDMI2
off
output VIRTUAL1
off
output DP2-1
crtc 1
mode 1600x900
pos 1366x0
rate 60.00
x-prop-audio auto
x-prop-broadcast_rgb Automatic
x-prop-max_bpc 12
x-prop-non_desktop 0
output eDP1
crtc 0
mode 1366x768
pos 0x132
primary
rate 59.99
x-prop-broadcast_rgb Automatic
x-prop-colorspace Default
x-prop-max_bpc 12
x-prop-non_desktop 0
x-prop-scaling_mode Full aspect

View File

@ -0,0 +1,2 @@
DP2-1 00ffffffffffff0022f02932010101011a190104a52c19782275d5a4544f9e26115054210800a9c081c0950081008180010101010101302a40c86084643018501300bbf91000001e000000fd00323c184611000a202020202020000000fc00485020503230320a2020202020000000ff00434e43353236304c46520a202000f4
eDP1 00ffffffffffff0026cfe3040000000000160103801c10780a123091565392281e5054000000010101010101010101010101010101010d1e56c25000363030203c00159c10000019000000000000000000000000000000000000000000fe00496e666f566973696f6e0a2020000000fe004d3132354e574e31205230200a005c

View File

@ -0,0 +1,25 @@
output DP1
off
output DP2
off
output DP2-1
off
output DP2-2
off
output HDMI1
off
output HDMI2
off
output VIRTUAL1
off
output DP2-3
crtc 2
mode 1366x768
pos 0x0
rate 60.00
output eDP1
crtc 0
mode 1366x768
pos 0x768
primary
rate 59.99

View File

@ -0,0 +1,2 @@
DP2-3 00ffffffffffff001e6d8f3e9df202000517010368221378ea00259e5752992915505423080031404540614081c00101010101010101201c5686500020300e38130058c21000001e000000fd00383f1e3508000a202020202020000000fc003136454e33330a202020202020000000ff00333035494e4b4835503138310a000a
eDP1 00ffffffffffff0026cfe3040000000000160103801c10780a123091565392281e5054000000010101010101010101010101010101010d1e56c25000363030203c00159c10000019000000000000000000000000000000000000000000fe00496e666f566973696f6e0a2020000000fe004d3132354e574e31205230200a005c

View File

@ -0,0 +1,19 @@
output DP1
off
output HDMI1
off
output HDMI2
off
output VIRTUAL1
off
output DP2
crtc 1
mode 1600x900
pos 0x0
rate 60.00
output eDP1
crtc 0
mode 1366x768
pos 117x900
primary
rate 59.99

View File

@ -0,0 +1,2 @@
#!/usr/bin/bash
~/.fehbg

View File

@ -0,0 +1,2 @@
DP2 00ffffffffffff0022f02a32010101011a190103682c19782a75d5a4544f9e26115054210800a9c081c0950081008180010101010101302a40c86084643018501300bbf91000001e000000fd00323c184611000a202020202020000000fc00485020503230320a2020202020000000ff00434e43353236304c46520a20200029
eDP1 00ffffffffffff0026cfe3040000000000160103801c10780a123091565392281e5054000000010101010101010101010101010101010d1e56c25000363030203c00159c10000019000000000000000000000000000000000000000000fe00496e666f566973696f6e0a2020000000fe004d3132354e574e31205230200a005c

View File

@ -0,0 +1,16 @@
output DP1
off
output DP2
off
output HDMI1
off
output HDMI2
off
output VIRTUAL1
off
output eDP1
crtc 0
mode 1366x768
pos 0x0
primary
rate 59.99

View File

@ -0,0 +1 @@
eDP1 00ffffffffffff0026cfe3040000000000160103801c10780a123091565392281e5054000000010101010101010101010101010101010d1e56c25000363030203c00159c10000019000000000000000000000000000000000000000000fe00496e666f566973696f6e0a2020000000fe004d3132354e574e31205230200a005c

View File

@ -0,0 +1,22 @@
output DP1
off
output HDMI2
off
output VIRTUAL1
off
output DP2
crtc 1
mode 1366x768
pos 0x0
rate 60.00
output HDMI1
crtc 2
mode 1360x768
pos 1366x0
rate 60.02
output eDP1
crtc 0
mode 1366x768
pos 0x768
primary
rate 59.99

View File

@ -0,0 +1,3 @@
DP2 00ffffffffffff001e6d8f3e9df202000517010368221378ea00259e5752992915505423080031404540614081c00101010101010101201c5686500020300e38130058c21000001e000000fd00383f1e3508000a202020202020000000fc003136454e33330a202020202020000000ff00333035494e4b4835503138310a000a
HDMI1 00ffffffffffff000d083000010000000f1e0103807341780acf74a3574cb02309484c21080081804540614095000101010101010101023a801871382d40582c4500c48e2100001e662150b051001b3040703600c48e2100001e000000fc00434854560a2020202020202020000000fd00324b1e5017000a2020202020200110020325f24d010304050790121314169f2022260907070917008301000067030c002000b82d8c0ad08a20e02d10103e9600c48e210000188c0ad090204031200c405500c48e21000019011d00bc52d01e20b8285540c48e2100001f011d80d0721c1620102c2580c48e2100009f00000000000000000000000000000000000091
eDP1 00ffffffffffff0026cfe3040000000000160103801c10780a123091565392281e5054000000010101010101010101010101010101010d1e56c25000363030203c00159c10000019000000000000000000000000000000000000000000fe00496e666f566973696f6e0a2020000000fe004d3132354e574e31205230200a005c

View File

@ -0,0 +1,19 @@
output DP1
off
output HDMI1
off
output HDMI2
off
output VIRTUAL1
off
output DP2
crtc 1
mode 1366x768
pos 0x0
rate 60.00
output eDP1
crtc 0
mode 1366x768
pos 0x768
primary
rate 59.99

View File

@ -0,0 +1,2 @@
DP2 00ffffffffffff001e6d8f3e9df202000517010368221378ea00259e5752992915505423080031404540614081c00101010101010101201c5686500020300e38130058c21000001e000000fd00383f1e3508000a202020202020000000fc003136454e33330a202020202020000000ff00333035494e4b4835503138310a000a
eDP1 00ffffffffffff0026cfe3040000000000160103801c10780a123091565392281e5054000000010101010101010101010101010101010d1e56c25000363030203c00159c10000019000000000000000000000000000000000000000000fe00496e666f566973696f6e0a2020000000fe004d3132354e574e31205230200a005c

View File

@ -0,0 +1,437 @@
# See dunst(5) for all configuration options
[global]
### Display ###
# Which monitor should the notifications be displayed on.
monitor = 0
# Display notification on focused monitor. Possible modes are:
# mouse: follow mouse pointer
# keyboard: follow window with keyboard focus
# none: don't follow anything
#
# "keyboard" needs a window manager that exports the
# _NET_ACTIVE_WINDOW property.
# This should be the case for almost all modern window managers.
#
# If this option is set to mouse or keyboard, the monitor option
# will be ignored.
follow = none
### Geometry ###
# dynamic width from 0 to 300
# width = (0, 300)
# constant width of 300
width = 300
# The maximum height of a single notification, excluding the frame.
height = 300
# Position the notification in the top right corner
origin = top-right
# Offset from the origin
offset = 20x30
# Scale factor. It is auto-detected if value is 0.
scale = 0
# Maximum number of notification (0 means no limit)
notification_limit = 0
### Progress bar ###
# Turn on the progess bar. It appears when a progress hint is passed with
# for example dunstify -h int:value:12
progress_bar = true
# Set the progress bar height. This includes the frame, so make sure
# it's at least twice as big as the frame width.
progress_bar_height = 10
# Set the frame width of the progress bar
progress_bar_frame_width = 0
# Set the minimum width for the progress bar
progress_bar_min_width = 150
# Set the maximum width for the progress bar
progress_bar_max_width = 300
# Show how many messages are currently hidden (because of
# notification_limit).
indicate_hidden = yes
# The transparency of the window. Range: [0; 100].
# This option will only work if a compositing window manager is
# present (e.g. xcompmgr, compiz, etc.). (X11 only)
transparency = 0
# Draw a line of "separator_height" pixel height between two
# notifications.
# Set to 0 to disable.
separator_height = 2
# Padding between text and separator.
padding = 8
# Horizontal padding.
horizontal_padding = 8
# Padding between text and icon.
text_icon_padding = 0
# Defines width in pixels of frame around the notification window.
# Set to 0 to disable.
frame_width = 1
# Defines color of the frame around the notification window.
frame_color = "#93a1a1"
# Define a color for the separator.
# possible values are:
# * auto: dunst tries to find a color fitting to the background;
# * foreground: use the same color as the foreground;
# * frame: use the same color as the frame;
# * anything else will be interpreted as a X color.
separator_color = auto
# Sort messages by urgency.
sort = yes
# Don't remove messages, if the user is idle (no mouse or keyboard input)
# for longer than idle_threshold seconds.
# Set to 0 to disable.
# A client can set the 'transient' hint to bypass this. See the rules
# section for how to disable this if necessary
idle_threshold = 120
### Text ###
font = DejaVu Sans Book 9
# The spacing between lines. If the height is smaller than the
# font height, it will get raised to the font height.
line_height = 0
# Possible values are:
# full: Allow a small subset of html markup in notifications:
# <b>bold</b>
# <i>italic</i>
# <s>strikethrough</s>
# <u>underline</u>
#
# For a complete reference see
# <https://docs.gtk.org/Pango/pango_markup.html>.
#
# strip: This setting is provided for compatibility with some broken
# clients that send markup even though it's not enabled on the
# server. Dunst will try to strip the markup but the parsing is
# simplistic so using this option outside of matching rules for
# specific applications *IS GREATLY DISCOURAGED*.
#
# no: Disable markup parsing, incoming notifications will be treated as
# plain text. Dunst will not advertise that it has the body-markup
# capability if this is set as a global setting.
#
# It's important to note that markup inside the format option will be parsed
# regardless of what this is set to.
markup = full
# The format of the message. Possible variables are:
# %a appname
# %s summary
# %b body
# %i iconname (including its path)
# %I iconname (without its path)
# %p progress value if set ([ 0%] to [100%]) or nothing
# %n progress value if set without any extra characters
# %% Literal %
# Markup is allowed
format = "<b>%s</b>\n%b"
# Alignment of message text.
# Possible values are "left", "center" and "right".
alignment = left
# Vertical alignment of message text and icon.
# Possible values are "top", "center" and "bottom".
vertical_alignment = center
# Show age of message if message is older than show_age_threshold
# seconds.
# Set to -1 to disable.
show_age_threshold = 60
# Specify where to make an ellipsis in long lines.
# Possible values are "start", "middle" and "end".
ellipsize = middle
# Ignore newlines '\n' in notifications.
ignore_newline = no
# Stack together notifications with the same content
stack_duplicates = true
# Hide the count of stacked notifications with the same content
hide_duplicate_count = false
# Display indicators for URLs (U) and actions (A).
show_indicators = yes
### Icons ###
# Align icons left/right/top/off
icon_position = off
# Scale small icons up to this size, set to 0 to disable. Helpful
# for e.g. small files or high-dpi screens. In case of conflict,
# max_icon_size takes precedence over this.
min_icon_size = 0
# Scale larger icons down to this size, set to 0 to disable
max_icon_size = 32
# Paths to default icons.
icon_path = /usr/share/icons/gnome/16x16/status/:/usr/share/icons/gnome/16x16/devices/
### History ###
# Should a notification popped up from history be sticky or timeout
# as if it would normally do.
sticky_history = yes
# Maximum amount of notifications kept in history
history_length = 20
### Misc/Advanced ###
# dmenu path.
dmenu = /usr/bin/dmenu -p dunst:
# Browser for opening urls in context menu.
browser = /usr/bin/xdg-open
# Always run rule-defined scripts, even if the notification is suppressed
always_run_script = true
# Define the title of the windows spawned by dunst
title = Dunst
# Define the class of the windows spawned by dunst
class = Dunst
# Define the corner radius of the notification window
# in pixel size. If the radius is 0, you have no rounded
# corners.
# The radius will be automatically lowered if it exceeds half of the
# notification height to avoid clipping text and/or icons.
corner_radius = 0
# Ignore the dbus closeNotification message.
# Useful to enforce the timeout set by dunst configuration. Without this
# parameter, an application may close the notification sent before the
# user defined timeout.
ignore_dbusclose = false
### Wayland ###
# These settings are Wayland-specific. They have no effect when using X11
# Uncomment this if you want to let notications appear under fullscreen
# applications (default: overlay)
# layer = top
# Set this to true to use X11 output on Wayland.
force_xwayland = false
### Legacy
# Use the Xinerama extension instead of RandR for multi-monitor support.
# This setting is provided for compatibility with older nVidia drivers that
# do not support RandR and using it on systems that support RandR is highly
# discouraged.
#
# By enabling this setting dunst will not be able to detect when a monitor
# is connected or disconnected which might break follow mode if the screen
# layout changes.
force_xinerama = false
### mouse
# Defines list of actions for each mouse event
# Possible values are:
# * none: Don't do anything.
# * do_action: Invoke the action determined by the action_name rule. If there is no
# such action, open the context menu.
# * open_url: If the notification has exactly one url, open it. If there are multiple
# ones, open the context menu.
# * close_current: Close current notification.
# * close_all: Close all notifications.
# * context: Open context menu for the notification.
# * context_all: Open context menu for all notifications.
# These values can be strung together for each mouse event, and
# will be executed in sequence.
mouse_left_click = close_current
mouse_middle_click = do_action, close_current
mouse_right_click = close_all
# Experimental features that may or may not work correctly. Do not expect them
# to have a consistent behaviour across releases.
[experimental]
# Calculate the dpi to use on a per-monitor basis.
# If this setting is enabled the Xft.dpi value will be ignored and instead
# dunst will attempt to calculate an appropriate dpi value for each monitor
# using the resolution and physical size. This might be useful in setups
# where there are multiple screens with very different dpi values.
per_monitor_dpi = false
[urgency_low]
# IMPORTANT: colors have to be defined in quotation marks.
# Otherwise the "#" and following would be interpreted as a comment.
background = "#586e75"
foreground = "#93a1a1"
timeout = 10
# Icon for notifications with low urgency, uncomment to enable
#default_icon = /path/to/icon
[urgency_normal]
background = "#073642"
foreground = "#93a1a1"
timeout = 10
# Icon for notifications with normal urgency, uncomment to enable
#default_icon = /path/to/icon
[urgency_critical]
background = "#dc322f"
foreground = "#eee8d5"
timeout = 0
# Icon for notifications with critical urgency, uncomment to enable
#default_icon = /path/to/icon
# Every section that isn't one of the above is interpreted as a rules to
# override settings for certain messages.
#
# Messages can be matched by
# appname (discouraged, see desktop_entry)
# body
# category
# desktop_entry
# icon
# match_transient
# msg_urgency
# stack_tag
# summary
#
# and you can override the
# background
# foreground
# format
# frame_color
# fullscreen
# new_icon
# set_stack_tag
# set_transient
# set_category
# timeout
# urgency
# icon_position
# skip_display
# history_ignore
# action_name
# word_wrap
# ellipsize
# alignment
# hide_text
#
# Shell-like globbing will get expanded.
#
# Instead of the appname filter, it's recommended to use the desktop_entry filter.
# GLib based applications export their desktop-entry name. In comparison to the appname,
# the desktop-entry won't get localized.
#
# SCRIPTING
# You can specify a script that gets run when the rule matches by
# setting the "script" option.
# The script will be called as follows:
# script appname summary body icon urgency
# where urgency can be "LOW", "NORMAL" or "CRITICAL".
#
# NOTE: It might be helpful to run dunst -print in a terminal in order
# to find fitting options for rules.
# Disable the transient hint so that idle_threshold cannot be bypassed from the
# client
#[transient_disable]
# match_transient = yes
# set_transient = no
#
# Make the handling of transient notifications more strict by making them not
# be placed in history.
#[transient_history_ignore]
# match_transient = yes
# history_ignore = yes
# fullscreen values
# show: show the notifications, regardless if there is a fullscreen window opened
# delay: displays the new notification, if there is no fullscreen window active
# If the notification is already drawn, it won't get undrawn.
# pushback: same as delay, but when switching into fullscreen, the notification will get
# withdrawn from screen again and will get delayed like a new notification
#[fullscreen_delay_everything]
# fullscreen = delay
#[fullscreen_show_critical]
# msg_urgency = critical
# fullscreen = show
#[espeak]
# summary = "*"
# script = dunst_espeak.sh
#[script-test]
# summary = "*script*"
# script = dunst_test.sh
#[ignore]
# # This notification will not be displayed
# summary = "foobar"
# skip_display = true
#[history-ignore]
# # This notification will not be saved in history
# summary = "foobar"
# history_ignore = yes
#[skip-display]
# # This notification will not be displayed, but will be included in the history
# summary = "foobar"
# skip_display = yes
#[signed_on]
# appname = Pidgin
# summary = "*signed on*"
# urgency = low
#
#[signed_off]
# appname = Pidgin
# summary = *signed off*
# urgency = low
#
#[says]
# appname = Pidgin
# summary = *says*
# urgency = critical
#
#[twitter]
# appname = Pidgin
# summary = *twitter.com*
# urgency = normal
#
#[stack-volumes]
# appname = "some_volume_notifiers"
# set_stack_tag = "volume"
#
# vim: ft=cfg

View File

@ -0,0 +1,328 @@
[global]
### Display ###
# Which monitor should the notifications be displayed on.
monitor = 0
# Display notification on focused monitor. Possible modes are:
# mouse: follow mouse pointer
# keyboard: follow window with keyboard focus
# none: don't follow anything
#
# "keyboard" needs a window manager that exports the
# _NET_ACTIVE_WINDOW property.
# This should be the case for almost all modern window managers.
#
# If this option is set to mouse or keyboard, the monitor option
# will be ignored.
follow = mouse
# The geometry of the window:
# [{width}]x{height}[+/-{x}+/-{y}]
# The geometry of the message window.
# The height is measured in number of notifications everything else
# in pixels. If the width is omitted but the height is given
# ("-geometry x2"), the message window expands over the whole screen
# (dmenu-like). If width is 0, the window expands to the longest
# message displayed. A positive x is measured from the left, a
# negative from the right side of the screen. Y is measured from
# the top and down respectively.
# The width can be negative. In this case the actual width is the
# screen width minus the width defined in within the geometry option.
# geometry = "300x5-30+20"
geometry = "310x1-20+30"
# Show how many messages are currently hidden (because of geometry).
indicate_hidden = yes
# Shrink window if it's smaller than the width. Will be ignored if
# width is 0.
shrink = yes
# The transparency of the window. Range: [0; 100].
# This option will only work if a compositing window manager is
# present (e.g. xcompmgr, compiz, etc.).
transparency = 0
# The height of the entire notification. If the height is smaller
# than the font height and padding combined, it will be raised
# to the font height and padding.
notification_height = 0
# Draw a line of "separator_height" pixel height between two
# notifications.
# Set to 0 to disable.
separator_height = 2
# Padding between text and separator.
padding = 8
# Horizontal padding.
horizontal_padding = 8
# Defines width in pixels of frame around the notification window.
# Set to 0 to disable.
frame_width = 3
# Defines color of the frame around the notification window.
frame_color = "#373b41"
# Define a color for the separator.
# possible values are:
# * auto: dunst tries to find a color fitting to the background;
# * foreground: use the same color as the foreground;
# * frame: use the same color as the frame;
# * anything else will be interpreted as a X color.
separator_color = frame
# Sort messages by urgency.
sort = yes
# Don't remove messages, if the user is idle (no mouse or keyboard input)
# for longer than idle_threshold seconds.
# Set to 0 to disable.
# Transient notifications ignore this setting.
idle_threshold = 120
### Text ###
font = Hack Nerd Font 8
# The spacing between lines. If the height is smaller than the
# font height, it will get raised to the font height.
line_height = 0
# Possible values are:
# full: Allow a small subset of html markup in notifications:
# <b>bold</b>
# <i>italic</i>
# <s>strikethrough</s>
# <u>underline</u>
#
# For a complete reference see
# <http://developer.gnome.org/pango/stable/PangoMarkupFormat.html>.
#
# strip: This setting is provided for compatibility with some broken
# clients that send markup even though it's not enabled on the
# server. Dunst will try to strip the markup but the parsing is
# simplistic so using this option outside of matching rules for
# specific applications *IS GREATLY DISCOURAGED*.
#
# no: Disable markup parsing, incoming notifications will be treated as
# plain text. Dunst will not advertise that it has the body-markup
# capability if this is set as a global setting.
#
# It's important to note that markup inside the format option will be parsed
# regardless of what this is set to.
markup = full
# The format of the message. Possible variables are:
# %a appname
# %s summary
# %b body
# %i iconname (including its path)
# %I iconname (without its path)
# %p progress value if set ([ 0%] to [100%]) or nothing
# %n progress value if set without any extra characters
# %% Literal %
# Markup is allowed
# format = "<b>%s</b>\n%b"
format = "%a: <b>%s</b> %b %p"
# Alignment of message text.
# Possible values are "left", "center" and "right".
alignment = left
# Show age of message if message is older than show_age_threshold
# seconds.
# Set to -1 to disable.
show_age_threshold = 60
# Split notifications into multiple lines if they don't fit into
# geometry.
word_wrap = yes
# When word_wrap is set to no, specify where to ellipsize long lines.
# Possible values are "start", "middle" and "end".
ellipsize = middle
# Ignore newlines '\n' in notifications.
ignore_newline = yes
# Merge multiple notifications with the same content
stack_duplicates = true
# Hide the count of merged notifications with the same content
hide_duplicate_count = false
# Display indicators for URLs (U) and actions (A).
show_indicators = yes
### Icons ###
# Align icons left/right/off
icon_position = left
# Scale larger icons down to this size, set to 0 to disable
max_icon_size = 16
# Paths to default icons.
icon_path = /usr/share/icons/Numix/16/status/:/usr/share/icons/Numix/16/devices/
### History ###
# Should a notification popped up from history be sticky or timeout
# as if it would normally do.
sticky_history = yes
# Maximum amount of notifications kept in history
history_length = 20
### Misc/Advanced ###
# dmenu path.
dmenu = /usr/bin/dmenu -p dunst:
# Browser for opening urls in context menu.
browser = /usr/bin/firefox -new-tab
# Always run rule-defined scripts, even if the notification is suppressed
always_run_script = true
# Define the title of the windows spawned by dunst
title = Dunst
# Define the class of the windows spawned by dunst
class = Dunst
# Print a notification on startup.
# This is mainly for error detection, since dbus (re-)starts dunst
# automatically after a crash.
startup_notification = false
### Legacy
# Use the Xinerama extension instead of RandR for multi-monitor support.
# This setting is provided for compatibility with older nVidia drivers that
# do not support RandR and using it on systems that support RandR is highly
# discouraged.
#
# By enabling this setting dunst will not be able to detect when a monitor
# is connected or disconnected which might break follow mode if the screen
# layout changes.
force_xinerama = false
# Experimental features that may or may not work correctly. Do not expect them
# to have a consistent behaviour across releases.
[experimental]
# Calculate the dpi to use on a per-monitor basis.
# If this setting is enabled the Xft.dpi value will be ignored and instead
# dunst will attempt to calculate an appropriate dpi value for each monitor
# using the resolution and physical size. This might be useful in setups
# where there are multiple screens with very different dpi values.
per_monitor_dpi = false
[shortcuts]
# Shortcuts are specified as [modifier+][modifier+]...key
# Available modifiers are "ctrl", "mod1" (the alt-key), "mod2",
# "mod3" and "mod4" (windows-key).
# Xev might be helpful to find names for keys.
# Close notification.
close = ctrl+space
# Close all notifications.
close_all = ctrl+shift+space
# Redisplay last message(s).
# On the US keyboard layout "grave" is normally above TAB and left
# of "1". Make sure this key actually exists on your keyboard layout,
# e.g. check output of 'xmodmap -pke'
history = ctrl+grave
# Context menu.
context = ctrl+shift+period
[urgency_low]
# IMPORTANT: colors have to be defined in quotation marks.
# Otherwise the "#" and following would be interpreted as a comment.
background = "#282a2e"
foreground = "#c5c8c6"
timeout = 10
# Icon for notifications with low urgency, uncomment to enable
#icon = /path/to/icon
[urgency_normal]
background = "#282a2e"
foreground = "#c5c8c6"
timeout = 10
# Icon for notifications with normal urgency, uncomment to enable
#icon = /path/to/icon
[urgency_critical]
background = "#282a2e"
foreground = "#c5c8c6"
frame_color = "#a54242"
timeout = 0
# Icon for notifications with critical urgency, uncomment to enable
#icon = /path/to/icon
# Every section that isn't one of the above is interpreted as a rules to
# override settings for certain messages.
# Messages can be matched by "appname", "summary", "body", "icon", "category",
# "msg_urgency" and you can override the "timeout", "urgency", "foreground",
# "background", "new_icon" and "format".
# Shell-like globbing will get expanded.
#
# SCRIPTING
# You can specify a script that gets run when the rule matches by
# setting the "script" option.
# The script will be called as follows:
# script appname summary body icon urgency
# where urgency can be "LOW", "NORMAL" or "CRITICAL".
#
# NOTE: if you don't want a notification to be displayed, set the format
# to "".
# NOTE: It might be helpful to run dunst -print in a terminal in order
# to find fitting options for rules.
#[espeak]
# summary = "*"
# script = dunst_espeak.sh
#[script-test]
# summary = "*script*"
# script = dunst_test.sh
#[ignore]
# # This notification will not be displayed
# summary = "foobar"
# format = ""
#[history-ignore]
# # This notification will not be saved in history
# summary = "foobar"
# history_ignore = yes
#[signed_on]
# appname = Pidgin
# summary = "*signed on*"
# urgency = low
#
#[signed_off]
# appname = Pidgin
# summary = *signed off*
# urgency = low
#
#[says]
# appname = Pidgin
# summary = *says*
# urgency = critical
#
#[twitter]
# appname = Pidgin
# summary = *twitter.com*
# urgency = normal
#
# vim: ft=cfg

View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<match target="font">
<edit name="embeddedbitmap" mode="assign">
<bool>false</bool>
</edit>
</match>
</fontconfig>

View File

@ -0,0 +1,9 @@
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
<fontconfig>
<match target="font">
<edit name="lcdfilter" mode="assign">
<const>lcddefault</const>
</edit>
</match>
</fontconfig>

View File

@ -0,0 +1,13 @@
file:///home/dim/Documents Documents
file:///home/dim/Downloads Downloads
file:///home/dim/Pictures Pictures
file:///home/dim/Videos Videos
file:///home/dim/Codes Codes
file:///home/dim/Documents/works/bahan-ajar-s1 bahan-ajar-s1
file:///home/dim/Documents/works/smk/desain-media-interaktif dmi
file:///home/dim/Documents/works works
file:///home/dim/Documents/works/lepisi/media-akreditasi media-akreditasi
file:///home/dim/Documents/kuliah kuliah
file:///home/dim/Documents/works/waf/divisi-it divisi-it
file:///media media
file:///home/dim/Documents/works/utpas utpas

View File

@ -0,0 +1,15 @@
[Settings]
gtk-theme-name=Arc
gtk-icon-theme-name=Arc
gtk-font-name=Droid Sans 10
gtk-cursor-theme-name=capitaine-cursors-light
gtk-cursor-theme-size=0
gtk-toolbar-style=GTK_TOOLBAR_BOTH
gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
gtk-button-images=1
gtk-menu-images=1
gtk-enable-event-sounds=1
gtk-enable-input-feedback-sounds=1
gtk-xft-antialias=1
gtk-xft-hinting=1
gtk-xft-hintstyle=hintfull

View File

@ -0,0 +1,150 @@
configuration {
modi: "window,ssh,combi,drun,run";
font: "hack 10";
location: 0;
/* yoffset: 0;*/
/* xoffset: 0;*/
/* fixed-num-lines: true;*/
show-icons: false;
/* terminal: "rofi-sensible-terminal";*/
/* ssh-client: "ssh";*/
/* ssh-command: "{terminal} -e {ssh-client} {host} [-p {port}]";*/
/* run-command: "{cmd}";*/
/* run-list-command: "";*/
/* run-shell-command: "{terminal} -e {cmd}";*/
/* window-command: "wmctrl -i -R {window}";*/
/* window-match-fields: "all";*/
icon-theme: "Mojave-CT";
/* drun-match-fields: "name,generic,exec,categories,keywords";*/
/* drun-categories: ;*/
/* drun-show-actions: false;*/
/* drun-display-format: "{name} [<span weight='light' size='small'><i>({generic})</i></span>]";*/
/* drun-url-launcher: "xdg-open";*/
disable-history: false;
/* ignored-prefixes: "";*/
/* sort: false;*/
/* sorting-method: "normal";*/
/* case-sensitive: false;*/
/* cycle: true;*/
/* sidebar-mode: false;*/
/* hover-select: false;*/
/* eh: 1;*/
/* auto-select: false;*/
/* parse-hosts: false;*/
/* parse-known-hosts: true;*/
combi-modi: "window,drun,run,ssh";
/* matching: "normal";*/
/* tokenize: true;*/
/* m: "-5";*/
/* filter: ;*/
/* dpi: -1;*/
/* threads: 0;*/
/* scroll-method: 0;*/
/* window-format: "{w} {c} {t}";*/
/* click-to-exit: true;*/
max-history-size: 12;
/* combi-hide-mode-prefix: false;*/
/* matching-negate-char: '-' /* unsupported */;*/
/* cache-dir: ;*/
/* window-thumbnail: false;*/
/* drun-use-desktop-cache: false;*/
/* drun-reload-desktop-cache: false;*/
/* normalize-match: false;*/
/* steal-focus: false;*/
/* application-fallback-icon: ;*/
/* pid: "/run/user/1000/rofi.pid";*/
/* display-window: ;*/
/* display-windowcd: ;*/
/* display-run: ;*/
/* display-ssh: ;*/
/* display-drun: ;*/
/* display-combi: ;*/
/* display-keys: ;*/
/* display-filebrowser: ;*/
/* kb-primary-paste: "Control+V,Shift+Insert";*/
/* kb-secondary-paste: "Control+v,Insert";*/
/* kb-clear-line: "Control+w";*/
/* kb-move-front: "Control+a";*/
/* kb-move-end: "Control+e";*/
/* kb-move-word-back: "Alt+b,Control+Left";*/
/* kb-move-word-forward: "Alt+f,Control+Right";*/
/* kb-move-char-back: "Left,Control+b";*/
/* kb-move-char-forward: "Right,Control+f";*/
/* kb-remove-word-back: "Control+Alt+h,Control+BackSpace";*/
/* kb-remove-word-forward: "Control+Alt+d";*/
/* kb-remove-char-forward: "Delete,Control+d";*/
/* kb-remove-char-back: "BackSpace,Shift+BackSpace,Control+h";*/
/* kb-remove-to-eol: "Control+k";*/
/* kb-remove-to-sol: "Control+u";*/
/* kb-accept-entry: "Control+j,Control+m,Return,KP_Enter";*/
/* kb-accept-custom: "Control+Return";*/
/* kb-accept-custom-alt: "Control+Shift+Return";*/
/* kb-accept-alt: "Shift+Return";*/
/* kb-delete-entry: "Shift+Delete";*/
/* kb-mode-next: "Shift+Right,Control+Tab";*/
/* kb-mode-previous: "Shift+Left,Control+ISO_Left_Tab";*/
/* kb-mode-complete: "Control+l";*/
/* kb-row-left: "Control+Page_Up";*/
/* kb-row-right: "Control+Page_Down";*/
/* kb-row-up: "Up,Control+p,ISO_Left_Tab";*/
/* kb-row-down: "Down,Control+n";*/
/* kb-row-tab: "Tab";*/
/* kb-page-prev: "Page_Up";*/
/* kb-page-next: "Page_Down";*/
/* kb-row-first: "Home,KP_Home";*/
/* kb-row-last: "End,KP_End";*/
/* kb-row-select: "Control+space";*/
/* kb-screenshot: "Alt+S";*/
/* kb-ellipsize: "Alt+period";*/
/* kb-toggle-case-sensitivity: "grave,dead_grave";*/
/* kb-toggle-sort: "Alt+grave";*/
/* kb-cancel: "Escape,Control+g,Control+bracketleft";*/
/* kb-custom-1: "Alt+1";*/
/* kb-custom-2: "Alt+2";*/
/* kb-custom-3: "Alt+3";*/
/* kb-custom-4: "Alt+4";*/
/* kb-custom-5: "Alt+5";*/
/* kb-custom-6: "Alt+6";*/
/* kb-custom-7: "Alt+7";*/
/* kb-custom-8: "Alt+8";*/
/* kb-custom-9: "Alt+9";*/
/* kb-custom-10: "Alt+0";*/
/* kb-custom-11: "Alt+exclam";*/
/* kb-custom-12: "Alt+at";*/
/* kb-custom-13: "Alt+numbersign";*/
/* kb-custom-14: "Alt+dollar";*/
/* kb-custom-15: "Alt+percent";*/
/* kb-custom-16: "Alt+dead_circumflex";*/
/* kb-custom-17: "Alt+ampersand";*/
/* kb-custom-18: "Alt+asterisk";*/
/* kb-custom-19: "Alt+parenleft";*/
/* kb-select-1: "Super+1";*/
/* kb-select-2: "Super+2";*/
/* kb-select-3: "Super+3";*/
/* kb-select-4: "Super+4";*/
/* kb-select-5: "Super+5";*/
/* kb-select-6: "Super+6";*/
/* kb-select-7: "Super+7";*/
/* kb-select-8: "Super+8";*/
/* kb-select-9: "Super+9";*/
/* kb-select-10: "Super+0";*/
/* ml-row-left: "ScrollLeft";*/
/* ml-row-right: "ScrollRight";*/
/* ml-row-up: "ScrollUp";*/
/* ml-row-down: "ScrollDown";*/
/* me-select-entry: "MousePrimary";*/
/* me-accept-entry: "MouseDPrimary";*/
/* me-accept-custom: "Control+MouseDPrimary";*/
timeout {
action: "kb-cancel";
delay: 0;
}
filebrowser {
directories-first: true;
sorting-method: "name";
}
}
@theme "slate"

View File

@ -0,0 +1,65 @@
* {
background: rgba(40, 44, 52, 100%);
foreground: rgba(140, 160, 170, 100%);
bordercolor: rgba(46, 52, 63, 100%);
background-color: var(background);
border-color: var(bordercolor);
text-color: var(foreground);
spacing: 0;
}
window {
/* border-radius: 8px; */
width: 512px;
}
inputbar {
border: 0 0 1px 0;
children: [prompt,entry];
}
prompt {
padding: 16px;
border: 0 1px 0 0;
}
textbox {
background-color: var(background);
border: 0 0 1px 0;
border-color: var(bordercolor);
padding: 8px 16px;
}
entry {
padding: 16px;
}
listview {
cycle: false;
margin: 0 0 -1px 0;
scrollbar: false;
lines: 6;
}
element {
border: 0 0 1px 0;
padding: 16px;
}
element selected {
background-color: var(bordercolor);
}
element-text {
background-color: rgba(0, 0, 0, 0%);
}
element-icon {
background-color: rgba ( 0, 0, 0, 0 % );
size: 1.0000em ;
text-color: inherit;
cursor: inherit;
padding: 0 1em 0 0;
}

View File

@ -0,0 +1,11 @@
[Unit]
Description=dwm window manager
After=xrdb.service
[Service]
ExecStart=/usr/local/bin/dwm
Restart=always
RestartSec=10
[Install]
WantedBy=xorg.target

View File

@ -0,0 +1,9 @@
[Unit]
Description=slstatus daemon
After=dwm.service
[Service]
ExecStart=/usr/local/bin/slstatus
[Install]
WantedBy=xorg.target

View File

@ -0,0 +1,10 @@
[Unit]
Description=SSH key agent
[Service]
Type=simple
Environment=SSH_AUTH_SOCK=%t/ssh-agent.socket
ExecStart=/usr/bin/ssh-agent -D -a $SSH_AUTH_SOCK
[Install]
WantedBy=default.target

View File

@ -0,0 +1,10 @@
[Unit]
Description=xcape service
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/xcape -e 'Control_L=Escape'
[Install]
WantedBy=xorg.target

View File

@ -0,0 +1,3 @@
[Unit]
Description=X session managed by systemd
BindsTo=graphical-session.target

View File

@ -0,0 +1,10 @@
[Unit]
Description=X resource database manager
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/xrdb -merge %h/.Xresources
[Install]
WantedBy=xorg.target

View File

@ -0,0 +1,15 @@
# This file is written by xdg-user-dirs-update
# If you want to change or add directories, just edit the line you're
# interested in. All local changes will be retained on the next run.
# Format is XDG_xxx_DIR="$HOME/yyy", where yyy is a shell-escaped
# homedir-relative path, or XDG_xxx_DIR="/yyy", where /yyy is an
# absolute path. No other format is supported.
#
XDG_DESKTOP_DIR="$HOME/Desktop"
XDG_DOWNLOAD_DIR="$HOME/Downloads"
XDG_TEMPLATES_DIR="$HOME/Templates"
XDG_PUBLICSHARE_DIR="$HOME/Public"
XDG_DOCUMENTS_DIR="$HOME/Documents"
XDG_MUSIC_DIR="$HOME/Music"
XDG_PICTURES_DIR="$HOME/Pictures"
XDG_VIDEOS_DIR="$HOME/Videos"

View File

@ -0,0 +1 @@
en_US

View File

@ -0,0 +1,75 @@
;;; $DOOMDIR/config.el -*- lexical-binding: t; -*-
;; Place your private configuration here! Remember, you do not need to run 'doom
;; sync' after modifying this file!
;; Some functionality uses this to identify you, e.g. GPG configuration, email
;; clients, file templates and snippets.
(setq user-full-name "Dhimas Widrayato"
user-mail-address "mailme@dhimas.org")
;; Doom exposes five (optional) variables for controlling fonts in Doom. Here
;; are the three important ones:
;;
;; + `doom-font'
;; + `doom-variable-pitch-font'
;; + `doom-big-font' -- used for `doom-big-font-mode'; use this for
;; presentations or streaming.
;;
;; They all accept either a font-spec, font string ("Input Mono-12"), or xlfd
;; font string. You generally only need these two:
;; (setq doom-font (font-spec :family "monospace" :size 12 :weight 'semi-light)
;; doom-variable-pitch-font (font-spec :family "sans" :size 13))
;; (setq doom-font (font-spec :family "Monoid Nerd Font Mono" :size 12 :weight 'regular)
;; doom-variable-pitch-font (font-spec :family "Monoid Nerd Font" :size 13))
(setq doom-font (font-spec :family "FiraCode Nerd Font Mono" :size 13)
doom-variable-pitch-font (font-spec :family "FiraCode Nerd Font")
doom-big-font (font-spec :family "FiraCode Nerd Font Mono" :size 21))
;; There are two ways to load a theme. Both assume the theme is installed and
;; available. You can either set `doom-theme' or manually load a theme with the
;; `load-theme' function. This is the default:
(setq doom-theme 'doom-one)
;; If you use `org' and don't want your org files in the default location below,
;; change `org-directory'. It must be set before org loads!
(setq org-directory "~/org/")
;; This determines the style of line numbers in effect. If set to `nil', line
;; numbers are disabled. For relative line numbers, set this to `relative'.
(setq display-line-numbers-type t)
;; Set the default dictionary to Indonesian
(setq ispell-dictionary "id")
;; Make lsp mode stop complaining when opening laravel application
(setq lsp-file-watch-threshold 2000)
;; Use backslash as localleader or major mode key
(setq doom-localleader-key "\\")
(setq! citar-bibliography "~/org/tesis.bib")
(setq confirm-kill-emacs nil)
;; BUGFIX: to prevent hangs on saving when editing org file with ox-hugo
(setq org-element-use-cache nil)
;; Here are some additional functions/macros that could help you configure Doom:
;;
;; - `load!' for loading external *.el files relative to this one
;; - `use-package!' for configuring packages
;; - `after!' for running code after a package has loaded
;; - `add-load-path!' for adding directories to the `load-path', relative to
;; this file. Emacs searches the `load-path' when you load packages with
;; `require' or `use-package'.
;; - `map!' for binding new keys
;;
;; To get information about any of these functions/macros, move the cursor over
;; the highlighted symbol at press 'K' (non-evil users must press 'C-c c k').
;; This will open documentation for it, including demos of how they are used.
;;
;; You can also try 'gd' (or 'C-c c d') to jump to their definition and see how
;; they are implemented.

View File

@ -0,0 +1,196 @@
;;; init.el -*- lexical-binding: t; -*-
;; This file controls what Doom modules are enabled and what order they load
;; in. Remember to run 'doom sync' after modifying it!
;; NOTE Press 'SPC h d h' (or 'C-h d h' for non-vim users) to access Doom's
;; documentation. There you'll find a "Module Index" link where you'll find
;; a comprehensive list of Doom's modules and what flags they support.
;; NOTE Move your cursor over a module's name (or its flags) and press 'K' (or
;; 'C-c c k' for non-vim users) to view its documentation. This works on
;; flags as well (those symbols that start with a plus).
;;
;; Alternatively, press 'gd' (or 'C-c c d') on a module to browse its
;; directory (for easy access to its source code).
(doom! :input
;;chinese
;;japanese
;;layout ; auie,ctsrnm is the superior home row
:completion
company ; the ultimate code completion backend
;;helm ; the *other* search engine for love and life
;;ido ; the other *other* search engine...
;;ivy ; a search engine for love and life
vertico ; the search engine of the future
:ui
;;deft ; notational velocity for Emacs
doom ; what makes DOOM look the way it does
doom-dashboard ; a nifty splash screen for Emacs
;;doom-quit ; DOOM quit-message prompts when you quit Emacs
;;(emoji +unicode) ; 🙂
hl-todo ; highlight TODO/FIXME/NOTE/DEPRECATED/HACK/REVIEW
;;hydra
;;indent-guides ; highlighted indent columns
ligatures ; ligatures and symbols to make your code pretty again
;;minimap ; show a map of the code on the side
modeline ; snazzy, Atom-inspired modeline, plus API
;;nav-flash ; blink cursor line after big motions
;;neotree ; a project drawer, like NERDTree for vim
ophints ; highlight the region an operation acts on
(popup +defaults) ; tame sudden yet inevitable temporary windows
;;tabs ; a tab bar for Emacs
;;treemacs ; a project drawer, like neotree but cooler
unicode ; extended unicode support for various languages
vc-gutter ; vcs diff in the fringe
vi-tilde-fringe ; fringe tildes to mark beyond EOB
;;window-select ; visually switch windows
workspaces ; tab emulation, persistence & separate workspaces
zen ; distraction-free coding or writing
:editor
(evil +everywhere); come to the dark side, we have cookies
file-templates ; auto-snippets for empty files
fold ; (nigh) universal code folding
;;(format +onsave) ; automated prettiness
;;god ; run Emacs commands without modifier keys
;;lispy ; vim for lisp, for people who don't like vim
;;multiple-cursors ; editing in many places at once
;;objed ; text object editing for the innocent
;;parinfer ; turn lisp into python, sort of
;;rotate-text ; cycle region at point between text candidates
snippets ; my elves. They type so I don't have to
word-wrap ; soft wrapping with language-aware indent
:emacs
dired ; making dired pretty [functional]
electric ; smarter, keyword-based electric-indent
;;ibuffer ; interactive buffer management
undo ; persistent, smarter undo for your inevitable mistakes
vc ; version-control and Emacs, sitting in a tree
:term
;;eshell ; the elisp shell that works everywhere
;;shell ; simple shell REPL for Emacs
term ; basic terminal emulator for Emacs
;;vterm ; the best terminal emulation in Emacs
:checkers
syntax ; tasing you for every semicolon you forget
(spell) ; tasing you for misspelling mispelling
;;grammar ; tasing grammar mistake every you make
:tools
ansible
biblio ; Writes a PhD for you (citation needed)
;;debugger ; FIXME stepping through code, to help you add bugs
;;direnv
docker
editorconfig ; let someone else argue about tabs vs spaces
ein ; tame Jupyter notebooks with emacs
(eval +overlay) ; run code, run (also, repls)
;;gist ; interacting with github gists
(lookup +dictionary +offline +docsets) ; navigate your code and its documentation
lsp ; M-x vscode
magit ; a git porcelain for Emacs
;;make ; run make tasks from Emacs
;;pass ; password manager for nerds
pdf ; pdf enhancements
;;prodigy ; FIXME managing external services & code builders
;;rgb ; creating color strings
;;taskrunner ; taskrunner for all your projects
terraform ; infrastructure as code
;;tmux ; an API for interacting with tmux
;;upload ; map local to remote projects via ssh/ftp
:os
(:if IS-MAC macos) ; improve compatibility with macOS
;;tty ; improve the terminal Emacs experience
:lang
;;agda ; types of types of types of types...
;;beancount ; mind the GAAP
;;cc ; C > C++ == 1
;;clojure ; java with a lisp
common-lisp ; if you've seen one lisp, you've seen them all
;;coq ; proofs-as-programs
;;crystal ; ruby at the speed of c
;;csharp ; unity, .NET, and mono shenanigans
data ; config/data formats
;;(dart +flutter) ; paint ui and not much else
;;dhall
;;elixir ; erlang done right
;;elm ; care for a cup of TEA?
emacs-lisp ; drown in parentheses
;;erlang ; an elegant language for a more civilized age
;;ess ; emacs speaks statistics
;;factor
;;faust ; dsp, but you get to keep your soul
;;fortran ; in FORTRAN, GOD is REAL (unless declared INTEGER)
;;fsharp ; ML stands for Microsoft's Language
;;fstar ; (dependent) types and (monadic) effects and Z3
;;gdscript ; the language you waited for
;;(go +lsp) ; the hipster dialect
;;(haskell +lsp) ; a language that's lazier than I am
;;hy ; readability of scheme w/ speed of python
;;idris ; a language you can depend on
;;json ; At least it ain't XML
;;(java +meghanada) ; the poster child for carpal tunnel syndrome
javascript ; all(hope(abandon(ye(who(enter(here))))))
;;julia ; a better, faster MATLAB
;;kotlin ; a better, slicker Java(Script)
latex ; writing papers in Emacs has never been so fun
;;lean ; for folks with too much to prove
;;ledger ; be audit you can be
;;lua ; one-based indices? one-based indices
markdown ; writing docs for people to ignore
;;nim ; python + lisp at the speed of c
;;nix ; I hereby declare "nix geht mehr!"
;;ocaml ; an objective camel
(org +hugo +roam2 +journal +present +pretty +pandoc) ; organize your plain life in plain text
(php +lsp) ; perl's insecure younger brother
;;plantuml ; diagrams for confusing people more
;;purescript ; javascript, but functional
python ; beautiful is better than ugly
;;qt ; the 'cutest' gui framework ever
;;racket ; a DSL for DSLs
;;raku ; the artist formerly known as perl6
;;rest ; Emacs as a REST client
;;rst ; ReST in peace
;;(ruby +rails) ; 1.step {|i| p "Ruby is #{i.even? ? 'love' : 'life'}"}
;;rust ; Fe2O3.unwrap().unwrap().unwrap().unwrap()
;;scala ; java, but good
;;(scheme +guile) ; a fully conniving family of lisps
sh ; she sells {ba,z,fi}sh shells on the C xor
;;sml
;;solidity ; do you need a blockchain? No.
;;swift ; who asked for emoji variables?
;;terra ; Earth and Moon in alignment for performance.
web ; the tubes
yaml ; JSON, but readable
;;zig ; C, but simpler
:email
(mu4e +org +gmail)
;;notmuch
;;(wanderlust +gmail)
:app
;;calendar
;;emms
;;everywhere ; *leave* Emacs!? You must be joking
;;irc ; how neckbeards socialize
;;(rss +org) ; emacs as an RSS reader
;;twitter ; twitter client https://twitter.com/vnought
:config
;;literate
(default +bindings +smartparens))
(use-package-hook! evil
:pre-init
(setq evil-respect-visual-line-mode t)
t)

View File

@ -0,0 +1,50 @@
;; -*- no-byte-compile: t; -*-
;;; $DOOMDIR/packages.el
;; To install a package with Doom you must declare them here and run 'doom sync'
;; on the command line, then restart Emacs for the changes to take effect -- or
;; use 'M-x doom/reload'.
;; To install SOME-PACKAGE from MELPA, ELPA or emacsmirror:
;(package! some-package)
;; To install a package directly from a remote git repo, you must specify a
;; `:recipe'. You'll find documentation on what `:recipe' accepts here:
;; https://github.com/raxod502/straight.el#the-recipe-format
;(package! another-package
; :recipe (:host github :repo "username/repo"))
;; If the package you are trying to install does not contain a PACKAGENAME.el
;; file, or is located in a subdirectory of the repo, you'll need to specify
;; `:files' in the `:recipe':
;(package! this-package
; :recipe (:host github :repo "username/repo"
; :files ("some-file.el" "src/lisp/*.el")))
;; If you'd like to disable a package included with Doom, you can do so here
;; with the `:disable' property:
;(package! builtin-package :disable t)
;; You can override the recipe of a built in package without having to specify
;; all the properties for `:recipe'. These will inherit the rest of its recipe
;; from Doom or MELPA/ELPA/Emacsmirror:
;(package! builtin-package :recipe (:nonrecursive t))
;(package! builtin-package-2 :recipe (:repo "myfork/package"))
;; Specify a `:branch' to install a package from a particular branch or tag.
;; This is required for some packages whose default branch isn't 'master' (which
;; our package manager can't deal with; see raxod502/straight.el#279)
;(package! builtin-package :recipe (:branch "develop"))
;; Use `:pin' to specify a particular commit to install.
;(package! builtin-package :pin "1a2b3c4d5e")
;; Doom's packages are pinned to a specific commit and updated from release to
;; release. The `unpin!' macro allows you to unpin single packages...
;(unpin! pinned-package)
;; ...or multiple packages
;(unpin! pinned-package another-pinned-package)
;; ...Or *all* packages (NOT RECOMMENDED; will likely break things)
;(unpin! t)

View File

@ -0,0 +1,18 @@
# DO NOT EDIT! This file will be overwritten by LXAppearance.
# Any customization should be done in ~/.gtkrc-2.0.mine instead.
include "/home/dim/.gtkrc-2.0.mine"
gtk-theme-name="Arc"
gtk-icon-theme-name="Arc"
gtk-font-name="Droid Sans 10"
gtk-cursor-theme-name="capitaine-cursors-light"
gtk-cursor-theme-size=0
gtk-toolbar-style=GTK_TOOLBAR_BOTH
gtk-toolbar-icon-size=GTK_ICON_SIZE_LARGE_TOOLBAR
gtk-button-images=1
gtk-menu-images=1
gtk-enable-event-sounds=1
gtk-enable-input-feedback-sounds=1
gtk-xft-antialias=1
gtk-xft-hinting=1
gtk-xft-hintstyle="hintfull"

View File

@ -0,0 +1,13 @@
#!/usr/bin/sh
case $1 in
"up")
xbacklight -inc 5
;;
"down")
xbacklight -dec 5
;;
*)
xbacklight -get
esac
dunstify -a "Brightness" -r 88879 -u low -t 1500 $(xbacklight -get)

View File

@ -0,0 +1,28 @@
#!/usr/bin/bash
### WARNING: Your distro might not put bash in this location.
### You could just use `/usr/bin/env bash` instead.
# only needed for debugging
exec >/tmp/udev.out 2>&1
HOME=/home/dim
XAUTHORITY=$HOME/.Xauthority
export XAUTHORITY HOME
DISPLAY=:0 ; export DISPLAY;
# Path to lock file
lock="/tmp/keyboard.lock"
# Lock the file (other atomic alternatives would be "ln" or "mkdir")
exec 9>"$lock"
if ! flock -n 9; then
notify-send -t 5000 "Keyboard script is already running."
exit 1
fi
/usr/bin/su dim -c "sleep 3; /usr//bin/xmodmap $HOME/.Xmodmap;" &
notify-send -t 2000 "Xmodmap finished" &
# The lock file will be unlocked when the script ends
echo '' > /tmp/keyboard.lock &
unset DISPLAY

View File

@ -0,0 +1,3 @@
#!/usr/bin/sh
/usr/local/bin/slock

View File

@ -0,0 +1,8 @@
#!/usr/bin/sh
# make CapsLock behave like Ctrl:
setxkbmap -option ctrl:nocaps
# make short-pressed Ctrl behave like Escape:
xcape -e 'Control_L=Escape'

View File

@ -0,0 +1,16 @@
#!/usr/bin/sh
case $1 in
"up")
pamixer -i 3
;;
"down")
pamixer -d 3
;;
"mute")
pamixer -t
;;
*)
pamixer --get-volume-human
esac
dunstify -a "Volume" -r 88878 -u low -t 1500 $(pamixer --get-volume-human)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1 @@
AddKeysToAgent yes

View File

@ -0,0 +1,46 @@
# List of plugins
set -g @plugin 'tmux-plugins/tpm'
set -g @plugin 'tmux-plugins/tmux-sensible'
set -g @plugin 'tmux-plugins/tmux-pain-control'
set -g @plugin 'roosta/tmux-fuzzback'
# Colorscheme
set -g @plugin 'dracula/tmux'
set -g @dracula-plugins "git time"
set -g @dracula-show-powerline false
set -g @dracula-day-month true
set -g @dracula-show-fahrenheit false
set -g @dracula-show-flags false
set -g @dracula-border-contrast true
set -g @dracula-military-time true
set -g prefix2 C-a
bind C-a send-prefix -2
bind Tab last-window
#bind-key K send-keys -R ^M \; clear-history
bind -n C-l send-keys C-l \; run 'sleep 0.1' \; clear-history
set -g base-index 1 # start windows numbering at 1
setw -g pane-base-index 1 # make pane numbering consistent with windows
# Fuzzback
set -g @fuzzback-popup 1
# Continuum
set -g @plugin 'tmux-plugins/tmux-resurrect'
set -g @plugin 'tmux-plugins/tmux-continuum'
set -g @continuum-restore 'on'
# Vim Keybinding
set-window-option -g mode-keys vi
bind-key -T copy-mode-vi v send -X begin-selection
bind-key -T copy-mode-vi V send -X select-line
bind-key -T copy-mode-vi y send -X copy-pipe-and-cancel 'xclip -in -selection clipboard'
# Initialize TMUX plugin manager (keep this line at the very bottom of tmux.conf)
run '~/.tmux/plugins/tpm/tpm'

View File

@ -0,0 +1,7 @@
#!/bin/sh
# Import environemnt to systemd user session
systemctl --user import-environment DISPLAY XAUTHORITY PATH
# Run graphical-session.target
systemctl --user start --wait xorg.target

View File

@ -0,0 +1,130 @@
# If you come from bash you might have to change your $PATH.
# export PATH=$HOME/bin:/usr/local/bin:$PATH
export PATH="$HOME/.local/bin:$PATH"
# Path to your oh-my-zsh installation.
export ZSH="$HOME/.oh-my-zsh"
# Set name of the theme to load --- if set to "random", it will
# load a random theme each time oh-my-zsh is loaded, in which case,
# to know which specific one was loaded, run: echo $RANDOM_THEME
# See https://github.com/ohmyzsh/ohmyzsh/wiki/Themes
#ZSH_THEME="robbyrussell"
source /usr/share/zsh-theme-powerlevel10k/powerlevel10k.zsh-theme
autoload -Uz compinit
compinit
# Completion for kitty
kitty + complete setup zsh | source /dev/stdin
# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh.
[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh
# Set list of themes to pick from when loading at random
# Setting this variable when ZSH_THEME=random will cause zsh to load
# a theme from this variable instead of looking in $ZSH/themes/
# If set to an empty array, this variable will have no effect.
# ZSH_THEME_RANDOM_CANDIDATES=( "robbyrussell" "agnoster" )
# Uncomment the following line to use case-sensitive completion.
# CASE_SENSITIVE="true"
# Uncomment the following line to use hyphen-insensitive completion.
# Case-sensitive completion must be off. _ and - will be interchangeable.
# HYPHEN_INSENSITIVE="true"
# Uncomment the following line to disable bi-weekly auto-update checks.
DISABLE_AUTO_UPDATE="true"
# Uncomment the following line to automatically update without prompting.
# DISABLE_UPDATE_PROMPT="true"
# Uncomment the following line to change how often to auto-update (in days).
# export UPDATE_ZSH_DAYS=13
# Uncomment the following line if pasting URLs and other text is messed up.
# DISABLE_MAGIC_FUNCTIONS="true"
# Uncomment the following line to disable colors in ls.
# DISABLE_LS_COLORS="true"
# Uncomment the following line to disable auto-setting terminal title.
# DISABLE_AUTO_TITLE="true"
# Uncomment the following line to enable command auto-correction.
# ENABLE_CORRECTION="true"
# Uncomment the following line to display red dots whilst waiting for completion.
# Caution: this setting can cause issues with multiline prompts (zsh 5.7.1 and newer seem to work)
# See https://github.com/ohmyzsh/ohmyzsh/issues/5765
# COMPLETION_WAITING_DOTS="true"
# Uncomment the following line if you want to disable marking untracked files
# under VCS as dirty. This makes repository status check for large repositories
# much, much faster.
# DISABLE_UNTRACKED_FILES_DIRTY="true"
# Uncomment the following line if you want to change the command execution time
# stamp shown in the history command output.
# You can set one of the optional three formats:
# "mm/dd/yyyy"|"dd.mm.yyyy"|"yyyy-mm-dd"
# or set a custom format using the strftime function format specifications,
# see 'man strftime' for details.
# HIST_STAMPS="mm/dd/yyyy"
# Would you like to use another custom folder than $ZSH/custom?
# ZSH_CUSTOM=/path/to/new-custom-folder
# Which plugins would you like to load?
# Standard plugins can be found in $ZSH/plugins/
# Custom plugins may be added to $ZSH_CUSTOM/plugins/
# Example format: plugins=(rails git textmate ruby lighthouse)
# Add wisely, as too many plugins slow down shell startup.
plugins=(
git
themes
bgnotify
archlinux
colored-man-pages
colorize
command-not-found
emoji-clock
sudo
thefuck
zsh-interactive-cd
zoxide
)
source $ZSH/oh-my-zsh.sh
# User configuration
# export MANPATH="/usr/local/man:$MANPATH"
# You may need to manually set your language environment
# export LANG=en_US.UTF-8
# Preferred editor for local and remote sessions
# if [[ -n $SSH_CONNECTION ]]; then
# export EDITOR='vim'
# else
# export EDITOR='mvim'
# fi
# Compilation flags
# export ARCHFLAGS="-arch x86_64"
# Set personal aliases, overriding those provided by oh-my-zsh libs,
# plugins, and themes. Aliases can be placed here, though oh-my-zsh
# users are encouraged to define aliases within the ZSH_CUSTOM folder.
# For a full list of active aliases, run `alias`.
#
# Example aliases
# alias zshconfig="mate ~/.zshrc"
# alias ohmyzsh="mate ~/.oh-my-zsh"
#alias ls="lsd"
alias ls="lsd"

View File

@ -0,0 +1,5 @@
* text=auto
*.zsh text eol=lf
*.zsh-theme text eol=lf
/prompt_powerlevel9k_setup text eol=lf
/prompt_powerlevel10k_setup text eol=lf

View File

@ -0,0 +1 @@
*.zwc

View File

@ -0,0 +1,22 @@
Copyright (c) 2009-2014 Robby Russell and contributors (see https://github.com/robbyrussell/oh-my-zsh/contributors)
Copyright (c) 2014-2017 Ben Hilburn <bhilburn@gmail.com>
Copyright (c) 2019 Roman Perepelitsa <roman.perepelitsa@gmail.com> and contributors (see https://github.com/romkatv/powerlevel10k/contributors)
MIT LICENSE
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,7 @@
ZSH := $(shell command -v zsh 2> /dev/null)
all:
pkg:
$(MAKE) -C gitstatus pkg
$(or $(ZSH),:) -fc 'for f in *.zsh-theme internal/*.zsh; do zcompile -R -- $$f.zwc $$f || exit; done'

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,193 @@
# Config file for Powerlevel10k with the style of Pure (https://github.com/sindresorhus/pure).
#
# Differences from Pure:
#
# - Git:
# - `@c4d3ec2c` instead of something like `v1.4.0~11` when in detached HEAD state.
# - No automatic `git fetch` (the same as in Pure with `PURE_GIT_PULL=0`).
#
# Apart from the differences listed above, the replication of Pure prompt is exact. This includes
# even the questionable parts. For example, just like in Pure, there is no indication of Git status
# being stale; prompt symbol is the same in command, visual and overwrite vi modes; when prompt
# doesn't fit on one line, it wraps around with no attempt to shorten it.
#
# If you like the general style of Pure but not particularly attached to all its quirks, type
# `p10k configure` and pick "Lean" style. This will give you slick minimalist prompt while taking
# advantage of Powerlevel10k features that aren't present in Pure.
# Temporarily change options.
'builtin' 'local' '-a' 'p10k_config_opts'
[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases')
[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob')
[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand')
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
() {
emulate -L zsh -o extended_glob
# Unset all configuration options.
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
autoload -Uz is-at-least && is-at-least 5.1 || return
# Prompt colors.
local grey=242
local red=1
local yellow=3
local blue=4
local magenta=5
local cyan=6
local white=7
# Left prompt segments.
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(
# =========================[ Line #1 ]=========================
context # user@host
dir # current directory
vcs # git status
command_execution_time # previous command duration
# =========================[ Line #2 ]=========================
newline # \n
virtualenv # python virtual environment
prompt_char # prompt symbol
)
# Right prompt segments.
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=(
# =========================[ Line #1 ]=========================
# command_execution_time # previous command duration
# virtualenv # python virtual environment
# context # user@host
# time # current time
# =========================[ Line #2 ]=========================
newline # \n
)
# Basic style options that define the overall prompt look.
typeset -g POWERLEVEL9K_BACKGROUND= # transparent background
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol
typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION= # no segment icons
# Add an empty line before each prompt except the first. This doesn't emulate the bug
# in Pure that makes prompt drift down whenever you use the Alt-C binding from fzf or similar.
typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=true
# Magenta prompt symbol if the last command succeeded.
typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS}_FOREGROUND=$magenta
# Red prompt symbol if the last command failed.
typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS}_FOREGROUND=$red
# Default prompt symbol.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION=''
# Prompt symbol in command vi mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION=''
# Prompt symbol in visual vi mode is the same as in command mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION=''
# Prompt symbol in overwrite vi mode is the same as in command mode.
typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=false
# Grey Python Virtual Environment.
typeset -g POWERLEVEL9K_VIRTUALENV_FOREGROUND=$grey
# Don't show Python version.
typeset -g POWERLEVEL9K_VIRTUALENV_SHOW_PYTHON_VERSION=false
typeset -g POWERLEVEL9K_VIRTUALENV_{LEFT,RIGHT}_DELIMITER=
# Blue current directory.
typeset -g POWERLEVEL9K_DIR_FOREGROUND=$blue
# Context format when root: user@host. The first part white, the rest grey.
typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE="%F{$white}%n%f%F{$grey}@%m%f"
# Context format when not root: user@host. The whole thing grey.
typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE="%F{$grey}%n@%m%f"
# Don't show context unless root or in SSH.
typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_CONTENT_EXPANSION=
# Show previous command duration only if it's >= 5s.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=5
# Don't show fractional seconds. Thus, 7s rather than 7.3s.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0
# Duration format: 1d 2h 3m 4s.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s'
# Yellow previous command duration.
typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=$yellow
# Grey Git prompt. This makes stale prompts indistinguishable from up-to-date ones.
typeset -g POWERLEVEL9K_VCS_FOREGROUND=$grey
# Disable async loading indicator to make directories that aren't Git repositories
# indistinguishable from large Git repositories without known state.
typeset -g POWERLEVEL9K_VCS_LOADING_TEXT=
# Don't wait for Git status even for a millisecond, so that prompt always updates
# asynchronously when Git state changes.
typeset -g POWERLEVEL9K_VCS_MAX_SYNC_LATENCY_SECONDS=0
# Cyan ahead/behind arrows.
typeset -g POWERLEVEL9K_VCS_{INCOMING,OUTGOING}_CHANGESFORMAT_FOREGROUND=$cyan
# Don't show remote branch, current tag or stashes.
typeset -g POWERLEVEL9K_VCS_GIT_HOOKS=(vcs-detect-changes git-untracked git-aheadbehind)
# Don't show the branch icon.
typeset -g POWERLEVEL9K_VCS_BRANCH_ICON=
# When in detached HEAD state, show @commit where branch normally goes.
typeset -g POWERLEVEL9K_VCS_COMMIT_ICON='@'
# Don't show staged, unstaged, untracked indicators.
typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED}_ICON=
# Show '*' when there are staged, unstaged or untracked files.
typeset -g POWERLEVEL9K_VCS_DIRTY_ICON='*'
# Show '⇣' if local branch is behind remote.
typeset -g POWERLEVEL9K_VCS_INCOMING_CHANGES_ICON=':⇣'
# Show '⇡' if local branch is ahead of remote.
typeset -g POWERLEVEL9K_VCS_OUTGOING_CHANGES_ICON=':⇡'
# Don't show the number of commits next to the ahead/behind arrows.
typeset -g POWERLEVEL9K_VCS_{COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=1
# Remove space between '⇣' and '⇡' and all trailing spaces.
typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${${${P9K_CONTENT/⇣* :⇡/⇣⇡}// }//:/ }'
# Grey current time.
typeset -g POWERLEVEL9K_TIME_FOREGROUND=$grey
# Format for the current time: 09:51:02. See `man 3 strftime`.
typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}'
# If set to true, time will update when you hit enter. This way prompts for the past
# commands will contain the start times of their commands rather than the end times of
# their preceding commands.
typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false
# Transient prompt works similarly to the builtin transient_rprompt option. It trims down prompt
# when accepting a command line. Supported values:
#
# - off: Don't change prompt when accepting a command line.
# - always: Trim down prompt when accepting a command line.
# - same-dir: Trim down prompt when accepting a command line unless this is the first command
# typed after changing current working directory.
typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off
# Instant prompt mode.
#
# - off: Disable instant prompt. Choose this if you've tried instant prompt and found
# it incompatible with your zsh configuration files.
# - quiet: Enable instant prompt and don't print warnings when detecting console output
# during zsh initialization. Choose this if you've read and understood
# https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
# - verbose: Enable instant prompt and print a warning when detecting console output during
# zsh initialization. Choose this if you've never tried instant prompt, haven't
# seen the warning, or if you are unsure what this all means.
typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose
# Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized.
# For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload
# can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you
# really need it.
typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true
# If p10k is already loaded, reload configuration.
# This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true.
(( ! $+functions[p10k] )) || p10k reload
}
# Tell `p10k configure` which file it should overwrite.
typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
'builtin' 'unset' 'p10k_config_opts'

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,111 @@
# Config file for Powerlevel10k with the style of robbyrussell theme from Oh My Zsh.
#
# Original: https://github.com/ohmyzsh/ohmyzsh/wiki/Themes#robbyrussell.
#
# Replication of robbyrussell theme is exact. The only observable difference is in
# performance. Powerlevel10k prompt is very fast everywhere, even in large Git repositories.
#
# Usage: Source this file either before or after loading Powerlevel10k.
#
# source ~/powerlevel10k/config/p10k-robbyrussell.zsh
# source ~/powerlevel10k/powerlevel10k.zsh-theme
# Temporarily change options.
'builtin' 'local' '-a' 'p10k_config_opts'
[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases')
[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob')
[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand')
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
() {
emulate -L zsh -o extended_glob
# Unset all configuration options.
unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR'
# Zsh >= 5.1 is required.
autoload -Uz is-at-least && is-at-least 5.1 || return
# Left prompt segments.
typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=(prompt_char dir vcs)
# Right prompt segments.
typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=()
# Basic style options that define the overall prompt look.
typeset -g POWERLEVEL9K_BACKGROUND= # transparent background
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_{LEFT,RIGHT}_WHITESPACE= # no surrounding whitespace
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SUBSEGMENT_SEPARATOR=' ' # separate segments with a space
typeset -g POWERLEVEL9K_{LEFT,RIGHT}_SEGMENT_SEPARATOR= # no end-of-line symbol
typeset -g POWERLEVEL9K_VISUAL_IDENTIFIER_EXPANSION= # no segment icons
# Green prompt symbol if the last command succeeded.
typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS}_FOREGROUND=green
# Red prompt symbol if the last command failed.
typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS}_FOREGROUND=red
# Prompt symbol: bold arrow.
typeset -g POWERLEVEL9K_PROMPT_CHAR_CONTENT_EXPANSION='%B➜ '
# Cyan current directory.
typeset -g POWERLEVEL9K_DIR_FOREGROUND=cyan
# Show only the last segment of the current directory.
typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_last
# Bold directory.
typeset -g POWERLEVEL9K_DIR_CONTENT_EXPANSION='%B$P9K_CONTENT'
# Git status formatter.
function my_git_formatter() {
emulate -L zsh
if [[ -n $P9K_CONTENT ]]; then
# If P9K_CONTENT is not empty, it's either "loading" or from vcs_info (not from
# gitstatus plugin). VCS_STATUS_* parameters are not available in this case.
typeset -g my_git_format=$P9K_CONTENT
else
# Use VCS_STATUS_* parameters to assemble Git status. See reference:
# https://github.com/romkatv/gitstatus/blob/master/gitstatus.plugin.zsh.
typeset -g my_git_format="${1+%B%4F}git:(${1+%1F}"
my_git_format+=${${VCS_STATUS_LOCAL_BRANCH:-${VCS_STATUS_COMMIT[1,8]}}//\%/%%}
my_git_format+="${1+%4F})"
if (( VCS_STATUS_NUM_CONFLICTED || VCS_STATUS_NUM_STAGED ||
VCS_STATUS_NUM_UNSTAGED || VCS_STATUS_NUM_UNTRACKED )); then
my_git_format+=" ${1+%3F}"
fi
fi
}
functions -M my_git_formatter 2>/dev/null
# Disable the default Git status formatting.
typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true
# Install our own Git status formatter.
typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}'
typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter()))+${my_git_format}}'
# Grey Git status when loading.
typeset -g POWERLEVEL9K_VCS_LOADING_FOREGROUND=246
# Instant prompt mode.
#
# - off: Disable instant prompt. Choose this if you've tried instant prompt and found
# it incompatible with your zsh configuration files.
# - quiet: Enable instant prompt and don't print warnings when detecting console output
# during zsh initialization. Choose this if you've read and understood
# https://github.com/romkatv/powerlevel10k/blob/master/README.md#instant-prompt.
# - verbose: Enable instant prompt and print a warning when detecting console output during
# zsh initialization. Choose this if you've never tried instant prompt, haven't
# seen the warning, or if you are unsure what this all means.
typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose
# Hot reload allows you to change POWERLEVEL9K options after Powerlevel10k has been initialized.
# For example, you can type POWERLEVEL9K_BACKGROUND=red and see your prompt turn red. Hot reload
# can slow down prompt by 1-2 milliseconds, so it's better to keep it turned off unless you
# really need it.
typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true
# If p10k is already loaded, reload configuration.
# This works even with POWERLEVEL9K_DISABLE_HOT_RELOAD=true.
(( ! $+functions[p10k] )) || p10k reload
}
# Tell `p10k configure` which file it should overwrite.
typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a}
(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]}
'builtin' 'unset' 'p10k_config_opts'

View File

@ -0,0 +1,88 @@
# 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
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**: 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*, 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`.
- **Microsoft Terminal** (the new thing): Open *Settings* (`Ctrl+,`), search for `fontFace` and set
value to `MesloLGS NF` for every profile.
- **IntelliJ**: Open *Intellij → 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`.
- **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`.
- **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.
**IMPORTANT:** Run `p10k configure` after changing terminal font. The old `~/.p10k.zsh` 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!_

View File

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

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

View File

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

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
}

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"
}
}

View File

@ -0,0 +1,674 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program 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.
This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<https://www.gnu.org/licenses/why-not-lgpl.html>.

View File

@ -0,0 +1,41 @@
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)
pkg:
GITSTATUS_DAEMON= GITSTATUS_CACHE_DIR=$(shell pwd)/usrbin ./install -f
$(or $(ZSH),:) -fc 'for f in *.zsh install; do zcompile -R -- $$f.zwc $$f || exit; done'
-include $(OBJS:.o=.dep)

View File

@ -0,0 +1,529 @@
# 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, on macOS you can install with Homebrew:
```zsh
brew install romkatv/gitstatus/gitstatus
echo 'source /usr/local/opt/gitstatus/gitstatus.prompt.zsh' >>! ~/.zshrc
```
(If you choose this option, replace `~/gitstatus` with `/usr/local/opt/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, on macOS you can install with Homebrew:
```zsh
brew install romkatv/gitstatus/gitstatus
echo 'source /usr/local/opt/gitstatus/gitstatus.prompt.sh' >> ~/.bashrc
```
(If you choose this option, replace `~/gitstatus` with `/usr/local/opt/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

@ -0,0 +1,540 @@
#!/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##*:*}" ]; 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 gcc git perl5
;;
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 list "$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"
if [ "$gitstatus_cpu" = x86-64 ]; then
cflags="$cflags -mtune=generic"
fi
libgit2_cmake_flags=
libgit2_cflags="$cflags"
gitstatus_cxx=g++
gitstatus_cxxflags="$cflags -I${workdir}/libgit2/include -DGITSTATUS_ZERO_NSEC -D_GNU_SOURCE"
gitstatus_ldflags="-L${workdir}/libgit2/build"
gitstatus_ldlibs=
gitstatus_make=make
case "$gitstatus_kernel" in
linux)
gitstatus_ldflags="$gitstatus_ldflags -static"
gitstatus_ldflags="$gitstatus_ldflags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
;;
freebsd)
gitstatus_make=gmake
gitstatus_ldflags="$gitstatus_ldflags -static"
gitstatus_ldflags="$gitstatus_ldflags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
;;
netbsd)
gitstatus_make=gmake
gitstatus_ldflags="$gitstatus_ldflags -static"
gitstatus_ldflags="$gitstatus_ldflags -Wl,-O1,--sort-common,--as-needed,-z,relro,-z,now"
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"
libgit2_cmake_flags="$libgit2_cmake_flags -DENABLE_REPRODUCIBLE_BUILDS=ON"
;;
cygwin*)
gitstatus_ldflags="$gitstatus_ldflags -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 gcc g++ 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
command wget -O "$libgit2_tmp" -- "$libgit2_url"
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=Release \
-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="$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
command git -C "$workdir"/repo init
command git -C "$workdir"/repo config user.name "Your Name"
command git -C "$workdir"/repo config user.email "you@example.com"
command git -C "$workdir"/repo commit --allow-empty --allow-empty-message --no-gpg-sign -m ''
resp="$(printf "hello\037$workdir/repo\036" | "$app".tmp)"
[ -n "$resp" -a -z "${resp##hello*1*$workdir/repo*master*}" ]
resp="$(printf 'hello\037\036' | "$app".tmp)"
[ -n "$resp" -a -z "${resp##hello*0*}" ]
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
armv6l) gitstatus_cpu=armv6;;
armv7l) gitstatus_cpu=armv7;;
arm64) gitstatus_cpu=armv8;;
aarch64) gitstatus_cpu=armv8-a;;
ppc64le) gitstatus_cpu=powerpc64le;;
riscv64) gitstatus_cpu=rv64imafdc;;
x86_64|amd64) gitstatus_cpu=x86-64;;
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;;
i386|i586|i686) docker_image=i386/alpine:3.11.6;;
armv6l) 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;;
*)
>&2 echo '[error] unable to infer docker image'
>&2 echo 'Please specify explicitly with `-i IMAGE`.'
exit 1
;;
esac
fi
fi
;;
freebsd|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

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.3.1"
# 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-82cefe2b42300224ad3c148f8b1a569757cc617a"
libgit2_sha256="dc701c4e2080f7901f5d599d642b629569e5581086b9838d481d09f284dc7621"

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,489 @@
# 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.
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 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: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;;
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"
$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
unset -f gitstatus_start_impl
if [[ "${GITSTATUS_STOP_ON_EXEC:-1}" == 1 ]]; then
type -t _gitstatus_exec &>/dev/null || function _gitstatus_exec() { exec "$@"; }
type -t _gitstatus_builtin &>/dev/null || function _gitstatus_builtin() { builtin "$@"; }
function _gitstatus_exec_wrapper() {
(( ! $# )) || gitstatus_stop
local ret=0
_gitstatus_exec "$@" || ret=$?
[[ -n "${GITSTATUS_DAEMON_PID:-}" ]] || gitstatus_start || true
return $ret
}
function _gitstatus_builtin_wrapper() {
while [[ "${1:-}" == builtin ]]; do shift; done
if [[ "${1:-}" == exec ]]; then
_gitstatus_exec_wrapper "${@:2}"
else
_gitstatus_builtin "$@"
fi
}
alias exec=_gitstatus_exec_wrapper
alias builtin=_gitstatus_builtin_wrapper
_GITSTATUS_EXEC_HOOK=1
else
unset _GITSTATUS_EXEC_HOOK
fi
}
# Stops gitstatusd if it's running.
function gitstatus_stop() {
[[ "${_GITSTATUS_CLIENT_PID:-$BASHPID}" == "$BASHPID" ]] || return 0
[[ -z "${_GITSTATUS_REQ_FD:-}" ]] || exec {_GITSTATUS_REQ_FD}>&- || true
[[ -z "${_GITSTATUS_RESP_FD:-}" ]] || exec {_GITSTATUS_RESP_FD}>&- || true
[[ -z "${GITSTATUS_DAEMON_PID:-}" ]] || kill "$GITSTATUS_DAEMON_PID" &>/dev/null || true
if [[ -n "${_GITSTATUS_EXEC_HOOK:-}" ]]; then
unalias exec builtin &>/dev/null || true
function _gitstatus_exec_wrapper() { _gitstatus_exec "$@"; }
function _gitstatus_builtin_wrapper() { _gitstatus_builtin "$@"; }
fi
unset _GITSTATUS_REQ_FD _GITSTATUS_RESP_FD GITSTATUS_DAEMON_PID _GITSTATUS_EXEC_HOOK
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_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_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
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

@ -0,0 +1,885 @@
# Zsh bindings for gitstatus.
#
# ------------------------------------------------------------------
#
# Example: Start gitstatusd, send it a request, wait for response and print it.
#
# source ~/gitstatus/gitstatus.plugin.zsh
# gitstatus_start MY
# gitstatus_query -d $PWD MY
# typeset -m 'VCS_STATUS_*'
#
# Output:
#
# VCS_STATUS_ACTION=''
# VCS_STATUS_COMMIT=c000eddcff0fb38df2d0137efe24d9d2d900f209
# VCS_STATUS_COMMITS_AHEAD=0
# VCS_STATUS_COMMITS_BEHIND=0
# VCS_STATUS_HAS_CONFLICTED=0
# VCS_STATUS_HAS_STAGED=0
# VCS_STATUS_HAS_UNSTAGED=1
# VCS_STATUS_HAS_UNTRACKED=1
# VCS_STATUS_INDEX_SIZE=33
# VCS_STATUS_LOCAL_BRANCH=master
# VCS_STATUS_NUM_ASSUME_UNCHANGED=0
# VCS_STATUS_NUM_CONFLICTED=0
# VCS_STATUS_NUM_STAGED=0
# VCS_STATUS_NUM_UNSTAGED=1
# VCS_STATUS_NUM_SKIP_WORKTREE=0
# VCS_STATUS_NUM_STAGED_NEW=0
# VCS_STATUS_NUM_STAGED_DELETED=0
# VCS_STATUS_NUM_UNSTAGED_DELETED=0
# VCS_STATUS_NUM_UNTRACKED=1
# VCS_STATUS_PUSH_COMMITS_AHEAD=0
# VCS_STATUS_PUSH_COMMITS_BEHIND=0
# VCS_STATUS_PUSH_REMOTE_NAME=''
# VCS_STATUS_PUSH_REMOTE_URL=''
# VCS_STATUS_REMOTE_BRANCH=master
# VCS_STATUS_REMOTE_NAME=origin
# VCS_STATUS_REMOTE_URL=git@github.com:romkatv/powerlevel10k.git
# VCS_STATUS_RESULT=ok-sync
# VCS_STATUS_STASHES=0
# VCS_STATUS_TAG=''
# VCS_STATUS_WORKDIR=/home/romka/powerlevel10k
[[ -o 'interactive' ]] || 'return'
# Temporarily change options.
'builtin' 'local' '-a' '_gitstatus_opts'
[[ ! -o 'aliases' ]] || _gitstatus_opts+=('aliases')
[[ ! -o 'sh_glob' ]] || _gitstatus_opts+=('sh_glob')
[[ ! -o 'no_brace_expand' ]] || _gitstatus_opts+=('no_brace_expand')
'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand'
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"${1:-}"="${${(%):-%x}:A:h}"
# Retrives status of a git repo from a directory under its working tree.
#
## Usage: gitstatus_query [OPTION]... NAME
#
# -d STR Directory to query. Defaults to the current directory. Has no effect if GIT_DIR
# is set.
# -c STR Callback function to call once the results are available. Called only after
# gitstatus_query returns 0 with VCS_STATUS_RESULT=tout.
# -t FLOAT Timeout in seconds. Negative value means infinity. Will block for at most this long.
# If no results are available by then: if -c isn't specified, will return 1; otherwise
# will set VCS_STATUS_RESULT=tout and return 0.
# -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:
#
# tout Timed out waiting for data; will call the user-specified callback later.
# norepo-sync The directory isn't a git repo.
# ok-sync The directory is a git repo.
#
# When the callback is called, VCS_STATUS_RESULT is set to one of the following values:
#
# norepo-async The directory isn't a git repo.
# ok-async The directory is a git repo.
#
# If VCS_STATUS_RESULT is ok-sync or ok-async, 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_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.
#
# !!!!! WARNING: CONCURRENT CALLS WITH THE SAME NAME ARE NOT ALLOWED !!!!!
#
# 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"${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
local -i no_diff OPTIND
local -F timeout=-1
while getopts ":d:c:t:p" opt; do
case $opt in
+p) no_diff=0;;
p) no_diff=1;;
d) dir=$OPTARG;;
c) callback=$OPTARG;;
t)
if [[ $OPTARG != (|+|-)<->(|.<->)(|[eE](|-|+)<->) ]]; then
print -ru2 -- "gitstatus_query: invalid -t argument: $OPTARG"
return 1
fi
timeout=OPTARG
;;
\?) print -ru2 -- "gitstatus_query: invalid option: $OPTARG" ; return 1;;
:) print -ru2 -- "gitstatus_query: missing required argument: $OPTARG"; return 1;;
*) print -ru2 -- "gitstatus_query: invalid option: $opt" ; return 1;;
esac
done
if (( OPTIND != ARGC )); then
print -ru2 -- "gitstatus_query: exactly one positional argument is required"
return 1
fi
local name=$*[OPTIND]
if [[ $name != [[:IDENT:]]## ]]; then
print -ru2 -- "gitstatus_query: invalid positional argument: $name"
return 1
fi
(( _GITSTATUS_STATE_$name == 2 )) || return
if [[ -z $GIT_DIR ]]; then
[[ $dir == /* ]] || dir=${(%):-%/}/$dir
else
[[ $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
(( ++_GITSTATUS_NUM_INFLIGHT_$name ))
if (( timeout == 0 )); then
typeset -g VCS_STATUS_RESULT=tout
_gitstatus_clear$fsuf
else
while true; do
_gitstatus_process_response$fsuf $name $timeout $req_id || return
[[ $VCS_STATUS_RESULT == *-async ]] || break
done
fi
[[ $VCS_STATUS_RESULT != tout || -n $callback ]]
}
# If the last call to gitstatus_query timed out (VCS_STATUS_RESULT=tout), wait for the callback
# to be called. Otherwise do nothing.
#
# Usage: gitstatus_process_results [OPTION]... NAME
#
# -t FLOAT Timeout in seconds. Negative value means infinity. Will block for at most this long.
#
# Returns an error only when invoked with incorrect arguments and when gitstatusd isn't running or
# broken.
#
# 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"${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
while getopts ":t:" opt; do
case $opt in
t)
if [[ $OPTARG != (|+|-)<->(|.<->)(|[eE](|-|+)<->) ]]; then
print -ru2 -- "gitstatus_process_results: invalid -t argument: $OPTARG"
return 1
fi
timeout=OPTARG
;;
\?) print -ru2 -- "gitstatus_process_results: invalid option: $OPTARG" ; return 1;;
:) print -ru2 -- "gitstatus_process_results: missing required argument: $OPTARG"; return 1;;
*) print -ru2 -- "gitstatus_process_results: invalid option: $opt" ; return 1;;
esac
done
if (( OPTIND != ARGC )); then
print -ru2 -- "gitstatus_process_results: exactly one positional argument is required"
return 1
fi
local name=$*[OPTIND]
if [[ $name != [[:IDENT:]]## ]]; then
print -ru2 -- "gitstatus_process_results: invalid positional argument: $name"
return 1
fi
(( _GITSTATUS_STATE_$name == 2 )) || return
while (( _GITSTATUS_NUM_INFLIGHT_$name )); do
_gitstatus_process_response$fsuf $name $timeout '' || return
[[ $VCS_STATUS_RESULT == *-async ]] || break
done
return 0
}
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"${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
(( $2 >= 0 )) && timeout=-t$2 && [[ -t $resp_fd ]]
sysread $timeout -i $resp_fd 'buf[$#buf+1]' || {
if (( $? == 4 )); then
if [[ -n $req_id ]]; then
typeset -g VCS_STATUS_RESULT=tout
_gitstatus_clear$fsuf
fi
return 0
else
gitstatus_stop$fsuf $name
return 1
fi
}
while [[ $buf != *$'\x1e' ]]; do
if ! sysread -i $resp_fd 'buf[$#buf+1]'; then
gitstatus_stop$fsuf $name
return 1
fi
done
local s
for s in ${(ps:\x1e:)buf}; do
local -a resp=("${(@ps:\x1f:)s}")
if (( resp[2] )); then
if [[ $resp[1] == $req_id' '* ]]; then
typeset -g VCS_STATUS_RESULT=ok-sync
else
typeset -g VCS_STATUS_RESULT=ok-async
fi
for VCS_STATUS_WORKDIR \
VCS_STATUS_COMMIT \
VCS_STATUS_LOCAL_BRANCH \
VCS_STATUS_REMOTE_BRANCH \
VCS_STATUS_REMOTE_NAME \
VCS_STATUS_REMOTE_URL \
VCS_STATUS_ACTION \
VCS_STATUS_INDEX_SIZE \
VCS_STATUS_NUM_STAGED \
VCS_STATUS_NUM_UNSTAGED \
VCS_STATUS_NUM_CONFLICTED \
VCS_STATUS_NUM_UNTRACKED \
VCS_STATUS_COMMITS_AHEAD \
VCS_STATUS_COMMITS_BEHIND \
VCS_STATUS_STASHES \
VCS_STATUS_TAG \
VCS_STATUS_NUM_UNSTAGED_DELETED \
VCS_STATUS_NUM_STAGED_NEW \
VCS_STATUS_NUM_STAGED_DELETED \
VCS_STATUS_PUSH_REMOTE_NAME \
VCS_STATUS_PUSH_REMOTE_URL \
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
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))
if (( dirty_max_index_size >= 0 && VCS_STATUS_INDEX_SIZE > dirty_max_index_size )); then
typeset -gi \
VCS_STATUS_HAS_UNSTAGED=-1 \
VCS_STATUS_HAS_CONFLICTED=-1 \
VCS_STATUS_HAS_UNTRACKED=-1
else
typeset -gi \
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
if [[ $resp[1] == $req_id' '* ]]; then
typeset -g VCS_STATUS_RESULT=norepo-sync
else
typeset -g VCS_STATUS_RESULT=norepo-async
fi
_gitstatus_clear$fsuf
fi
(( --_GITSTATUS_NUM_INFLIGHT_$name ))
[[ $VCS_STATUS_RESULT == *-async ]] && emulate zsh -c "${resp[1]#* }"
done
return 0
}
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
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] )) || [[ $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
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 _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|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
kill -- -$pgid
}
} &!
(( lock_fd == -1 )) && return
{
if zsystem flock -- $file_prefix.lock && [[ -e $file_prefix.lock ]]; then
zf_rm -f -- $file_prefix.lock $file_prefix.fifo
kill -- -$pgid
fi
} &!
}
# Starts gitstatusd in the background. Does nothing and succeeds if gitstatusd is already running.
#
# Usage: gitstatus_start [OPTION]... NAME
#
# -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.
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
local -i async=0
local -a args=()
local -i dirty_max_index_size=-1
while getopts ":t:s:u:c:d:m:eaUWD" opt; do
case $opt in
a) async=1;;
+a) async=0;;
t)
if [[ $OPTARG != (|+)<->(|.<->)(|[eE](|-|+)<->) ]] || (( ${timeout::=OPTARG} <= 0 )); then
print -ru2 -- "gitstatus_start: invalid -t argument: $OPTARG"
return 1
fi
;;
s|u|c|d|m)
if [[ $OPTARG != (|-|+)<-> ]]; then
print -ru2 -- "gitstatus_start: invalid -$opt argument: $OPTARG"
return 1
fi
args+=(-$opt $OPTARG)
[[ $opt == m ]] && dirty_max_index_size=OPTARG
;;
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;;
*) print -ru2 -- "gitstatus_start: invalid option: $opt" ; return 1;;
esac
done
if (( OPTIND != ARGC )); then
print -ru2 -- "gitstatus_start: exactly one positional argument is required"
return 1
fi
local name=$*[OPTIND]
if [[ $name != [[:IDENT:]]## ]]; then
print -ru2 -- "gitstatus_start: invalid positional argument: $name"
return 1
fi
local -i lock_fd resp_fd stderr_fd
local file_prefix xtrace=/dev/null daemon_log=/dev/null culprit
{
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
() {
if [[ $xtrace != /dev/null && -o no_xtrace ]]; then
exec {stderr_fd}>&2 || return
exec 2>>$xtrace || return
setopt xtrace
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
fi
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
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
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
gitstatus_stop$fsuf $name
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
}
}
# Stops gitstatusd if it's running.
#
# Usage: gitstatus_stop NAME.
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
fi
local name=$1
if [[ $name != [[:IDENT:]]## ]]; then
print -ru2 -- "gitstatus_stop: invalid positional argument: $name"
return 1
fi
local state_var=_GITSTATUS_STATE_$name
local req_fd_var=_GITSTATUS_REQ_FD_$name
local resp_fd_var=_GITSTATUS_RESP_FD_$name
local lock_fd_var=_GITSTATUS_LOCK_FD_$name
local client_pid_var=_GITSTATUS_CLIENT_PID_$name
local daemon_pid_var=GITSTATUS_DAEMON_PID_$name
local inflight_var=_GITSTATUS_NUM_INFLIGHT_$name
local file_prefix_var=_GITSTATUS_FILE_PREFIX_$name
local dirty_max_index_size_var=_GITSTATUS_DIRTY_MAX_INDEX_SIZE_$name
local req_fd=${(P)req_fd_var}
local resp_fd=${(P)resp_fd_var}
local lock_fd=${(P)lock_fd_var}
local daemon_pid=${(P)daemon_pid_var}
local file_prefix=${(P)file_prefix_var}
local cleanup=_gitstatus_cleanup_$name-$fsuf
local process=_gitstatus_process_response_$name-$fsuf
if (( $+functions[$cleanup] )); then
add-zsh-hook -d zshexit $cleanup
unfunction -- $cleanup
fi
if (( $+functions[$process] )); then
[[ -n $resp_fd ]] && zle -F $resp_fd
unfunction -- $process
fi
[[ $daemon_pid == <1-> ]] && kill -- -$daemon_pid 2>/dev/null
[[ $file_prefix == /* ]] && zf_rm -f -- $file_prefix.lock $file_prefix.fifo
[[ $lock_fd == <1-> ]] && zsystem flock -u $lock_fd
[[ $req_fd == <1-> ]] && exec {req_fd}>&-
[[ $resp_fd == <1-> ]] && exec {resp_fd}>&-
unset $state_var $req_fd_var $lock_fd_var $resp_fd_var $client_pid_var $daemon_pid_var
unset $inflight_var $file_prefix_var $dirty_max_index_size_var
unset VCS_STATUS_RESULT
_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"${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
fi
local name=$1
if [[ $name != [[:IDENT:]]## ]]; then
print -ru2 -- "gitstatus_check: invalid positional argument: $name"
return 1
fi
(( _GITSTATUS_STATE_$name == 2 ))
}
(( ${#_gitstatus_opts} )) && setopt ${_gitstatus_opts[@]}
'builtin' 'unset' '_gitstatus_opts'

View File

@ -0,0 +1,103 @@
# 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=$'\e[0m' # no color
local clean=$'\e[38;5;076m' # green foreground
local untracked=$'\e[38;5;014m' # teal foreground
local modified=$'\e[38;5;011m' # yellow foreground
local conflicted=$'\e[38;5;196m' # 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
# 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)

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

View File

@ -0,0 +1,34 @@
# 0
#
# 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.3.1"; sha256="c96baef70b81b5a1d46adcc9e93721eaf4bdc295562bdd2baf210a6b416b9911";
uname_s_glob="cygwin_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="18b5be49f6eb9ff1cf25e76d6f2333c7402e686e05ce5b88ca107c80504210d8";
uname_s_glob="darwin"; uname_m_glob="arm64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="0394e2ac880c1e190ace0346499d4670861297ecc2f84315ecb8ba3c98aa68d9";
uname_s_glob="darwin"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="26d582fe9a0b2090c28e84e5e32a6d42d6988cedff51e41ec5f789512c53b0fc";
uname_s_glob="freebsd"; uname_m_glob="amd64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="54a823373458a0908187ba8d1c5b8921015c844811916451674cc09fbdff88bb";
uname_s_glob="linux"; uname_m_glob="aarch64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="4e0a506eafb14b009cf6670f0e11399ac7e765cad17b9fcf38ef65516d248bfa";
uname_s_glob="linux"; uname_m_glob="armv6l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="4cc78bf24c6cfb4580ae48b13e1f26b6b00869c612b5d662632f7561f7475252";
uname_s_glob="linux"; uname_m_glob="armv7l"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="80f5ebc8efe62a0c322e92d15d469e80f9d3ce967c5d5118138674c47f96747b";
uname_s_glob="linux"; uname_m_glob="armv8l"; file="gitstatusd-${uname_s}-aarch64"; version="v1.3.1"; sha256="4e0a506eafb14b009cf6670f0e11399ac7e765cad17b9fcf38ef65516d248bfa";
uname_s_glob="linux"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="ba506fbecf4a4430533e661bb63c7b77f6b4836ea013bdf8a6eabeace456f3b9";
uname_s_glob="linux"; uname_m_glob="ppc64le"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="1bf907db28ac7d6516add51be47b73b1854b84ecf46de56ccb1479e6a7e29ed2";
uname_s_glob="linux"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="91bcc1efafff8c896e8f172ff624d9407494f7a26b4ad1bf573f52623be2ca91";
uname_s_glob="msys_nt-10.0"; uname_m_glob="i686"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="618d2425c6a22fa3762fe6fe252f9ddb4ed9138df1377e48b2f119cd4875f400";
uname_s_glob="msys_nt-10.0"; uname_m_glob="x86_64"; file="gitstatusd-${uname_s}-${uname_m}"; version="v1.3.1"; sha256="bdfae7a7c0fd83d0214a7eabde3b7d8709336bd08697a74d48bea4a04c352676";
# Fallbacks to official gitstatusd binaries.
uname_s_glob="cygwin_nt-*"; uname_m_glob="i686"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.3.1"; sha256="c96baef70b81b5a1d46adcc9e93721eaf4bdc295562bdd2baf210a6b416b9911";
uname_s_glob="cygwin_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-cygwin_nt-10.0-${uname_m}"; version="v1.3.1"; sha256="18b5be49f6eb9ff1cf25e76d6f2333c7402e686e05ce5b88ca107c80504210d8";
uname_s_glob="mingw32_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.3.1"; sha256="618d2425c6a22fa3762fe6fe252f9ddb4ed9138df1377e48b2f119cd4875f400";
uname_s_glob="mingw32_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.3.1"; sha256="bdfae7a7c0fd83d0214a7eabde3b7d8709336bd08697a74d48bea4a04c352676";
uname_s_glob="mingw64_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.3.1"; sha256="618d2425c6a22fa3762fe6fe252f9ddb4ed9138df1377e48b2f119cd4875f400";
uname_s_glob="mingw64_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.3.1"; sha256="bdfae7a7c0fd83d0214a7eabde3b7d8709336bd08697a74d48bea4a04c352676";
uname_s_glob="msys_nt-*"; uname_m_glob="i686"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.3.1"; sha256="618d2425c6a22fa3762fe6fe252f9ddb4ed9138df1377e48b2f119cd4875f400";
uname_s_glob="msys_nt-*"; uname_m_glob="x86_64"; file="gitstatusd-msys_nt-10.0-${uname_m}"; version="v1.3.1"; sha256="bdfae7a7c0fd83d0214a7eabde3b7d8709336bd08697a74d48bea4a04c352676";

View File

@ -0,0 +1,365 @@
#!/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-*)
flags+=(-s)
tmp='/c/tmp'
env='MSYSTEM=MSYS'
intro='pacman -Syu --noconfirm; 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
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

@ -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_

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

Some files were not shown because too many files have changed in this diff Show More