faster ยป random things to gain velocity

faster !!!

random things to be faster

big thank you 
          @keradus

https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/pull/7777

Fixer is a great and widely used tool, but comparing to other modern PHP tools it lacked one crucial thing: ability to utilise more CPU cores. Until now ๐Ÿฅณ !

hi

Richard Heine

95f0efe 2024-07-22 12:32:14 +0200 95f0efe stan .......
e990c3d 2024-07-22 12:24:55 +0200 e990c3d stan ......
7c3da72 2024-07-22 12:14:19 +0200 7c3da72 stan .....
a58533e 2024-07-22 12:06:22 +0200 a58533e stan ....
a7ffe04 2024-07-22 11:58:01 +0200 a7ffe04 stan ..
5875eec 2024-07-22 10:31:16 +0200 5875eec cs fixer .
7e379cb 2024-07-22 10:26:33 +0200 7e379cb stan .
2eed927 2024-07-22 10:16:43 +0200 2eed927 correct versions array

patat

a presentation tool built by Jasper Van der Jeugt

https://github.com/jaspervdj/patat

---
title: example
author: Richard Heine
patat:
    margins:
        left: 4
    transition:
        type: slide
...
---

# slide 1

---

# slide 2
fzf

fzf

Itโ€™s an interactive filter program for any kind of list; files, command history, processes, hostnames, bookmarks, git commits, etc. It implements a โ€œfuzzyโ€ matching algorithm, so you can quickly type in patterns with omitted characters and still get the results you want.

if invoked without any arguments or input

  faster/parts/30-git/20-conditional-includes.md
  faster/parts/30-git/10-tags.md
  faster/parts/10-patat/00-patat.md
  faster/parts/20-fzf.md
  faster/parts/01-intro.md
  faster/parts/00-header.md
  faster/compile.md
  faster/Makefile
  .idea/workspace.xml
  .idea/vcs.xml
  .idea/slides.iml
  .idea/php.xml
  .idea/modules.xml
  .idea/git_toolbox_blame.xml
  .idea/.gitignore
โ–Œ .gitignore
  16/16 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
>
โ–Œ faster/parts/30-git/20-conditional-includes.md
  faster/parts/30-git/10-tags.md
  2/16 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
> tags

will return

$ fzf
faster/parts/30-git/20-conditional-includes.md
$

reading from stdin

$ (echo foo-1-baz; echo foo-2-bar; echo foo-1-wtf) | fzf
  foo-1-wtf
  foo-2-bar
โ–Œ foo-1-baz
  3/3 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
>
โ–Œ foo-1-baz
  1/3 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
>  1z
$ (echo foo-1-baz; echo foo-2-bar; echo foo-1-wtf) | fzf
foo-1-baz

customizing fzf

$ fzf --help

    -x, --extended          Extended-search mode
    -e, --exact             Enable Exact-match
    -i, --ignore-case       Case-insensitive match (default: smart-case match)
    --literal               Do not normalize latin script letters before matching
    -n, --nth=N[,..]        Comma-separated list of field index expressions
    -d, --delimiter=STR     Field delimiter regex (default: AWK-style)
    --tail=NUM              Maximum number of items to keep in memory
    --track                 Track the current selection when the result is updated
    --tac                   Reverse the order of the input
    --disabled              Do not perform search
    --tiebreak=CRI[,..]     Comma-separated list of sort criteria to apply
    -m, --multi[=MAX]       Enable multi-select with tab/shift-tab
    --cycle                 Enable cyclic scroll
    --wrap                  Enable line wrap
    [...]
git

git - tags sorting

$ git tag 4.10.0
fatal: tag '4.10.0' already exists
$ git tag
4.1.0
4.10.0
4.11.0
4.2.0
4.3.0
4.4.0
4.5.0
4.6.0
4.7.0
4.8.0
4.9.0
$ git config --global tag.sort version:refname
$ git tag
4.8.1
4.8.2
4.8.3
4.8.4
4.9.0
4.10.0
4.11.0

git - conditional includes

~/.gitconfig

[include]
  path = ~/.config/git/generic

[includeIf "gitdir:~/src/work/"]
  path = ~/.config/git/work

[includeIf "gitdir:~/src/private/"]
  path = ~/.config/git/private
  • gitdir & gitdir/i
  • onbranch [includeIf "onbranch:foo-branch"]
  • hasconfig:remote.*.url [includeIf "hasconfig:remote.*.url:https://example.com/**"]
    • As for the naming of this keyword, it is for forwards compatibility with a naming scheme that supports more variable-based include conditions, but currently Git only supports the exact keyword described above.

~/.config/git/generic

[init]
  defaultBranch = main
[core]
  hooksPath = ~/.githooks
  abbrev = 7
  pager = less -F -X -I
[rerere]
  enabled = true
  autoupdate = true
[push]
  default = current
[pull]
  rebase = true
  default = current
[tig "color"]
  cursor = default blue

~/.config/git/private

[user]
  email = easteregg@verfriemelt.org
  name = เฒ _เฒ 

