Files
config/bash/bashrc-osx
T
bchanot f42e28807b feat(repo-sync): one local tree for every repo reachable on your forges
bin/repo-sync ports the Alphalink dotfiles repo/repo-reset zsh functions to
bash + zsh on Linux and macOS, for several forges at once: GitLab, GitHub
(+GHES), Gitea/Forgejo, Bitbucket Cloud, via curl + jq (no per-forge CLI).
Tokens live in ~/.config/repos/forges.conf (0600, parsed line by line, never
tracked), written by `repo-sync add`. Cache ~/.cache/repos/list refreshed
at most once a day (mkdir lock: flock is not on macOS), same namespace/project
on two forges kept once, archived and mirrors skipped. Layout
~/repos/<namespace with / as @>/<project>.

rc files: `repo <project> [namespace]` clones on demand and cd's, with
completion (bash compgen, zsh _describe), background refresh at shell start.
install.sh: jq in the apt and brew lists, chmod repo-sync.

Verified: shellcheck + bash -n, zsh -n; stub-curl harness over the four forges
(pagination, dedup, auth headers, lock, stale cache, path/clone); live GitLab
refresh (139 projects, 3s).
2026-10-07 15:31:03 +02:00

174 lines
6.0 KiB
Plaintext

# Homebrew on PATH (Apple Silicon: /opt/homebrew, Intel: /usr/local).
# Must come first: brew's bash, vim, git, fzf and dtach shadow the system ones.
for brew_bin in /opt/homebrew/bin/brew /usr/local/bin/brew; do
if [ -x "$brew_bin" ]; then
eval "$("$brew_bin" shellenv)"
break
fi
done
unset brew_bin
## Enable color support of ls and also add handy aliases
# Some colors (BSD ls: -G instead of GNU --color)
alias ls='ls -G'
alias grep='grep --color=auto'
## Some export
# colored GCC warnings and errors
export GCC_COLORS='error=01;31:warning=01;35:note=01;36:caret=01;32:locus=01:quote=01'
# XDG cache dir: tmux.conf saves resurrect/continuum sessions under it
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$HOME/.cache}"
# Ensure ~/.local/bin is on PATH (pipx CLIs + personal scripts from bin/)
case ":$PATH:" in
*":$HOME/.local/bin:"*) ;;
*) export PATH="$HOME/.local/bin:$PATH" ;;
esac
# Set history size to unlimited
if [[ $EUID == 0 ]] ; then
export HISTSIZE=0
export HISTFILESIZE=0
else
export HISTSIZE=-1
export HISTFILESIZE=-1
fi
#export LANG=en_US.UTF-8
# Identity for git commits and vim headers. Filled by install.sh (asked once,
# reused from an existing ~/.bashrc / ~/.zshrc on re-runs), never stored here.
export USER="@USER@"
export EMAIL="@EMAIL@"
## Activate and custom bash completion
#bind 'TAB:menu-complete'
#bind 'set show-all-if-ambiguous on'
## Setting prompt style
# Get current branch in git repo
function parse_git_branch() {
BRANCH=`git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'`
if [ ! "${BRANCH}" == "" ]
then
STAT=`parse_git_dirty`
echo " [${BRANCH}${STAT}]"
else
echo ""
fi
}
# BSD date has no %N: use bash 5's microsecond clock, scaled to nanoseconds.
# Falls back to whole seconds under the system bash 3.2.
function timer_now {
if [ -n "${EPOCHREALTIME:-}" ]; then
echo "${EPOCHREALTIME/[.,]/}000"
else
echo "$(date +%s)000000000"
fi
}
function timer_start {
timer_start=${timer_start:-$(timer_now)}
}
function timer_stop {
local delta_us=$((($(timer_now) - $timer_start) / 1000))
local us=$((delta_us % 1000))
local ms=$(((delta_us / 1000) % 1000))
local s=$(((delta_us / 1000000) % 60))
local m=$(((delta_us / 60000000) % 60))
local h=$((delta_us / 3600000000))
# Goal: always show around 3 digits of accuracy
if ((h > 0)); then timer_show=${h}h${m}m
elif ((m > 0)); then timer_show=${m}m${s}s
elif ((s >= 10)); then timer_show=${s}.$((ms / 100))s
elif ((s > 0)); then timer_show=${s}.$(printf %03d $ms)s
elif ((ms >= 100)); then timer_show=${ms}ms
elif ((ms > 0)); then timer_show=${ms}.$((us / 100))ms
else timer_show=${us}us
fi
unset timer_start
}
# get current status of git repo
function parse_git_dirty {
status=`git status 2>&1 | tee`
dirty=`echo -n "${status}" 2> /dev/null | grep "modified:" &> /dev/null; echo "$?"`
untracked=`echo -n "${status}" 2> /dev/null | grep "Untracked files" &> /dev/null; echo "$?"`
ahead=`echo -n "${status}" 2> /dev/null | grep "Your branch is ahead of" &> /dev/null; echo "$?"`
newfile=`echo -n "${status}" 2> /dev/null | grep "new file:" &> /dev/null; echo "$?"`
renamed=`echo -n "${status}" 2> /dev/null | grep "renamed:" &> /dev/null; echo "$?"`
deleted=`echo -n "${status}" 2> /dev/null | grep "deleted:" &> /dev/null; echo "$?"`
bits=''
if [ "${renamed}" == "0" ] || [ "${newfile}" == "0" ] || [ "${untracked}" == "0" ] || [ "${dirty}" == "0" ]; then
bits="+${bits}"
fi
if [ "${ahead}" == "0" ]; then
bits="*${bits}"
fi
if [ "${deleted}" == "0" ]; then
bits="-${bits}"
fi
if [ ! "${bits}" == "" ]
then
echo " ${bits}"
else
echo ""
fi
}
function set_prompt {
timer_stop
if [[ $EUID == 0 ]] ; then
export PS1='`if [ $? = 0 ]; then echo "\[\033[01;36m\]✔"; else echo "\[\033[01;31m\]✘"; fi` ($timer_show) \[\033[01;31m\]\u [\[\033[00;0m\] \w \[\033[01;31m\]]\[\033[01;34m\]$(parse_git_branch " %s") \[\033[00;00m\]> '
else
export PS1='`if [ $? = 0 ]; then echo "\[\033[01;36m\]✔"; else echo "\[\033[01;31m\]✘"; fi` ($timer_show) \[\033[01;32m\]\u [\[\033[00;0m\] \w \[\033[01;32m\]]\[\033[01;34m\]$(parse_git_branch " %s") \[\033[00;00m\]> '
fi
}
trap 'timer_start' DEBUG
PROMPT_COMMAND='set_prompt'
## Lancement des commandes au demarrages
# claude-dans-dtach : creer une session (claude tournant dans dtach, detache via Ctrl-\).
# Usage : cd ~/projets/seo && cc seo -> session nommee "seo".
# Pas de systemd sur macOS : dtach est lance directement (pas de slice cgroup).
dtach_claude() {
local name="${1:-claude-$(date +%H%M%S)}"
mkdir -p "$HOME/.dtach"
dtach -c "$HOME/.dtach/${name}" -e '^\' \
bash -c 'set -a; [ -f "$HOME/.claude/.env" ] && . "$HOME/.claude/.env"; set +a; exec claude'
}
alias cc='dtach_claude'
# Rappeler a la demande le menu de reprise.
alias d='source ~/.local/bin/dtach-router'
# repo-sync : un arbre local (~/repos) pour tous les depots des forges declarees
# dans ~/.config/repos/forges.conf. `repo <projet> [namespace]` clone si besoin
# puis s'y place ; la liste se rafraichit en arriere-plan, au plus 1 fois par jour.
repo() {
local dir
dir="$(repo-sync path "$@")" && cd "$dir" || return
}
_repo_complete() {
local cache="${REPOS_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/repos/list}" words
[ -f "$cache" ] || return 0
if [ "$COMP_CWORD" -eq 1 ]; then
words="$(cut -f1 "$cache" | sort -u)"
else
words="$(awk -F'\t' -v p="${COMP_WORDS[1]}" '$1 == p { print $2 }' "$cache")"
fi
# shellcheck disable=SC2207
COMPREPLY=($(compgen -W "$words" -- "${COMP_WORDS[COMP_CWORD]}"))
}
complete -F _repo_complete repo
case $- in *i*) command -v repo-sync >/dev/null && (repo-sync refresh --quiet >/dev/null 2>&1 &) ;; esac
# Au demarrage d'un shell interactif, proposer de reprendre une session dtach existante
# (silencieux si aucune). Place ici plutot que dans ~/.profile car les terminaux VS Code
# Remote-SSH sont non-login et ne lisent pas ~/.profile ; ~/.bashrc, lui, est toujours lu.
case $- in *i*) [ -x "$HOME/.local/bin/dtach-router" ] && . "$HOME/.local/bin/dtach-router" ;; esac