dotcli: fix help falling through to git.

This commit is contained in:
2026-07-04 01:17:04 -04:00
parent 6ad0f109df
commit 80581f5029
6 changed files with 150 additions and 44 deletions

View File

@@ -3,14 +3,24 @@
## Architecture
`dot` is defined in one file: `~/.config/fish/functions/dot.fish`. It holds
two functions:
three functions:
- `dot` (`--wraps=git`) — dispatches `init` and any file found under
- `dot` (`--wraps=git`) — dispatches `init`, `help`, and any file found under
`~/.config/dot/commands/`, otherwise forwards everything to
`git --git-dir=~/.dotfiles --work-tree=$HOME $argv` (full passthrough).
- `__dot_init` — the bootstrap logic, inlined in the same file rather than
autoloaded separately, because it's the one subcommand that must work
before the dotfiles repo has ever been cloned onto a machine.
- `__dot_help` — prints usage: the built-in commands plus whatever is
currently found under `~/.config/dot/commands/`, generated by globbing that
directory rather than a hardcoded list, so it can't drift from reality.
`__dot_help`'s glob over `~/.config/dot/commands/*.fish` is duplicated in
`~/.config/fish/completions/dot.fish`'s `__dot_custom_subcommands` rather than
shared: fish only autoloads a function from a file named after that function,
so a helper defined inside `dot.fish` would be undefined if tab-completion
ran before `dot` had ever been sourced in the session. Keep both copies in
sync when the listing logic changes.
`dot init`:
@@ -33,9 +43,9 @@ of `dot` itself.
1. Create `~/.config/dot/commands/<name>.fish` defining a `_dot_<name>`
function.
2. Confirm `dot <name>` dispatches to it. No other wiring is needed —
`~/.config/fish/completions/dot.fish` discovers new command files by
globbing that directory, and `--wraps=git` still covers raw git
subcommands.
`~/.config/fish/completions/dot.fish` and `__dot_help` both discover new
command files by globbing that directory, and `--wraps=git` still covers
raw git subcommands.
3. Add a case to `~/.config/dot/tests/dot.fish` covering it and run
`fishtape ~/.config/dot/tests/dot.fish` until it passes.

View File

@@ -70,3 +70,22 @@ end" >$HOME/.config/dot/commands/mark.fish
dot mark >/dev/null 2>&1
@test "dispatches to a command file under ~/.config/dot/commands/" (cat $marker) = marked
# --- dot help ---
set -gx HOME (mktemp -d)
dot init --url $remote >/dev/null 2>&1
set -l help_output (dot help)
set -l help_status $status
@test "dot help succeeds" $help_status -eq 0
@test "dot help lists init" (string match -q '*init*' -- $help_output; echo $status) -eq 0
@test "dot help mentions git passthrough" (string match -q '*git*' -- $help_output; echo $status) -eq 0
mkdir -p $HOME/.config/dot/commands
echo "function _dot_mark
echo marked
end" >$HOME/.config/dot/commands/mark.fish
set -l help_with_custom (dot help)
@test "dot help lists custom commands found under ~/.config/dot/commands/" (string match -q '*mark*' -- $help_with_custom; echo $status) -eq 0

View File