~/.config/git/work

[user]
  email = rheine@work.org
  name = Richard Heine
[core]
    sshCommand = ssh -i ~/.ssh/id_rsa.work
    excludesfile = ~/.config/git/work.gitignore

git - conditional includes

commit in repository in /src/privat

commit 75f56e1 (HEAD -> main)
Author: เฒ _เฒ  <easteregg@verfriemelt.org>
Date:   2024-09-27 23:41:07 +0200

commit in repository in /src/work

commit 75f56e1 (HEAD -> main)
Author: Richard Heine <rheine@work.org>
Date:   2024-09-27 08:35:01 +0200

git rev-parse

git-rev-parse - Pick out and massage parameters

So You Think You Know Git - FOSDEM 2024

https://www.youtube.com/watch?v=aolI_Rz0ZqY

$ git rev-parse HEAD
04bdae471848b34890a219699c1ad5105f2de279
$  git rev-parse --short HEAD
04bdae4
$ cd $(mktemp -d)
$ git rev-parse --is-inside-work-tree
fatal: not a git repository (or any parent up to mount point /)

$ git init .
Initialized empty Git repository in /tmp/tmp.7lKOFtTB5X/.git/

$ git rev-parse --is-inside-work-tree
true

in scripts

git rev-parse --is-inside-work-tree 2>&1 >/dev/null

if [ $? -ne 0 ]
then
    echo 'not a git repository'
    exit 1
fi

git rev-parse โ€“show-toplevel

$ cd /example
$ git init .
$ mkdir foo/bar/what -p && cd foo/bar/what 
$ git rev-parse --show-toplevel
/example

git describe

Give an object a human readable name based on an available ref

The command finds the most recent tag that is reachable from a commit. If the tag points to the commit, then only the tag is shown. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit. The result is a โ€œhuman-readableโ€ object name which can also be used to identify the commit to other git commands.

$ git tag 0.0.1 HEAD~2
$ git describe --tags
0.0.1-2-ge2f5a07

By default (without โ€“all or โ€“tags) git describe only shows annotated tags.

$ git tag 0.0.2
$ git describe --tags
0.0.2

useful for container image tagging and versions in footers

$ git describe --tags > .version
$ docker build . -t service-$(git describe --tags)

git for-each-ref

Iterate over all refs that match <pattern> and show them according to the given <format>, after sorting them according to the given set of <key>. If <count> is given, stop after showing that many refs. The interpolated values in <format> can optionally be quoted as string literals in the specified host language allowing their direct evaluation in that language.

$ git for-each-ref
e2f5a0786c0bc57269a9b5ec654581fac4624119 commit refs/heads/JIRA-1337/implement-something-nice
e2f5a0786c0bc57269a9b5ec654581fac4624119 commit refs/heads/main
565bee11eac5e4cd68771ff9239b1f2371f255a4 commit refs/remotes/origin/main
75f56e13b0add68ba2c1cfc9b30d1c398978100a commit refs/tags/0.0.1
e2f5a0786c0bc57269a9b5ec654581fac4624119 commit refs/tags/0.0.2

filter for specific refs

$ git for-each-ref refs/tags
75f56e13b0add68ba2c1cfc9b30d1c398978100a commit refs/tags/0.0.1
e2f5a0786c0bc57269a9b5ec654581fac4624119 commit refs/tags/0.0.2
$ git for-each-ref refs/heads refs/remotes
e2f5a0786c0bc57269a9b5ec654581fac4624119 commit refs/heads/JIRA-1337/implement-something-nice
565bee11eac5e4cd68771ff9239b1f2371f255a4 commit refs/heads/main
565bee11eac5e4cd68771ff9239b1f2371f255a4 commit refs/remotes/origin/main

use --format with refname in order to get useful output - no awk required

refname > [โ€ฆ] If lstrip= (rstrip=) is appended, strips slash-separated path components from the front (back) of the refname (e.g.ย %(refname:lstrip=2) turns refs/tags/foo into foo and %(refname:rstrip=2) turns refs/tags/foo into refs). If is a negative number, strip as many path components as necessary from the specified end to leave - path components (e.g.ย %(refname:lstrip=-2) turns refs/tags/foo into tags/foo and %(refname:rstrip=-1) turns refs/tags/foo into refs). When the ref does not have enough components, the result becomes an empty string if stripping with positive , or it becomes the full refname if stripping with negative . Neither is an error.

$ git for-each-ref --format '%(refname:lstrip=2)' refs/heads
JIRA-1337/implement-something-nice
main
tig

git - tig

What is Tig?

Tig is an ncurses-based text-mode interface for git. It functions mainly as a Git repository browser, but can also assist in staging changes for commit at chunk level and act as a pager for output from various Git commands.

2024-09-27 13:22 +0000 Not Committed Yet o Unstaged changes
2024-09-27 15:21 +0200 เฒ _เฒ                o [main] includes
2024-09-27 15:03 +0200 เฒ _เฒ                o drop faster.md in favor of compile.md
2024-09-27 14:41 +0200 เฒ _เฒ                I init


