carbon-core-system

Integration repository for versioned configuration and software on Carbon
git clone https://ccx.te2000.cz/git/carbon-core-system
Log | Files | Refs | Submodules

commit 12439307a36e96bf97afb30f68b3aebcd07303a1
parent 356c86ca3c2b767f7ce6c587550b452e436a7c72
Author: Jan Pobříslo <ccx@te2000.cz>
Date:   Mon, 15 Aug 2022 22:25:18 +0200

Merge remote-tracking branch 'dorje-server/server'

Diffstat:
M.gitmodules | 13++++++++-----
Aconf | 1+
Minstall | 508++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Apush-to-host | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Ds6-rc | 1-
Ascripts | 1+
Mupdate-bzr | 6++++--
7 files changed, 396 insertions(+), 217 deletions(-)

diff --git a/.gitmodules b/.gitmodules @@ -1,9 +1,12 @@ [submodule "slashpackage"] path = package - url = /home/ccx/git/slashpackage -[submodule "carbon-s6-rc"] - path = s6-rc - url = https://ccx.te2000.cz/git/carbon-s6-rc + url = ./package [submodule "init"] path = init - url = https://ccx.te2000.cz/git/carbon-s6-init + url = ./init +[submodule "scripts"] + path = scripts + url = ./scripts +[submodule "conf"] + path = conf + url = ./conf diff --git a/conf b/conf @@ -0,0 +1 @@ +Subproject commit 9f46c0d846fc2dad4ea89dd4768be0bd09b19678 diff --git a/install b/install @@ -2,21 +2,27 @@ setopt no_unset warn_create_global extended_glob zmodload -F zsh/stat b:zstat || exit $? zmodload -m -F zsh/files b:zf_\* || exit $? -zmodload zsh/zutil || exit $? -zmodload zsh/datetime || exit $? cd $0:h || exit $? +umask 022 +### Utility functions {{{1 die_ret() { local ret ret=$1 shift - echo >&2 "$@" + printf >&2 '%s\n' "$@" exit $ret } die() { die_ret 1 "$@" } +die100() { # 100: wrong usage + die_ret 100 "$@" +} +die111() { # 111: system call failed + die_ret 111 "$@" +} require() { if ! check_$1; then @@ -27,9 +33,31 @@ require() { fi } +### Configuration {{{1 +typeset -ga submodule_list=( package init scripts conf ) +typeset -gA submodule_ignore_changes=( + package 1 +) +typeset -gA submodule_has_submodules=( + . 1 + package 1 +) + +### Checks {{{1 +check_submodule_init() { + local -a submodule_dirs + local sub + submodule_dirs=( ${(f)"$(sed -n '/^\tpath = /{s/^[^=]* = //;p}' <$submodule/.gitmodules)"} ) + for sub in $submodule_dirs; do + [[ -f $submodule/$sub/.git ]] || return $? + done +} +make_submodule_init() { + (cd $submodule && git submodule init && git submodule update) +} + check_committed() { local git_status - local -a changes git_status=$(cd $submodule && git status -s --ignore-submodules=dirty) || return $? [[ -z $git_status ]] && return 0 printf "Following changes found in %s:\n%s\n" $submodule "$git_status" @@ -48,75 +76,58 @@ make_committed() { fi } -install_package() { - git checkout --force || exit $? - git clean -fx || exit $? - git submodule update --force || exit $? - exec env slashpackage=${sm_dst[$submodule]} ./install-all +check_current() { + local -A stat_out + [[ -L /current ]] || return 1 + zstat -L -H stat_out /current || return $? + [[ $stat_out[link] == ${${sm_dst[.]}#/} ]] } - -install_unpack() { - local dst=${sm_dst[$submodule]} - local cmd=${sm_dst[package]}/command - s6-mkdir -p $dst || exit $? - git archive --format=tar ${sm_commit[$submodule]} | tar -xC $dst - exit $(( ${(j.|.)pipestatus} )) # error if git archive or tar exited non-zero +make_current() { + preinstall + s6-ln -s -f -n "${${sm_dst[.]}#/}" /current + postinstall_or_rollback } -ln_sub() { - s6-ln -s ../${${sm_dst[$1]}:t} ${2:-$1} || exit $? +recursive_immutable() { + ## Noisy but hopefully appropriate exitcode + #chattr -R -f +i "$@" + ## Skips warnings on symlinks but exitcode might not be useful + find "$@" -depth \! -o -type l -exec chattr +i '{}' + + ## Skip lockfiles so they can be opened read-write + # find "$@" -depth -name lock \! -o -type l -exec chattr +i '{}' + } -ln_from_sub() { - s6-ln -s ../${${sm_dst[$1]}:t}/$2 $3 || exit $? + +check_bzr_export() { + [[ -d ${bzr_dst} ]] } -ln_sub_command() { - local cmd - for cmd in ../${${sm_dst[$1]}:t}/{command,bin}/*(N); do - s6-ln -s ../$cmd ./command/ || exit $? - done +make_bzr_export() { + local tmp=$bzr_dst:h/.new.$bzr_dst:t + brz export --format=dir --revision=revid:$bzr_revid -d "$bzr_src" $tmp/ || exit $? + mv $tmp $bzr_dst || exit $? + recursive_immutable $bzr_dst } -install_dot() { - local submodule cmd - - # export sm_dst_* variables for each submodule - for submodule in "${(k@)sm_dst}"; do - if [[ $submodule == . ]]; then - export sm_dst_all=$sm_dst[.] - else - eval "export sm_dst_${submodule//-/_}"='$sm_dst[$submodule]' - fi - done - - s6-mkdir -p ${sm_dst[.]}/command || exit $? - install_bzr_repos - cd ${sm_dst[.]} || exit $? - ln_from_sub package package package - ln_sub_command package - ln_sub s6-rc s6-rc-source - ./command/s6-rc-compile ./s6-rc-db ./s6-rc-source || exit $? - ln_sub init init-maker - ./init-maker/install $PWD || exit $? - for cmd in init/{command,bin}/*(N); do - s6-ln -s ../$cmd ./command/ || exit $? - done - - for cmd in bzr/*/{command,bin,sbin}/*(N); do - s6-ln -s ../$cmd ./command/ || exit $? - done - - mkdir -p zsh-functions - local fun - for fun in bzr/*/zsh-functions/*(N); do - s6-ln -s ../$fun ./zsh-functions/ || exit $? - done +check_installed() { + [[ -f ${sm_dst[$submodule]}/.done ]] +} +make_installed() { + local dst=${sm_dst[$submodule]} + if [[ -e $dst ]]; then + rm -r $dst || exit $? + fi + ( cd $submodule && install_submodule ) || exit $? + touch $dst/.done + recursive_immutable $dst } +### Install functions {{{1 install_submodule() { case $submodule in (package) install_package;; (s6-rc) install_unpack;; (init) install_unpack;; + (scripts) install_unpack;; + (conf) install_unpack;; (.) install_dot;; (*) echo >&2 "unhandled submodule: ${(qqq)submodule}" @@ -125,55 +136,19 @@ install_submodule() { esac } -check_installed() { - [[ -f ${sm_dst[$submodule]}/.done ]] -} -make_installed() { - local dst=${sm_dst[$submodule]} - if [[ -e $dst ]]; then - rm -r $dst || exit $? - fi - ( cd $submodule && install_submodule ) || exit $? - touch $dst/.done -} -typeset -f -t make_installed - -check_current() { - local -A stat_out - [[ -L /current ]] || return 1 - zstat -L -H stat_out /current || return $? - [[ $stat_out[link] == ${${sm_dst[.]}#/} ]] -} -make_current() { - s6-ln -s -f -n "${${sm_dst[.]}#/}" /current -} -typeset -f -t make_current - -submodule() { - local -a do_commit - zparseopts -D c=do_commit - submodule=$1 - if ! [[ -d $1 ]]; then - git submodule update --recursive $submodule || exit $? - fi - (($#do_commit)) && require committed - sm_commit[$submodule]=$(cd $submodule && git show -s --pretty=format:%H%n) || exit $? - if [[ $submodule == . ]]; then - sm_dst[.]=/versions/all.${sm_commit[.]} - else - sm_dst[$submodule]=/versions/$submodule.${sm_commit[$submodule]} - git submodule update --recursive --force $submodule || exit $? - fi +install_package() { + git checkout --force || exit $? + git clean -fx || exit $? + git submodule update --init --force || exit $? + exec env slashpackage=${sm_dst[$submodule]} ./install-all } -#typeset -f -t submodule -check_bzr_export() { - [[ -d ${bzr_dst} ]] -} -make_bzr_export() { - local tmp=$bzr_dst:h/.new.$bzr_dst:t - brz export --format=dir --revision=revid:$bzr_revid -d "$bzr_src" $tmp/ || exit $? - mv $tmp $bzr_dst +install_unpack() { + local dst=${sm_dst[$submodule]} + local cmd=${sm_dst[package]}/command + zf_mkdir -p $dst || exit $? + git archive --format=tar ${sm_commit[$submodule]} | tar --no-same-owner --no-same-permissions -xC $dst + exit $(( ${(j.|.)pipestatus} )) # error if git archive or tar exited non-zero } install_bzr_repo(){ @@ -183,7 +158,7 @@ install_bzr_repo(){ bzr_src=$3 require bzr_export - s6-ln -s ../../$1.$2 $sm_dst[.]/bzr/$1 || exit $? + s6-ln -ns ../../$1.$2 $sm_dst[.]/bzr/$1 || exit $? } install_bzr_repos(){ local name url revid @@ -202,132 +177,247 @@ install_bzr_repos(){ done } -link_changed() { - [[ "${preinstall_links[$1]:-}" != "${postinstall_links[$1]:-}" ]] +# Utility for symlinking into /versions/all.*/ +ln_sub() { + s6-ln -ns ../${${sm_dst[$1]}:t} ${sm_dst[.]}/${2:-$1} || exit $? } -preinstall() { - typeset -gA preinstall_links - typeset -g preinstall_current - local l - for l in /current/**/*(@N); do - preinstall_links[${l#/current/}]=$l:A +ln_from_sub() { + s6-ln -ns ../${${sm_dst[$1]}:t}/$2 ${sm_dst[.]}/$3 || exit $? +} +ln_sub_command() { + ( + cd ${sm_dst[.]} || exit $? + local cmd + for cmd in ../${${sm_dst[$1]}:t}/{command,bin}/*(N); do + s6-ln -s ../$cmd ${sm_dst[.]}/command/ || exit $? + done + ) || exit $? +} +ln_sub_zsh_functions() { + local fun + for fun in ../${${sm_dst[$1]}:t}/zsh-functions/*(N); do + s6-ln -s ../$fun ${sm_dst[.]}/zsh-functions/ || exit $? done - local -A statinfo - zstat -H statinfo +link /current - preinstall_current=$statinfo[link] - s6-rename /previous /previous.1 - s6-ln -sf $preinstall_current /previous || exit $? } -#typeset -ft preinstall +install_dot() { + local submodule cmd fun -ensure_symlink() { - local target - if ! [[ -e $1 ]]; then - s6-ln -s $2 $1 || return $? - fi + # export sm_dst_* variables for each submodule + for submodule in "${(k@)sm_dst}"; do + if [[ $submodule == . ]]; then + export sm_dst_all=$sm_dst[.] + else + eval "export sm_dst_${submodule//-/_}"='$sm_dst[$submodule]' + fi + done - if [[ $2 == /* ]]; then - target=$2:A - else - target=$1:h/$2 - target=$target:A - fi + # create symlinks for submodules and populate ./command + zf_mkdir -p ${sm_dst[.]}/command ${sm_dst[.]}/zsh-functions || exit $? - if ! [[ -h $1 ]]; then - echo >&2 "Error: not a symlink: ${(qqq)1}" - return 1 - elif [[ $1:A != $target ]]; then - echo >&2 "Error: ${(qqq)1} points to ${(qqq)1:A} instead of ${(qqq)target}" - return 1 - fi + ln_from_sub package package package + ln_sub_command package + + install_bzr_repos + ( + cd ${sm_dst[.]} || exit $? + for cmd in bzr/*/{command,bin,sbin}/*(N); do + s6-ln -s ../$cmd ./command/ || exit $? + done + for fun in bzr/*/zsh-functions/*(N); do + s6-ln -s ../$fun ./zsh-functions/ || exit $? + done + ) || exit $? + + # ln_sub s6-rc s6-rc-source + #./command/s6-rc-compile ./s6-rc-db ./s6-rc-source || exit $? + + ln_sub init init-maker + ln_sub_command init + #./init-maker/install $PWD || exit $? + + ln_sub scripts + ln_sub_command scripts + ln_sub_zsh_functions scripts + + ln_sub conf + ln_sub_command conf + ln_sub_zsh_functions conf + + # After all is symlinked the rest of installation is done by conf submodule + ${sm_dst[.]}/conf/install || exit $? } -postinstall() { - # TODO: Ensure patched Toybox login is compiled and installed. - ensure_symlink /etc/loginexec ../current/bzr/logincaps/etc/loginexec || return $? - ensure_symlink /command current/command || return $? - - if [[ -d /run/s6-rc/ ]]; then - if link_changed package; then - if link_changed s6-rc-source; then - # Both s6-rc DB source and installed software changed. - # s6-rc-format-upgrade to tmpdir, then s6-rc-update - local tmp_db=/run/old-s6-rc-db-migration.$EPOCHREALTIME - - s6-rc-compile $tmp_db "$preinstall_links[s6-rc-source]" || return $? - s6-rc-format-upgrade -v2 $tmp_db || return $? - s6-rc-update -v2 /current/s6-rc-db/ || return $? - rm -r $tmp_db || return $? - else - # Installed software changed but s6-rc DB source is the same. - # s6-rc-format-upgrade to new compiled DB directly - s6-rc-format-upgrade -v2 /current/s6-rc-db/ || return $? - fi - elif link_changed s6-rc-source; then - # s6-rc DB source changed while keeping same version of software. - # s6-rc-update to new compiled DB directly - s6-rc-update -v2 /current/s6-rc-db/ || return $? - fi +### Main code {{{1 +preinstall() { + typeset -g preinstall_current + [[ -e /current ]] || return 0 - # Check if we need to regenerate containers or their service directories. - if link_changed bzr/containers || link_changed bzr/confz; then - if (( $+confz_verbose )); then - zsh -lc "verbose=1 confz site_containers" </dev/null 2>&1 | cat -v - ret=$((${(j.|.)pipestatus})) # Nonzero iff any of commands in pipeline returned nonzero. - else - zsh -lc "quiet=1 confz site_containers" </dev/null 2>&1 | cat -v - ret=$((${(j.|.)pipestatus})) # Nonzero iff any of commands in pipeline returned nonzero. - fi - if (($ret)); then - echo >&2 "Error: command failed ($ret): confz site_containers" - return $ret - fi - s6-svscanctl -a /run/service || return $? - fi - fi - return 0 + local -A statinfo + zstat -H statinfo +link /current + preinstall_current=$statinfo[link] + preinstall_current_p=${${:-/current}:P} + # for postinstall script + export preinstall_current preinstall_current_p + s6-rename /previous /previous.1 + s6-ln -nsf $preinstall_current /previous || exit $? } -typeset -ft postinstall postinstall_or_rollback() { - typeset -gA postinstall_links - local l ret - for l in /current/**/*(@N); do - postinstall_links[${l#/current/}]=$l:A - done + local l ret sub - if ! postinstall; then - echo >&2 "Fatal: postinstall script failed, rolling back to $preinstall_current" - s6-ln -sf $preinstall_current /current || exit $? - s6-rename /previous.1 /previous + if ! ${sm_dst[.]}/conf/postinstall; then + if [[ -n $preinstall_current ]]; then + echo >&2 "Fatal: postinstall script failed, rolling back to $preinstall_current" + s6-ln -nsf $preinstall_current /current || exit $? + s6-rename /previous.1 /previous + else + echo >&2 "Fatal: postinstall script failed for initial installation, removing /current" + zf_rm /current + fi exit 1 fi git tag --force deployed - local sub # Mark current HEAD as master in submodules - for sub in package s6-rc init; do + for sub in $submodule_list; do (cd $sub && git checkout -B master HEAD) done } -# End of functions, now install things in right order -typeset -g submodule -typeset -gA sm_commit sm_dst +ensure_head() { + local line sub_state sub_commit sub_name ret git_sub_changes + local -a git_status git_log_to_head git_log_from_head + + git_status=( ${(f)"$(git status -s --ignore-submodules=dirty)"}) \ + || die_ret $? "Fatal: 'git status' failed" + if (( $git_status[(I)???.gitmodules] )); then + die ".gitmodules uncommited, please make commit manually" + fi + + git_status=( ${(f)"$(git submodule status --cached)"}) \ + || die_ret $? "Fatal: 'git submodule status' failed" + for line in $git_status; do + sub_state=${line[1]} + sub_commit=${line[2,41]} + sub_name=${line[43,-1]% \(*} + submodule=$sub_name + case $sub_state in + ('U') die "Submodule ${(qqq)sub_name} has merge conflicts" + ;; + ('-') # Submodule not initialized + git submodule update --init -- $sub_name \ + || die_ret $? "Failed to initilize submodule ${(qqq)sub_name}" + if (($+submodule_has_submodules[$sub_name])); then + (cd $sub_name && git submodule update --init) || exit $? + fi + ;; + ('+') # Commit in submodule differs from expected one + # Check if desired commit is ancestor of current + # Note: might return 128 if revision wasn't fetched yet + git_log_to_head=( ${(f)"$( cd ./$sub_name && git log --format=oneline $sub_commit..HEAD )"} ) + ret=$? + if (( $ret == 128 )); then # likely: fatal: Invalid revision range + (cd $sub_name && git fetch --all) + git_log_to_head=( ${(f)"$( git log --format=oneline $sub_commit..HEAD )"} ) + ret=$? + fi + (( $ret )) && die_ret $? "git log returned error" + git_log_from_head=( ${(f)"$( cd ./$sub_name && git log --format=oneline HEAD..$sub_commit )"} ) + if (($#git_log_to_head && $#git_log_from_head == 0)); then + # Can advance directly from saved commit to HEAD + echo "$#git_log_to_head new commits in submodule. Add and commit them?" + printf 'last submodule commit: %s\n' $git_log_to_head[1] + local REPLY + read -q || exit 0 + git add -- $sub_name || exit $? + git commit -m "update ${(qqq)sub_name} submodule" -- $sub_name || exit $? + if ! (($+submodule_ignore_changes[$sub_name])); then + require committed + fi + elif (($#git_log_to_head == 0 && $#git_log_from_head)); then + # Can advance directly from HEAD to saved commit + if ! (($+submodule_ignore_changes[$sub_name])); then + git_sub_changes=$(cd $sub_name && git status -s --ignore-submodules=dirty) \ + || die "Error from git status in ${(qqq)sub_name}" + [[ -z $git_sub_changes ]] \ + || die "Uncommitted changes in ${(qqq)sub_name}" + fi + (cd $sub_name && git reset --hard && git clean -fx) || exit $? + git submodule update --force -- $sub_name + elif (($#git_log_to_head && $#git_log_from_head)); then + # HEAD diverged from saved commit + die "Submodule ${(qqq)sub_name} HEAD diverged." + else + # No shared history!? + die "Submodule ${(qqq)sub_name} reports no shared history." + fi + ;; + (' ') # Commit in submodule same as in tree + if ! (($+submodule_ignore_changes[$sub_name])); then + require committed + fi + ;; + (*) die "Submodule ${(qqq)sub_name} has unknown state ${(qqq)sub_state}" + ;; + esac + done + submodule=. + require committed +} +load_commits() { + typeset -gA sm_commit sm_dst + local line sub_state sub_commit sub_name + local -a git_status + + sm_commit[.]=$(git show -s --pretty=format:%H%n) || exit $? + sm_dst[.]=/versions/all.${sm_commit[.]} + + git_status=( ${(f)"$(git submodule status --cached)"}) \ + || die_ret $? "Fatal: 'git submodule status' failed" + for line in $git_status; do + sub_state=${line[1]} + sub_commit=${line[2,41]} + sub_name=${line[43,-1]% \(*} + case $sub_state in + (' ') + sm_commit[$sub_name]=$sub_commit + sm_dst[$sub_name]=/versions/$sub_name.${sm_commit[$sub_name]} + ;; + (*) die "Submodule ${(qqq)sub_name} not clean (state ${(qqq)sub_state})" + ;; + esac + done +} -submodule package -require installed -PATH=${sm_dst[package]}/command:$PATH +main() { + local sub + ensure_head + load_commits + + for sub in $submodule_list .; do + submodule=$sub + require installed + if [[ -d ${sm_dst[$sub]}/command ]]; then + PATH=${sm_dst[$sub]}/command:$PATH + fi + done -submodule -c s6-rc -require installed + require current +} -submodule -c init -require installed +if (($+trace_func)); then + # Use trace_func environment variable as a pattern and enable tracing + # on all functions that match it. + typeset -g function_to_trace + for function_to_trace in ${(k)functions}; do + if [[ $function_to_trace == $~trace_func ]]; then + typeset -f -t $function_to_trace || die "something wrong with typeset -f -t $function_to_trace" + printf >&2 '+++ tracing enabled for function %s\n' ${(qqq)function_to_trace} + fi + done + unset function_to_trace +fi -submodule -c . -require installed +typeset -g submodule -preinstall -require current -postinstall_or_rollback +main diff --git a/push-to-host b/push-to-host @@ -0,0 +1,83 @@ +#!/bin/zsh +setopt no_unset warn_create_global extended_glob +zmodload -F zsh/stat b:zstat || exit $? +zmodload -m -F zsh/files b:zf_\* || exit $? +zmodload zsh/zutil || exit $? # for zparseopts +zmodload zsh/datetime || exit $? # for $EPOCHREALTIME + +cd $0:h || exit $? + +### Utility functions {{{1 +die_ret() { + local ret + ret=$1 + shift + printf >&2 '%s\n' "$@" + exit $ret +} +die() { + die_ret 1 "$@" +} +die100() { # 100: wrong usage + die_ret 100 "$@" +} +die111() { # 111: system call failed + die_ret 111 "$@" +} + +pretend() { + : "$@" +} +typeset -f -t pretend + +typeset -g brz=${commands[brz]:-bzr} + +process_submodules() { + local line sub_state sub_commit sub_name + local -a git_status + + git_status=( ${(f)"$(git submodule status --cached --recursive)"}) \ + || die_ret $? "Fatal: 'git submodule status' failed" + for line in $git_status; do + sub_state=${line[1]} + sub_commit=${line[2,41]} + sub_name=${line[43,-1]% \(*} + =cd $sub_name git push ${remote}:git/core-system/$sub_name ${sub_commit}:refs/heads/core-system-$branch_name || exit $? + done +} + +process_bzr() { + # local -a revision_list + local revision_list repo_name revid + #revision_list=( "${(@f)"$(git cat-file blob $branch_name:bzr/revisions)"}" ) \ + # + revision_list="$(git cat-file blob $branch_name:bzr/revisions)" \ + || die_ret $? "Could not get the list of bzr revisions" + while IFS=$'\t' read -r repo_name revid ; do + $brz push -d ../../bzr/$repo_name bzr+ssh://$remote/~/bzr/$repo_name || exit $? + done <<<$revision_list +} + +main() { + typeset -g remote branch_name + case "$#" in + (2) + branch_name=$2 + ;; + (1) + branch_name=$(git branch --show-current) || exit $? + ;; + (*) + die100 "usage: push-to-host remote [branch_name]" + ;; + + esac + [[ -z $branch_name ]] && die "Error: no branch" + remote=$1 + process_submodules + git push ${remote}:git/core-system ${branch_name}:refs/heads/core-system-${branch_name} + process_bzr +} +typeset -f -t main + +main "$@" diff --git a/s6-rc b/s6-rc @@ -1 +0,0 @@ -Subproject commit cc63a57a30e66e8c171d3fae2b3504e764f247e8 diff --git a/scripts b/scripts @@ -0,0 +1 @@ +Subproject commit 98fabf6dffa7a3a7850f1797f557e181b7e8460f diff --git a/update-bzr b/update-bzr @@ -21,6 +21,8 @@ pretend() { } typeset -ft pretend +brz=${commands[brz]:-bzr} + load_sources() { unset repo_sources typeset -gA repo_sources @@ -43,7 +45,7 @@ make_revisions_new() { local name url local -a info for name in $repos; do - info=( $(brz version-info $repo_sources[$name]) ) || + info=( $($brz version-info $repo_sources[$name]) ) || die_ret $? brz version-info failed [[ $info[1] == "revision-id:" ]] || die malformed brz version-info output @@ -91,7 +93,7 @@ compare_revisions() { bzrlog() { # first argument needs to be revision range - brz log --show-diff --long --forward -r "$@" + $brz log --show-diff --long --forward -r "$@" } show_diffs() {