@@ -1,5 +1,6 @@
function __dot_custom_subcommands
echo init
echo help
path basename $HOME/.config/dot/commands/*.fish 2>/dev/null | path change-extension ''
end

View File

@@ -0,0 +1,80 @@
# This file contains fish universal variable definitions.
# VERSION: 3.0
SETUVAR __done_min_cmd_duration:10000
SETUVAR __done_notification_urgency_level:low
SETUVAR _fisher_jorgebucaran_2F_fishtape_files:\x7e/\x2econfig/fish/functions/fishtape\x2efish\x1e\x7e/\x2econfig/fish/completions/fishtape\x2efish
SETUVAR _fisher_plugins:jorgebucaran/fishtape
SETUVAR _fisher_upgraded_to_4_4:\x1d
SETUVAR pure_begin_prompt_with_current_directory:true
SETUVAR pure_check_for_new_release:false
SETUVAR pure_color_at_sign:pure_color_mute
SETUVAR pure_color_aws_profile:pure_color_warning
SETUVAR pure_color_command_duration:pure_color_warning
SETUVAR pure_color_current_directory:pure_color_primary
SETUVAR pure_color_danger:red
SETUVAR pure_color_dark:black
SETUVAR pure_color_exit_status:pure_color_danger
SETUVAR pure_color_git_branch:pure_color_mute
SETUVAR pure_color_git_dirty:pure_color_mute
SETUVAR pure_color_git_stash:pure_color_info
SETUVAR pure_color_git_unpulled_commits:pure_color_info
SETUVAR pure_color_git_unpushed_commits:pure_color_info
SETUVAR pure_color_hostname:pure_color_mute
SETUVAR pure_color_info:cyan
SETUVAR pure_color_jobs:pure_color_normal
SETUVAR pure_color_k8s_context:pure_color_success
SETUVAR pure_color_k8s_namespace:pure_color_primary
SETUVAR pure_color_k8s_prefix:pure_color_info
SETUVAR pure_color_light:white
SETUVAR pure_color_mute:brblack
SETUVAR pure_color_nixdevshell_prefix:pure_color_info
SETUVAR pure_color_nixdevshell_symbol:pure_color_mute
SETUVAR pure_color_normal:normal
SETUVAR pure_color_prefix_root_prompt:pure_color_danger
SETUVAR pure_color_primary:blue
SETUVAR pure_color_prompt_on_error:pure_color_danger
SETUVAR pure_color_prompt_on_success:pure_color_success
SETUVAR pure_color_success:magenta
SETUVAR pure_color_system_time:pure_color_mute
SETUVAR pure_color_username_normal:pure_color_mute
SETUVAR pure_color_username_root:pure_color_light
SETUVAR pure_color_virtualenv:pure_color_mute
SETUVAR pure_color_warning:yellow
SETUVAR pure_convert_exit_status_to_signal:false
SETUVAR pure_enable_aws_profile:true
SETUVAR pure_enable_container_detection:true
SETUVAR pure_enable_git:true
SETUVAR pure_enable_k8s:false
SETUVAR pure_enable_nixdevshell:false
SETUVAR pure_enable_single_line_prompt:false
SETUVAR pure_enable_virtualenv:true
SETUVAR pure_reverse_prompt_symbol_in_vimode:true
SETUVAR pure_separate_prompt_on_error:false
SETUVAR pure_shorten_prompt_current_directory_length:0
SETUVAR pure_shorten_window_title_current_directory_length:0
SETUVAR pure_show_exit_status:false
SETUVAR pure_show_jobs:false
SETUVAR pure_show_numbered_git_indicator:false
SETUVAR pure_show_prefix_root_prompt:false
SETUVAR pure_show_subsecond_command_duration:false
SETUVAR pure_show_system_time:false
SETUVAR pure_symbol_aws_profile_prefix:
SETUVAR pure_symbol_container_prefix:
SETUVAR pure_symbol_exit_status_prefix:\x7c
SETUVAR pure_symbol_exit_status_separator:\x7c
SETUVAR pure_symbol_git_dirty:\x2a
SETUVAR pure_symbol_git_stash:\u2261
SETUVAR pure_symbol_git_unpulled_commits:\u21e3
SETUVAR pure_symbol_git_unpushed_commits:\u21e1
SETUVAR pure_symbol_k8s_prefix:\u2638
SETUVAR pure_symbol_nixdevshell_prefix:\u2744\ufe0f
SETUVAR pure_symbol_prefix_root_prompt:\x23
SETUVAR pure_symbol_prompt:\u276f
SETUVAR pure_symbol_reverse_prompt:\u276e
SETUVAR pure_symbol_ssh_prefix:
SETUVAR pure_symbol_title_bar_separator:\x2d
SETUVAR pure_symbol_virtualenv_prefix:
SETUVAR pure_system_time_format:\x2b\x25T
SETUVAR pure_threshold_command_duration:5
SETUVAR pure_truncate_prompt_current_directory_keeps:\x2d1
SETUVAR pure_truncate_window_title_current_directory_keeps:\x2d1

View File

@@ -7,6 +7,11 @@ function dot --wraps=git --description 'Manage dotfiles via a bare repo checked
return $status
end
if test "$argv[1]" = help
__dot_help
return $status
end
set -l commands_dir $HOME/.config/dot/commands
set -l command_file $commands_dir/$argv[1].fish
@@ -87,3 +92,23 @@ function __dot_init
echo "dot init: bootstrapped $dotfiles_dir from $url"
end
# The custom-subcommand glob is duplicated (not shared with
# completions/dot.fish) because fish only autoloads a function from a file
# named after that function; a shared helper would go undefined if `dot help`
# ran in a completion context before `dot` itself had ever been sourced.
function __dot_help
echo "dot: manage dotfiles via a bare repo checked out over \$HOME"
echo
echo "Commands:"
echo " init bootstrap the dotfiles repo on a new machine"
echo " help show this message"
for f in $HOME/.config/dot/commands/*.fish
test -e $f; or continue
echo " "(path basename $f | path change-extension '')
end
echo
echo "Any other command is passed through to git (dot status, dot add, dot commit, dot push, ...)."
end

View File

@@ -1,27 +1,10 @@
# dotfiles
Managed as a bare git repo checked out over `$HOME`. Not cloned in the usual
sense — `git --git-dir=~/.dotfiles --work-tree=$HOME` treats `$HOME` itself as
the working tree.
## Everyday use
A fish function named `dot` wraps that invocation:
```fish
dot status
dot add .bashrc
dot commit -m 'update bashrc'
dot push
```
Any git subcommand works — `dot` forwards whatever you type straight to git.
Dotfiles managed as a bare git repo checked out over `$HOME`, for machines
running CachyOS with KDE Plasma.
## Bootstrapping a new machine
Before `dot` exists there's nothing to autoload it from, so the very first
step is fetching that one file by hand:
```sh
mkdir -p ~/.config/fish/functions
curl -fsSL https://git.alexion.dev/alexion/dotfiles/raw/branch/main/.config/fish/functions/dot.fish \
@@ -29,25 +12,13 @@ curl -fsSL https://git.alexion.dev/alexion/dotfiles/raw/branch/main/.config/fish
fish -c 'dot init'
```
`dot init`:
## Commands
- clones the bare repo to `~/.dotfiles`
- backs up any pre-existing files that would be overwritten by checkout into
`~/.dotfiles-backup/<timestamp>/`
- checks out the tracked files onto `$HOME`
- sets `status.showUntrackedFiles=no` so `dot status` doesn't list all of
`$HOME`
| Command | Description |
| ----------- | ----------------------------------------------- |
| `dot help` | Lists available commands. |
| `dot init` | Bootstraps the dotfiles repo on a new machine. |
| `dot <git>` | Everything else is passed to `git`. |
It refuses to run if `~/.dotfiles` already exists, and it never falls back to
creating a fresh empty repo if the clone fails.
## Adding new subcommands
Beyond `init`, `dot` looks for `~/.config/dot/commands/<name>.fish`. Each file
should define a `_dot_<name>` function; `dot <name> args...` sources the file
and calls it. These files are deliberately *not* under
`~/.config/fish/functions/`, so they never become independently invokable
commands or clutter tab-completion outside of `dot` itself.
`~/.config/fish/completions/dot.fish` discovers them automatically by
scanning that directory, so a new command file gets tab-completion for free.
See [DOT-CLI.md](../.claude/skills/dotfiles/DOT-CLI.md) for the `dot` tool's
internal architecture, bootstrap logic, subcommand dispatch, and test suite.