[main] Unstaged changes                                                                             100%
2024-09-27 13:22 +0000 Not Committed Yet o Unstaged โ”‚commit e62b21e
2024-09-27 15:21 +0200 เฒ _เฒ                o [main] inโ”‚Author:     เฒ _เฒ  <easteregg@verfriemelt.org>
2024-09-27 15:03 +0200 เฒ _เฒ                o drop fastโ”‚AuthorDate: 2024-09-27 15:03:33 +0200
2024-09-27 14:41 +0200 เฒ _เฒ                I init     โ”‚Commit:     เฒ _เฒ  <easteregg@verfriemelt.org>
                                                    โ”‚CommitDate: 2024-09-27 15:03:33 +0200
                                                    โ”‚
                                                    โ”‚    drop faster.md in favor of compile.md
                                                    โ”‚---
                                                    โ”‚ .gitignore                       |   1 +
                                                    โ”‚ faster/Makefile                  |   8 +-
                                                    โ”‚ faster/faster.md                 | 202 ----------
                                                    โ”‚ faster/parts/30-git/10-tags.md   |   1 +
                                                    โ”‚ .../20-conditional-includes.md   |  88 ++--
                                                    โ”‚ 5 files changed, 71 insertions(+), 229 deletions(-
                                                    โ”‚
                                                    โ”‚diff --git a/.gitignore b/.gitignore
[main] e62b21e3e60f06f86095a58c96c44dc757f22e51 100%โ”‚[diff] e62b21e - line 1 of 365                   4%

tig blame $filename

75f56e1 เฒ _เฒ  2024-09-27 14:41 +0200   1โ”‚ # git - conditional includes
75f56e1 เฒ _เฒ  2024-09-27 14:41 +0200   2โ”‚
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200   3โ”‚ **~/.gitconfig**
75f56e1 เฒ _เฒ  2024-09-27 14:41 +0200   4โ”‚ ```bash
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200   5โ”‚ [include]
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200   6โ”‚   path = ~/.config/git/generic
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200   7โ”‚
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200   8โ”‚ [includeIf "gitdir:~/src/work/"]
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200   9โ”‚   path = ~/.config/git/work
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200  10โ”‚
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200  11โ”‚ [includeIf "gitdir:~/src/private/"]
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200  12โ”‚   path = ~/.config/git/private
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200  13โ”‚ ```
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200  14โ”‚
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200  15โ”‚ . . .
e62b21e เฒ _เฒ  2024-09-27 15:03 +0200  16โ”‚
[blame] faster/parts/30-git/20-conditional-includes.md - line 7 of 79                                20%
lets put it together 

fzf + cd

listing all your projects

$ find $HOME/src -mindepth 2 -maxdepth 2 | cut -d '/' -f 5-
private/phpstan-src
private/infection
private/slides
private/symfony1
private/phpunit
private/PHP-CS-Fixer
private/symfony
private/wrapped
private/timer
private/sachsencacher.de
work/frankenphp
work/matomo
work/laravel

combine with the cd command & store as an alias

$ alias gg='cd $HOME/src/"$(find $HOME/src -mindepth 2 -maxdepth 2 | cut -d '/' -f 5- | sort -r | fzf)"'

fzf + tig

blame

$ tig blame -- $(fzf)

create an alias

$ alias ffb='tig blame -- $(fzf)'
  • fast blaming via $ ffb
  • navigate with , to jump to selected parent
  • use < to jump back

fzf & git for-each-ref ยป check โ€™dis out!

give me branches

$ git branch something lame
$ git for-each-ref --format '%(refname:lstrip=2)' refs/heads
JIRA-1337/implement-something-lame
JIRA-1337/implement-something-nice
main

filter with fzf

$ git for-each-ref --format '%(refname:lstrip=2)' refs/heads | fzf
  main
  JIRA-1337/implement-something-lame
โ–Œ JIRA-1337/implement-something-nice
  3/3 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
>

sort it better

$ git for-each-ref --format '%(refname:lstrip=2)' refs/heads | \
    awk '{ print length(), $0 | "sort -n" }' | \
    cut -d ' ' -f2-
main
JIRA-1337/implement-something-lame
JIRA-1337/implement-something-nice

fzf will take input in reverse order, so we want the shortest branchname at the top

$ (echo a; echo b; echo c;) | fzf
  c
  b
โ–Œ a
  3/3 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
$ git for-each-ref --format '%(refname:lstrip=2)' refs/heads | \
    awk '{ print length(), $0 | "sort -n" }' | \
    cut -d ' ' -f2- | \
    fzf
  JIRA-1337/implement-something-nice
  JIRA-1337/implement-something-lame
โ–Œ main
  3/3 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
>

remote branches are missing. so we need to get them too

$ git fetch --all
From github.com:verfriemelt-dot-org/slides
* [new branch]      JIRA-31337/something-even-nicer -> origin/JIRA-31337/something-even-nicer

after fetching we can get all refs in refs/remote and refs/heads

$ git for-each-ref --format '%(refname:lstrip=2)' refs/heads refs/remotes
JIRA-1337/implement-something-lame
JIRA-1337/implement-something-nice
main
origin/JIRA-31337/something-even-nicer
origin/main

but checking checking out branches which start with origin/ does not yield the desired result

$ git checkout origin/JIRA-31337/something-even-nicer
Note: switching to 'origin/JIRA-31337/something-even-nicer'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by switching back to a branch.

so we must get rid of the origin/ prefix for that

$ ( git for-each-ref --format '%(refname:lstrip=2)' refs/heads; \
git for-each-ref --format '%(refname:lstrip=3)' refs/remotes/origin ) | \
    awk '{ print length(), $0 | "sort -n" }' | \
    cut -d ' ' -f2- | \
    fzf
main
main
JIRA-31337/something-even-nicer
JIRA-1337/implement-something-lame
JIRA-1337/implement-something-nice

add in uniq to get rid of branches already present in refs/heads

$ ( git for-each-ref --format '%(refname:lstrip=2)' refs/heads; \
    git for-each-ref --format '%(refname:lstrip=3)' refs/remotes/origin ) | \
    awk '{ print length(), $0 | "sort -n" }' | uniq | \
    cut -d ' ' -f2- | \
    fzf
  JIRA-1337/implement-something-nice
  JIRA-1337/implement-something-lame
  JIRA-31337/something-even-nicer
โ–Œ main
  4/4 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
>

combine that with git checkout and magic

$ git checkout $(( git for-each-ref --format '%(refname:lstrip=2)' refs/heads; \
    git for-each-ref --format '%(refname:lstrip=3)' refs/remotes/origin ) | \
    awk '{ print length(), $0 | "sort -n" }' | uniq | \
    cut -d ' ' -f2- | \
    fzf)
  JIRA-1337/implement-something-nice
  JIRA-1337/implement-something-lame
โ–Œ JIRA-31337/something-even-nicer
  main
  4/4 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
branch 'JIRA-31337/something-even-nicer' set up to track 'origin/JIRA-31337/something-even-nicer'.
Switched to a new branch 'JIRA-31337/something-even-nicer'

thats too unwieldy for an alias, lets put that into a script instead

$ chmod +x ~/bin/git-branch-picker 
$ head ~/bin/git-branch-picker
#!/usr/bin/env bash

( git for-each-ref --format '%(refname:lstrip=2)' refs/heads; \
    git for-each-ref --format '%(refname:lstrip=3)' refs/remotes/origin ) | \
    awk '{ print length(), $0 | "sort -n" }' | uniq | \
    cut -d ' ' -f2- | \
    fzf --preview="git -c color.ui=always log --no-merges -n 100 --oneline \$( git show-ref {} -s | head -n1 ) --"

alias with

$ alias gb="git checkout $(git-branch-picker)"

note the extra options

fzf --preview="git -c color.ui=always log --no-merges -n 100 --oneline \$( git show-ref {} -s | head -n1 ) --"
                                     โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ
                                     โ”‚ d741e02 git rev-parse --toplevel โ”‚
                                     โ”‚ b2b98c1 more examples            โ”‚
                                     โ”‚ 6506944 more seperation          โ”‚
                                     โ”‚ 50efe70 fzf + cd                 โ”‚
                                     โ”‚ 2934d57 fzf + git for-each-ref   โ”‚
                                     โ”‚ 30e42c1 fzf + git for-each-ref   โ”‚
                                     โ”‚ ded3faf fzf + git for-each-ref   โ”‚
                                     โ”‚ c7caae9 fzf + git for-each-ref   โ”‚
                                     โ”‚ 5a65ea0 git for-each-ref         โ”‚
                                     โ”‚ 565bee1 more slides              โ”‚
                                     โ”‚ e2f5a07 includes                 โ”‚
                                     โ”‚ e62b21e drop faster.md in favor  โ”‚
                                     โ”‚ 75f56e1 init                     โ”‚
                                     โ”‚                                  โ”‚
                                     โ”‚                                  โ”‚
                                     โ”‚                                  โ”‚
                                     โ”‚                                  โ”‚
  JIRA-1337/implement-something-nice โ”‚                                  โ”‚
  JIRA-1337/implement-something-lame โ”‚                                  โ”‚
  JIRA-31337/something-even-nicer    โ”‚                                  โ”‚
โ–Œ main                               โ”‚                                  โ”‚
  4/4 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚                                  โ”‚
>                                    โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ

fzf + git cherry-pick

$ git cherry-pick $(git log --oneline --all --reverse | fzf | cut -f1 -d ' ')

fzf + git merge

$ git merge $(git-branch-picker)

working with multiple php versions

the non-docker user

$ php public/index.php

Fatal error: Composer detected issues in your platform: Your Composer dependencies require a 
PHP version ">= 8.3.0". You are running 8.2.24. in vendor/composer/platform_check.php on line 22
$ ls /usr/bin/php*
/usr/bin/php5.6
/usr/bin/php7.0
/usr/bin/php7.1
/usr/bin/php7.2
/usr/bin/php7.3
/usr/bin/php7.4
/usr/bin/php8.0
/usr/bin/php8.1
/usr/bin/php8.2
/usr/bin/php8.3
/usr/bin/php8.4

for debian & ubuntu based systems: https://deb.sury.org/

$ sudo update-alternatives --config php
There are 11 choices for the alternative php (providing /usr/bin/php).

  Selection    Path             Priority   Status
------------------------------------------------------------
* 0            /usr/bin/php8.4   84        auto mode
  1            /usr/bin/php5.6   56        manual mode
  2            /usr/bin/php7.0   70        manual mode
  3            /usr/bin/php7.1   71        manual mode
  4            /usr/bin/php7.2   72        manual mode
  5            /usr/bin/php7.3   73        manual mode
  6            /usr/bin/php7.4   74        manual mode
  7            /usr/bin/php8.0   80        manual mode
  8            /usr/bin/php8.1   81        manual mode
  9            /usr/bin/php8.2   82        manual mode
  10           /usr/bin/php8.3   83        manual mode
  11           /usr/bin/php8.4   84        manual mode

Press <enter> to keep the current choice[*], or type selection number:

lets hack a script for that

assumptions:

  • we want to run the minimal supported php version by a project
  • if in a project, we want to use the projects version specified in composer.json, otherwise the system default
  • version constraints for php often look like this
    • "php": ">=8.1"
    • "php": "^7.4 || ^8.1"
    • should be enough to grab the first one, usually they are sorted most of the times
    • never seen "php": "^8.1 || ^7.4"

~/bin/php

#!/usr/bin/env bash

# we want to be able to specify the version we want to run at all times
if [[ -n "${PHP}" ]]
then
    exec php"$PHP" "$@"
fi

# system-default if nothing specified
exec /usr/bin/php "$@"
$ php
8.4.0RC3 (cli) (built: Nov  4 2024 23:24:44) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.4.0RC3, Copyright (c) Zend Technologies

overwriting with PHP env var

$ PHP=7.1 php --version
PHP 7.1.33-64+0~20240802.94+debian11~1.gbpa8e4d8 (cli) (built: Aug  2 2024 16:05:50) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies

in a project?

# read from git repositories composer.json
_is_repository=$(git rev-parse --is-inside-work-tree 2>/dev/null)

# use default if not the case
if [[ -z $_is_repository ]]
then
    exec /usr/bin/php "$@"
fi

checking for composer.json

_project_root=$( git rev-parse --show-toplevel )

# check for composer.json in project root
if [[ ! -f ${_project_root}/composer.json ]]
then
    exec /usr/bin/php "$@"
fi

getting the php version from composer.json

# read supported versions and pick the latest one via head -n1
version=$(jq -r .require.php <"${_project_root}/composer.json" 2>/dev/null | grep -o "[0-9].[0-9]" | tail -n1)

# check if version is empty
if [[ -z "$version" ]]
then
    exec /usr/bin/php "$@"
fi

# check if version requested is available or default to the oldest
if command -v php"$version">/dev/null
then
    exec php"$version" "$@"
fi

full script

#!/usr/bin/env bash

if [[ -n "${PHP}" ]]
then
    exec php"$PHP" "$@"
fi

_is_repository=$(git rev-parse --is-inside-work-tree 2>/dev/null)
if [[ -z $_is_repository ]]
then
    exec /usr/bin/php "$@"
fi

_project_root=$( git rev-parse --show-toplevel )
if [[ ! -f ${_project_root}/composer.json ]]
then
    exec /usr/bin/php "$@"
fi

version=$(jq -r .require.php <"${_project_root}/composer.json" 2>/dev/null| grep -o "[0-9].[0-9]" | tail -n1)
if [[ -z $version ]]
then
    exec /usr/bin/php "$@"
fi

if ! command -v php"$version">/dev/null
then
    exec /usr/bin/php "$@"
fi

exec php"$version" "$@"
git hooks 

git hooks

[core]
  hooksPath = ~/.githooks

https://git-scm.com/docs/githooks

Hooks are programs you can place in a hooks directory to trigger actions at certain points in gitโ€™s execution. Hooks that donโ€™t have the executable bit set are ignored.

By default the hooks directory is $GIT_DIR/hooks, but that can be changed via the core.hooksPath configuration variable.

hooks:

  • applypatch-msg
  • pre-applypatch
  • post-applypatch
  • pre-commit
  • pre-merge-commit
  • prepare-commit-msg
  • commit-msg
  • post-commit
  • pre-rebase
  • post-checkout
  • post-merge
  • pre-push
  • pre-receive
  • update
  • proc-receive
  • post-receive
  • post-update
  • reference-transaction
  • push-to-checkout
  • pre-auto-gc
  • post-rewrite
  • sendemail-validate
  • fsmonitor-watchman
  • p4-changelist
  • p4-prepare-changelist
  • p4-post-changelist
  • p4-pre-submit
  • post-index-change

githook ยป prepare-commit-msg

$ head -25 .git/hooks/prepare-commit-msg.sample
#!/bin/sh
#
# An example hook script to prepare the commit log message.
# Called by "git commit" with the name of the file that has the
# commit message, followed by the description of the commit
# message's source.  The hook's purpose is to edit the commit
# message file.  If the hook fails with a non-zero status,
# the commit is aborted.
#
# To enable this hook, rename this file to "prepare-commit-msg".

# This hook includes three examples. The first one removes the
# "# Please enter the commit message..." help message.
#
# The second includes the output of "git diff --name-status -r"
# into the message, just before the "git status" output.  It is
# commented because it doesn't cope with --amend or with squashed
# commits.
#
# The third example adds a Signed-off-by line to the message, that can
# still be edited.  This is rarely a good idea.

COMMIT_MSG_FILE=$1
COMMIT_SOURCE=$2
SHA1=$3

githook ยป prepare-commit-msg

#!/usr/bin/env bash

_git_repository_root=$( git rev-parse --show-toplevel )
_git_branchname=$( git symbolic-ref -q HEAD )

case $2 in
  "");;                # default commit call
  "message");;         # passed message from commit -m
  "template") exit 0;; # from configured msg template
  "squash")   exit 0;; # squash
  "merge" )   exit 0;; # merge
  "commit" )  exit 0;; # commit ammend or copied message via -c, -C <commitid>
  *)          exit 0;;
