feat(auto-pull): opt-in background fast-forward for registered repos #60
@@ -258,7 +258,7 @@ If you'd rather set them by hand, each category is controlled by a universal var
|
||||
| Variable | Disables |
|
||||
|---|---|
|
||||
| `__fish_config_op_aliases` | Command shadows: `ls`→eza, `cat`→bat, `cd`→zoxide, `rm`→trash, `top`→btop, `edit`→multi-editor launcher, and friends; `grep`/`cp`/`mv`/`wget` flag injection |
|
||||
| `__fish_config_op_autoexec` | Startup side-effects: Fisher bootstrap, theme apply, `paru`/`yay` wrapper generation, auto venv activation, WakaTime hook |
|
||||
| `__fish_config_op_autoexec` | Startup side-effects: Fisher bootstrap, theme apply, `paru`/`yay` wrapper generation, auto venv activation, WakaTime hook, auto-pull background fast-forward |
|
||||
| `__fish_config_op_overrides` | Vi mode, `exit`→`smart_exit`, `$PAGER`/`$MANPAGER`/`$CDPATH`, bang-bang history expansion, autopair, puffer, Starship prompt, theme colors |
|
||||
| `__fish_config_op_integrations` | Kitty/WezTerm window abbreviations, `done` notifications, `spwin`/`tab`/`split`, `hist`, `logs`, `upgrade`, WakaTime |
|
||||
| `__fish_config_op_logging` | Scrollback capture on exit, tmux `pipe-pane` pane logging, zellij `dump-screen` capture on exit, `paru`/`yay` AUR log wrappers, Kitty watcher capture (sentinel-file coordinated) |
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# Completions for the `auto-pull` registry command.
|
||||
|
||||
function __auto_pull_registered
|
||||
set -l list "$XDG_CONFIG_HOME/.user-dots/fish/auto-pull.list"
|
||||
test -r "$list"; or return
|
||||
for l in (command cat "$list" 2>/dev/null)
|
||||
test -n "$l"; or continue
|
||||
printf '%s\t%s\n' (path basename "$l") "$l"
|
||||
end
|
||||
end
|
||||
|
||||
set -l subcmds list add remove status
|
||||
|
||||
# Subcommands (only as the first argument).
|
||||
complete -c auto-pull -f -n "not __fish_seen_subcommand_from $subcmds" \
|
||||
-a list -d 'Show registered repos'
|
||||
complete -c auto-pull -f -n "not __fish_seen_subcommand_from $subcmds" \
|
||||
-a add -d "Register a repo (default: current)"
|
||||
complete -c auto-pull -f -n "not __fish_seen_subcommand_from $subcmds" \
|
||||
-a remove -d 'Unregister a repo'
|
||||
complete -c auto-pull -f -n "not __fish_seen_subcommand_from $subcmds" \
|
||||
-a status -d 'Show enabled state and registry path'
|
||||
complete -c auto-pull -f -n "not __fish_seen_subcommand_from $subcmds" \
|
||||
-s h -l help -d 'Show help'
|
||||
|
||||
# `add` takes a directory path.
|
||||
complete -c auto-pull -n "__fish_seen_subcommand_from add" -a '(__fish_complete_directories)'
|
||||
|
||||
# `remove` completes registered repo basenames.
|
||||
complete -c auto-pull -f -n "__fish_seen_subcommand_from remove" -a '(__auto_pull_registered)'
|
||||
@@ -0,0 +1,62 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# ╭──────────────────────────────────────────────────────────╮
|
||||
# │ Auto-Pull (C2 — Autoexec) │
|
||||
# ╰──────────────────────────────────────────────────────────╯
|
||||
#
|
||||
# Background, fast-forward-only `git pull` for opted-in repositories. Fires
|
||||
# when the working directory enters the fish-config repo (always covered as a
|
||||
# baseline) or any repository registered via the `auto-pull` command. The
|
||||
# actual sync is delegated to `_auto_pull_sync`, which only ever fast-forwards
|
||||
# a clean repo — see that function for the full safety contract.
|
||||
#
|
||||
# Manage the registry with: auto-pull add / remove / list / status
|
||||
|
||||
# C2 guard: when auto-execution is disabled, do not register the handler.
|
||||
__fish_config_op_enabled __fish_config_op_autoexec; or exit
|
||||
|
||||
# SYNOPSIS
|
||||
# __auto_pull_on_pwd (event handler, --on-variable PWD)
|
||||
#
|
||||
# DESCRIPTION
|
||||
# On every directory change, checks whether the new $PWD is inside a
|
||||
# registered repository (or the fish-config baseline) and, if so, kicks a
|
||||
# background fast-forward. A throttle global ensures it fires once per
|
||||
# repository entry rather than on every sub-directory change.
|
||||
function __auto_pull_on_pwd --on-variable PWD
|
||||
set -q __fish_config_dir; or set -g __fish_config_dir $XDG_CONFIG_HOME/fish
|
||||
|
||||
set -l list "$XDG_CONFIG_HOME/.user-dots/fish/auto-pull.list"
|
||||
|
||||
# Candidate roots: the fish-config repo plus the user's registry.
|
||||
set -l roots $__fish_config_dir
|
||||
test -r "$list"; and set -a roots (command cat "$list" 2>/dev/null)
|
||||
|
||||
# Find the registered root containing $PWD (exact match or a sub-directory).
|
||||
set -l hit
|
||||
for r in $roots
|
||||
test -n "$r"; or continue
|
||||
if test "$PWD" = "$r"; or string match -q -- "$r/*" "$PWD"
|
||||
set hit "$r"
|
||||
break
|
||||
end
|
||||
end
|
||||
|
||||
# Outside every registered repo: clear the throttle so re-entry re-syncs.
|
||||
if test -z "$hit"
|
||||
set -e __auto_pull_last
|
||||
return
|
||||
end
|
||||
|
||||
# Already synced for this repo entry — skip until we leave and return.
|
||||
test "$hit" = "$__auto_pull_last"; and return
|
||||
set -g __auto_pull_last "$hit"
|
||||
|
||||
# Background fast-forward. A --no-config child sources just the worker so
|
||||
# it stays fast and free of shell side-effects; real `git`, no shadows.
|
||||
set -l worker "$__fish_config_dir/functions/_auto_pull_sync.fish"
|
||||
test -r "$worker"; or return
|
||||
fish --no-config -c 'source $argv[1]; _auto_pull_sync $argv[2]' -- "$worker" "$hit" &
|
||||
disown 2>/dev/null
|
||||
end
|
||||
@@ -88,6 +88,7 @@ less=### less
|
||||
rawfish=### rawfish
|
||||
view=### view
|
||||
git=## 5.4 Git and Version Control
|
||||
auto-pull=### auto-pull
|
||||
branch=### branch
|
||||
gi=### gi
|
||||
gitignore=### gi
|
||||
|
||||
+36
-1
@@ -762,6 +762,30 @@ Add -i (interactive confirmation) to destructive commands:
|
||||
|
||||
## 5.4 Git and Version Control
|
||||
|
||||
### auto-pull
|
||||
|
||||
Synopsis: auto-pull [list]
|
||||
auto-pull add [PATH]
|
||||
auto-pull remove <NAME|PATH>
|
||||
auto-pull status
|
||||
|
||||
Manages the registry of repositories that are background fast-forwarded
|
||||
when you enter them (see "Auto-pull fast-forward" under the C2 component
|
||||
reference). The fish-config repo is always covered as a baseline. The
|
||||
registry is machine-local at ~/.config/.user-dots/fish/auto-pull.list, one
|
||||
absolute path per line, and is never committed. Registry management works
|
||||
even when C2 auto-execution is disabled; only the background sync is gated.
|
||||
|
||||
list Show registered repos (default)
|
||||
add [PATH] Register PATH's git root (default: current repo)
|
||||
remove <NAME|PATH> Unregister by basename or exact path
|
||||
status Show enabled/disabled state, repo count, list path
|
||||
|
||||
cd ~/src/qmk_firmware; and auto-pull add
|
||||
auto-pull add ~/work/api
|
||||
auto-pull list
|
||||
auto-pull remove qmk_firmware
|
||||
|
||||
### branch
|
||||
|
||||
Synopsis: branch <branch_name>
|
||||
@@ -1677,9 +1701,11 @@ __fish_config_op_autoexec prevents all of them.
|
||||
yay wrapper Every startup Writes ~/.local/bin/yay wrapper
|
||||
Python venv activation On every cd Sources .venv/bin/activate.fish
|
||||
WakaTime command hook On every command Reports to WakaTime API
|
||||
Auto-pull fast-forward On entering a repo Background ff-only git pull
|
||||
|
||||
When C2 is disabled: no Fisher install, no theme application, no paru/yay
|
||||
wrapper generation, no automatic venv activation, and no WakaTime reporting.
|
||||
wrapper generation, no automatic venv activation, no WakaTime reporting, and
|
||||
no auto-pull (the PWD handler is never registered).
|
||||
The first-run completion marker (__fish_config_first_run_complete) is still
|
||||
set so the init does not re-run on subsequent shells.
|
||||
|
||||
@@ -1687,6 +1713,15 @@ Python venv activation fires on every directory change. If a directory uses
|
||||
direnv (.envrc present), direnv takes priority and auto-venv is skipped for
|
||||
that directory.
|
||||
|
||||
Auto-pull fast-forwards opted-in repositories in the background when you cd
|
||||
into them. The fish-config repo is always covered; other repos are added with
|
||||
the `auto-pull` command (see its entry in the functions reference). It only
|
||||
ever fast-forwards a clean repo whose branch has an upstream — never rebases,
|
||||
merges, or overwrites work — so it is a no-op on dirty trees, divergent
|
||||
branches, or repos without a remote. The handler fires once per repo entry
|
||||
(not on every sub-directory cd). The registry is machine-local at
|
||||
~/.config/.user-dots/fish/auto-pull.list and is never committed.
|
||||
|
||||
#### C3 — Key and Environment Overrides
|
||||
|
||||
These change fundamental shell behavior: how keys work, which pager opens,
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# _auto_pull_sync <dir>
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Best-effort, side-effect-safe fast-forward of a single git repository.
|
||||
# Performs a fetch and a fast-forward-only merge of the current branch's
|
||||
# upstream, but only when it is completely safe to do so: the work tree
|
||||
# must be clean, the branch must have an upstream, and the merge must be a
|
||||
# true fast-forward. Any failed precondition makes the function a no-op.
|
||||
# It never rebases, never creates a merge commit, and never overwrites
|
||||
# committed or uncommitted work — worst case it does nothing.
|
||||
#
|
||||
# Intended to be invoked in the background by the auto-pull PWD handler
|
||||
# (conf.d/auto-pull.fish), but safe to call directly.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# dir Absolute path to the git repository to fast-forward
|
||||
#
|
||||
# RETURNS
|
||||
# 0 Fast-forward applied (or already up to date)
|
||||
# 1 A precondition failed; nothing was changed
|
||||
#
|
||||
# EXAMPLE
|
||||
# _auto_pull_sync ~/.config/fish
|
||||
function _auto_pull_sync --description 'Fast-forward a repo when clean and a ff is possible'
|
||||
set -l d $argv[1]
|
||||
test -n "$d"; or return 1
|
||||
|
||||
# Must be a git work tree.
|
||||
command git -C "$d" rev-parse --is-inside-work-tree >/dev/null 2>&1; or return 1
|
||||
|
||||
# Work tree must be clean (no staged or unstaged changes).
|
||||
command git -C "$d" diff --quiet 2>/dev/null
|
||||
and command git -C "$d" diff --cached --quiet 2>/dev/null
|
||||
or return 1
|
||||
|
||||
# Current branch must have an upstream to fast-forward against.
|
||||
command git -C "$d" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
|
||||
or return 1
|
||||
|
||||
# Fetch, then fast-forward only. --ff-only aborts (no-op) on any divergence.
|
||||
command git -C "$d" fetch -q 2>/dev/null; or return 1
|
||||
command git -C "$d" merge -q --ff-only '@{u}' >/dev/null 2>&1
|
||||
end
|
||||
@@ -0,0 +1,150 @@
|
||||
# Copyright (C) 2026 Rootiest
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
# SYNOPSIS
|
||||
# auto-pull [list]
|
||||
# auto-pull add [PATH]
|
||||
# auto-pull remove <NAME|PATH>
|
||||
# auto-pull status
|
||||
#
|
||||
# DESCRIPTION
|
||||
# Manages the auto-pull registry: the list of repositories that are
|
||||
# background fast-forwarded when you enter them (see conf.d/auto-pull.fish
|
||||
# and _auto_pull_sync). The fish-config repo is always covered as a baseline
|
||||
# and does not need to be added. The registry is a plain text file, one
|
||||
# absolute git-toplevel path per line, stored machine-locally in
|
||||
# ~/.config/.user-dots/fish/auto-pull.list (never committed to the config).
|
||||
#
|
||||
# Registry management works regardless of the C2 auto-execution guard; only
|
||||
# the background sync itself is gated by __fish_config_op_autoexec.
|
||||
#
|
||||
# ARGUMENTS
|
||||
# list Show registered repos (default when no subcommand given)
|
||||
# add [PATH] Register PATH's git root; defaults to the current repo
|
||||
# remove <NAME|PATH> Unregister by basename or exact path
|
||||
# status Show whether auto-pull is enabled and the registry path
|
||||
# -h, --help Show this help message
|
||||
#
|
||||
# RETURNS
|
||||
# 0 Subcommand succeeded
|
||||
# 1 Bad usage, target is not a git repo, or target not registered
|
||||
#
|
||||
# EXAMPLE
|
||||
# cd ~/src/qmk_firmware; and auto-pull add
|
||||
# auto-pull add ~/work/api
|
||||
# auto-pull list
|
||||
# auto-pull remove qmk_firmware
|
||||
function auto-pull --description 'Manage the auto-pull repository registry'
|
||||
set -l c_head (set_color --bold cyan)
|
||||
set -l c_cmd (set_color --bold white)
|
||||
set -l c_flag (set_color yellow)
|
||||
set -l c_ok (set_color green)
|
||||
set -l c_warn (set_color yellow)
|
||||
set -l c_err (set_color red)
|
||||
set -l c_dim (set_color brblack)
|
||||
set -l c_reset (set_color normal)
|
||||
|
||||
set -q __fish_config_dir; or set -l __fish_config_dir $XDG_CONFIG_HOME/fish
|
||||
set -l list "$XDG_CONFIG_HOME/.user-dots/fish/auto-pull.list"
|
||||
|
||||
set -l cmd $argv[1]
|
||||
set -e argv[1]
|
||||
|
||||
if test "$cmd" = -h; or test "$cmd" = --help; or contains -- -h $argv; or contains -- --help $argv
|
||||
echo "$c_head""Usage:$c_reset $c_cmd""auto-pull$c_reset $c_flag""[list | add | remove | status]$c_reset $c_dim""[PATH|NAME]$c_reset"
|
||||
echo
|
||||
echo " Manage the registry of repos that are background fast-forwarded on entry."
|
||||
echo
|
||||
echo "$c_head""Subcommands:$c_reset"
|
||||
echo " $c_flag""list$c_reset Show registered repos (default)"
|
||||
echo " $c_flag""add$c_reset $c_dim""[PATH]$c_reset Register PATH's git root (default: current repo)"
|
||||
echo " $c_flag""remove$c_reset $c_dim""<NAME|PATH>$c_reset Unregister by basename or exact path"
|
||||
echo " $c_flag""status$c_reset Show enabled/disabled state and registry path"
|
||||
echo " $c_flag-h$c_reset, $c_flag--help$c_reset Show this help message"
|
||||
return 0
|
||||
end
|
||||
|
||||
switch "$cmd"
|
||||
case '' list
|
||||
echo "$c_head""Auto-pull registry:$c_reset"
|
||||
echo " $c_dim$__fish_config_dir$c_reset $c_dim(fish-config, baseline)$c_reset"
|
||||
if test -r "$list"
|
||||
for l in (command cat "$list" 2>/dev/null)
|
||||
test -n "$l"; or continue
|
||||
echo " $l $c_dim("(path basename "$l")")$c_reset"
|
||||
end
|
||||
end
|
||||
return 0
|
||||
|
||||
case add
|
||||
set -l target $argv[1]
|
||||
test -n "$target"; or set target $PWD
|
||||
set -l root (command git -C "$target" rev-parse --show-toplevel 2>/dev/null)
|
||||
if test -z "$root"
|
||||
echo "$c_err""auto-pull: not a git repository: $target$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
if test "$root" = "$__fish_config_dir"
|
||||
echo "$c_warn""auto-pull: fish-config is always covered (baseline); nothing to add$c_reset"
|
||||
return 0
|
||||
end
|
||||
command mkdir -p (path dirname "$list")
|
||||
test -f "$list"; or command touch "$list"
|
||||
if contains -- "$root" (command cat "$list" 2>/dev/null)
|
||||
echo "$c_dim""auto-pull: already registered: $root$c_reset"
|
||||
return 0
|
||||
end
|
||||
echo "$root" >>"$list"
|
||||
echo "$c_ok""→ auto-pull added: $root$c_reset"
|
||||
return 0
|
||||
|
||||
case remove rm
|
||||
set -l target $argv[1]
|
||||
if test -z "$target"
|
||||
echo "$c_err""auto-pull: usage: auto-pull remove <NAME|PATH>$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
if not test -r "$list"
|
||||
echo "$c_dim""auto-pull: registry is empty$c_reset"
|
||||
return 1
|
||||
end
|
||||
set -l kept
|
||||
set -l removed
|
||||
for l in (command cat "$list" 2>/dev/null)
|
||||
test -n "$l"; or continue
|
||||
if test "$l" = "$target"; or test (path basename "$l") = "$target"
|
||||
set -a removed "$l"
|
||||
else
|
||||
set -a kept "$l"
|
||||
end
|
||||
end
|
||||
if test -z "$removed"
|
||||
echo "$c_err""auto-pull: not registered: $target$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
if test -n "$kept"
|
||||
printf '%s\n' $kept >"$list"
|
||||
else
|
||||
printf '' >"$list"
|
||||
end
|
||||
echo "$c_warn""→ auto-pull removed: $removed$c_reset"
|
||||
return 0
|
||||
|
||||
case status
|
||||
if __fish_config_op_enabled __fish_config_op_autoexec
|
||||
echo "$c_ok""auto-pull: ENABLED$c_reset $c_dim(C2 auto-execution on)$c_reset"
|
||||
else
|
||||
echo "$c_warn""auto-pull: DISABLED$c_reset $c_dim(via __fish_config_op_autoexec)$c_reset"
|
||||
end
|
||||
set -l repos
|
||||
test -r "$list"; and set repos (command cat "$list" 2>/dev/null | string match -rv '^\s*$')
|
||||
echo " registered repos: "(count $repos)" $c_dim(+ fish-config baseline)$c_reset"
|
||||
echo " list file: $c_dim$list$c_reset"
|
||||
return 0
|
||||
|
||||
case '*'
|
||||
echo "$c_err""auto-pull: unknown subcommand: $cmd$c_reset" >&2
|
||||
echo "$c_dim""try: auto-pull --help$c_reset" >&2
|
||||
return 1
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user