esac

if [[ ! -z $tick ]]
then
  sed -i "1s/^/${tick}: /" "$1"
  exit 0
fi

supported_projects=("FOO" "BAR" "JIRA")

exp='('
exp+=$( IFS="|" ; echo "${supported_projects[*]}" )
exp+=')'
exp+="-[0-9]+"

if [[ $_git_branchname =~ $exp ]]
then
  sed -i "1s/^/${BASH_REMATCH[0]}: /" "$1"
  exit 0
fi

never forget ticket numbers again

$ git checkout -b feature/JIRA-1337-fancy-task
$ git commit -m 'i did something' --allow-empty
[feature/JIRA-1337-fancy-task 563833b] JIRA-1337: i did something

$ git show 
commit 563833b (HEAD -> feature/JIRA-1337-fancy-task)
Author: Richard Heine <rheine@work.org>
Date:   2024-09-27 22:12:01 +0200

    JIRA-1337: i did something

overwrite with tick=JIRA-1 git commit -m 'foo'

$ tick=JIRA-1 git commit -m 'i did related to another jira' --allow-empty
[feature/JIRA-1337-fancy-task c27b82a] JIRA-1: i did related to another jira

$ git show
commit c27b82a (HEAD -> feature/JIRA-1337)
Author: Richard Heine <rheine@work.org>
Date:   2024-09-27 22:16:02 +0200

    JIRA-1: i did related to another jira

pre-commit-hook

what does this tell you?

95f0efe 2024-07-22 12:32:14 +0200 95f0efe stan .......
e990c3d 2024-07-22 12:24:55 +0200 e990c3d stan ......
7c3da72 2024-07-22 12:14:19 +0200 7c3da72 stan .....
a58533e 2024-07-22 12:06:22 +0200 a58533e stan ....
a7ffe04 2024-07-22 11:58:01 +0200 a7ffe04 stan ..
5875eec 2024-07-22 10:31:16 +0200 5875eec cs fixer .
7e379cb 2024-07-22 10:26:33 +0200 7e379cb stan .
2eed927 2024-07-22 10:16:43 +0200 2eed927 correct versions array

hint: typical pipelineruns at the time took about 3-5 minutes

what is required for pushing code and a green pipeline?

  • not push to a protected branch
  • code needs to be valid code
  • tests must pass
  • phpstan must not complain
  • rector rules must not be violated
  • csfixer must not complain

pre-commit-hook

The pre-commit hook is run first, before you even type in a commit message. Itโ€™s used to inspect the snapshot thatโ€™s about to be committed, to see if youโ€™ve forgotten something, to make sure tests run, or to examine whatever you need to inspect in the code. Exiting non-zero from this hook aborts the commit, although you can bypass it with git commit --no-verify.

how to start

~/.githooks/pre-commit

#!/bin/bash
script_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"

black=$'\e[30m'
red=$'\e[31m'
green=$'\e[32m'
yellow=$'\e[33m'
blue=$'\e[34m'
magenta=$'\e[35m'
cyan=$'\e[36m'
white=$'\e[37m'
reset=$'\e[0;0m'

_git_repository_root=$( git rev-parse --show-toplevel )
_git_branchname=$( git symbolic-ref -q HEAD )

if [[ $(pwd) =~ /work/ ]]
then
  is_work="true"
fi

# include all pre-commit parts
for hook in $script_dir/pre-commit.d/*
do
  if [[ ! -z $skip && $hook =~ $skip ]]
  then
    continue
  fi

  source "$hook"
done

pre-commit-hook ยป branch protection

~/.githooks/pre-commit.d/10-branchproection.sh

#!/usr/bin/env bash

# protect some important branches
branchprotection='^refs\/heads\/(release|develop|master|main|[0-9]+\.x$)'

if [[ $_git_branchname =~ $branchprotection  ]]
then
  echo "${red}$_git_branchname is writeable via PRs only" >&2
  exit 1;
fi

pre-commit-hook ยป linting

~/.githooks/pre-commit.d/15-lint.sh

#!/bin/bash

# find staged php files
staged_files=( $(git diff-index --cached --name-only --diff-filter=d HEAD | grep -E '\.php$') )
[[ ${#staged_files} -eq 0 ]] && return

# run
echo "php:     ${white}`command -v php`${reset}"
echo "version: ${white}$( php -r "echo PHP_VERSION;" )${reset}"
echo "checking ${green}${#staged_files[@]}${reset} files"
echo ""

result=0
for file in ${staged_files[@]}
do
    lintresult=$(php -l "$file")

    if [[ $? -eq 0 ]]
    then
      echo "    ${green}$file${reset}"
    else
      echo "    ${red}$file${reset}"
      echo $lintresult | sed 's/^/      /g'

      result=1
    fi
done

[[ $result -ne 0 ]] && exit $result

pre-commit-hook ยป tests

~/.githooks/pre-commit.d/20-tests.sh

#!/usr/bin/env bash

possible_config_paths=( "$_git_root/phpunit.xml" "$_git_root/phpunit.xml.dist" "$_git_root/phpunit.dist.xml" )

for file in ${possible_config_paths[@]}
do
  if [[ -f $file ]]
  then
      config_path=$file
      break
  fi
done
[[ -z $config_path ]] && return

possible_paths=( "$_git_root/vendor/bin/paratest" "$_git_root/vendor/bin/phpunit" )
for phpunit_path in ${possible_paths[@]}
do
  if [[ -f $phpunit_path ]]
  then
    phpunit_bin=$phpunit_path
    break
  fi
done

echo "bin: ${white}$phpunit_bin${reset}"
echo "conf: ${white}$config_path${reset}"
echo "version: ${white}$( $phpunit_bin --version )${reset}"
echo ""

$phpunit_bin -c $config_path
[[ $? -ne 0 ]] && exit 1

pre-commit-hook ยป phpstan

~/.githooks/pre-commit.d/30-phpstan.sh

#!/usr/bin/env bash

possible_config_paths=( "$_git_root/phpstan.neon" "$_git_root/phpstan.dist.neon" )
for file in ${possible_config_paths[@]}
do
  if [[ -f $file ]]
  then
      config_path=$file
      break
  fi
done
[[ -z $config_path ]] && return

staged_files=( $(git diff-index --cached --name-only --diff-filter=d HEAD | grep -E '\.php$') )
[[ ${#staged_files} -eq 0 ]] && return

possible_paths=( "$_git_root/bin/phpstan" "$_git_root/vendor/bin/phpstan" "$_git_root/lib/vendor/bin/phpstan" )
for phpstan_path in ${possible_paths[@]}
do
  if [[ -f $phpstan_path ]]
  then
    phpstan_bin=$phpstan_path
    break
  fi
done
[[ -z $phpstan_bin ]] && exit 1

echo "bin: ${white}$phpstan_bin${reset}"
echo "conf: ${white}$phpstan_conf${reset}"
echo "version: ${white}$( $phpstan_bin --version )${reset}"

nice -19 $phpstan_bin analyse -vv -c $phpstan_conf

[[ $? -ne 0 ]] && exit 1

pre-commit-hook ยป rector

~/.githooks/pre-commit.d/40-rector.sh

#!/usr/bin/env bash

# more of the same

nice -19 $rector_bin process --dry-run --config $rector_conf 

if [[ $? -gt 0 ]]
then
  exec < /dev/tty
  read -p 'apply changes? (Y/n) ' yn

  if [[ $yn = 'n' ]]
  then
    echo 'aborting commit'
    exit 1
  fi

  nice -19 $rector_bin process --config $rector_conf

  # readd files to commit
  git add ${staged_files[@]}
fi

pre-commit-hook ยป csfixer

~/.githooks/pre-commit.d/50-csfixer.sh


$csfixer_bin --config=$config_path fix --using-cache=no -v --path-mode=intersection --dry-run --diff -- ${staged_files[@]}

if [[ $? -gt 0 ]]
then

  exec < /dev/tty
  read -p 'fix codestlye? (Y/n) ' yn

  if [[ $yn = 'n' ]]
  then
    echo 'aborting commit'
    exit 1
  fi

  $csfixer_bin --config=$config_path fix --using-cache=no --path-mode=intersection -- ${staged_files[@]}

  # readd files to commit
  git add ${staged_files[@]}

fi

pre-commit-hook ยป final structure

$ tree ~/.githooks
.
โ”œโ”€โ”€ pre-commit
โ””โ”€โ”€ pre-commit.d
    โ”œโ”€โ”€ 10-branchproection.sh
    โ”œโ”€โ”€ 15-lint.sh
    โ”œโ”€โ”€ 20-tests.sh
    โ”œโ”€โ”€ 30-phpstan.sh
    โ”œโ”€โ”€ 40-rector.sh
    โ””โ”€โ”€ 50-csfixer.sh
  if [[ ! -z $skip && $hook =~ $skip ]]
  then
    continue
  fi
$ skip=. git commit -m 'fix typo' readme.md
$ skip=phpstan git commit -am 'resolv phpstan baseline'
entr
zsh

zsh

Zsh is a UNIX command interpreter (shell) usable as an interactive login shell and as a shell script command processor. Of the standard shells, zsh most closely resembles ksh but includes many enhancements. Zsh has command line editing, builtin spelling correction, programmable command completion, shell functions (with autoloading), a history mechanism, and a host of other features.

The Zsh Line Editor (ZLE) & bindkey

The ZLE module contains three related builtin commands. The bindkey command manipulates keymaps and key bindings; the vared command invokes ZLE on the value of a shell parameter; and the zle command manipulates editing widgets and allows command line access to ZLE commands from within shell functions.

openRepository() {
    destination=$(find $HOME/src -mindepth 2 -maxdepth 2 | cut -d '/' -f 5- | sort -r | fzf)

    if [[ ! -z $destination ]]
    then
      cd "$destination"
      zle reset-prompt
    fi
}

zle -N openRepository
bindkey "^g^g" openRepository

The Zsh Line Editor (ZLE) & bindkey

https://zsh.sourceforge.io/Guide/zshguide04.html

    -s in-string out-string ...

        Bind each in-string to each out-string. When in-string is typed, 
        out-string will be pushed back and treated as input to the line editor. 
        When -R is also used, interpret the in-strings as ranges.

git

bindkey -s "^g^p" "git pull\n"
bindkey -s "^g^f" "git fetch --all\n"
bindkey -s "^g^u" "git push -u\n"
bindkey -s "^g^s" "git status\n"
bindkey -s "^g^d" "git diff\n"
bindkey -s "^g^b" "git checkout \$(gf -o -c)\n"
bindkey -s "^g^m" "git merge \$(gf -o)\n"
bindkey -s "^g^rc" "git rebase --continue\n"
bindkey -s "^g^rs" "git rebase --skip\n"
bindkey -s "^g^ra" "git rebase --abort\n"
bindkey -s "^g^a" "git add -p\n"

bindkey -s "^gc" "git commit -m \"\"\eOD"

docker

bindkey -s "^b^u" "docker compose up\n"
bindkey -s "^b^b" "docker compose build --pull\n"
bindkey -s "^b^d" "docker compose down\n"
bindkey -s "^b^k" "docker compose down -v\n"
bindkey -s "^b^w" "docker compose exec php /bin/bash\n"

composer

bindkey -s "^p^i" "composer install\n"
bindkey -s "^p^u" "composer update --bump-after-update \n"
bindkey -s "^p^u^u" "composer update --bump-after-update --no-scripts\n"
bindkey -s "^p^b" "composer bump\n"
bindkey -s "^p^o" "composer outdated --locked -D\n"

php

bindkey -s "^p^t" "phpunit\n"
bindkey -s "^p^r" "nice -19 rector\n"
bindkey -s "^p^s" "nice -19 phpstan -vvv\n"
bindkey -s "^p^f" "nice -19 infection --show-mutations --threads=max -vvv\n"
bindkey -s "^p^x" "[ -z \"\$XDEBUG_MODE\" ] && export XDEBUG_MODE=debug || unset XDEBUG_MODE \n"
bindkey -s "^p^w^t" "fd \\.php$ -E var/cache | entr -c phpunit\n"
bindkey -s "^p^w^s" "fd \\.php$ -E var/cache | entr -c nice -19 phpstan -vvv\n"

gitlab

bindkey -s "^o^m" ' -o merge_request.create'
bindkey -s "^o^n" ' -o merge_request.title=""\eOD'
bindkey -s "^o^l" ' -o merge_request.label=""\eOD'
bindkey -s "^o^t" ' -o merge_request.target=release/'
bindkey -s "^o^a" ' -o merge_request.merge_when_pipeline_succeeds'
bindkey -s "^g^u^m" 'git push -u -o merge_request.create -o merge_request.merge_when_pipeline_succeeds -o merge_request.title=""'

Makefile

help your buddies to not forget

.PHONY: help compile watch run
help: ## Shows this help
    @awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z0-9_\-\.]+:.*?## / {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' $(MAKEFILE_LIST)

compile: ## creates final compiled.md
    rm -f compiled.md || true
    touch compiled.md
    for part in $$(find ./parts  -type f | sort); do (cat $$part; echo) >> compiled.md; done

watch: ## creates watcher with entr to recompile
    find parts | entr -c make compile

run: compile ## run patat in watcher mode
    patat -w compiled.md

make

$ make
help                           Shows this help
compile                        creates final faster.md
watch                          creates watcher with entr to recompile
run                            run patat in watcher mode