test: expand test coverage across core functions, enforce strict fish_indent linting, and add network isolation harnesses #143

Merged
rootiest merged 8 commits from test/network-edge-case-coverage into main 2026-09-10 01:00:47 +00:00
64 changed files with 3068 additions and 978 deletions
+4 -5
View File
@@ -3,7 +3,7 @@
function __bd_debug
set -l file "$BASH_COMP_DEBUG_FILE"
if test -n "$file"
echo "$argv" >> $file
echo "$argv" >>$file
end
end
@@ -108,7 +108,6 @@ function __bd_requires_order_preservation
return 1
end
# This function does two things:
# - Obtain the completions and store them in the global __bd_comp_results
# - Return false if file completion should be performed
@@ -215,18 +214,18 @@ end
# so we can properly delete any completions provided by another script.
# Only do this if the program can be found, or else fish may print some errors; besides,
# the existing completions will only be loaded if the program can be found.
if type -q "bd"
if type -q bd
# The space after the program name is essential to trigger completion for the program
# and not completion of the program name itself.
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
complete --do-complete "bd " > /dev/null 2>&1
complete --do-complete "bd " >/dev/null 2>&1
end
# Remove any pre-existing completions for the program since we will be handling all of them.
complete -c bd -e
# this will get called after the two calls below and clear the $__bd_perform_completion_once_result global
complete -c bd -n '__bd_clear_perform_completion_once_result'
complete -c bd -n __bd_clear_perform_completion_once_result
# The call to __bd_prepare_completions will setup __bd_comp_results
# which provides the program's completion choices.
# If this doesn't require order preservation, we don't use the -k flag
+22 -22
View File
@@ -1,14 +1,14 @@
# fish completion for copilot
# Generated by `copilot completion fish`. Do not edit by hand.
complete -c copilot -n '__fish_use_subcommand' -f -a 'login' -d 'Authenticate with Copilot'
complete -c copilot -n '__fish_use_subcommand' -f -a 'help' -d 'Display help information'
complete -c copilot -n '__fish_use_subcommand' -f -a 'init' -d 'Initialize Copilot instructions'
complete -c copilot -n '__fish_use_subcommand' -f -a 'update' -d 'Download the latest version'
complete -c copilot -n '__fish_use_subcommand' -f -a 'version' -d 'Display version information'
complete -c copilot -n '__fish_use_subcommand' -f -a 'plugin' -d 'Manage plugins'
complete -c copilot -n '__fish_use_subcommand' -f -a 'mcp' -d 'Manage MCP servers'
complete -c copilot -n '__fish_use_subcommand' -f -a 'completion' -d 'Generate a shell completion script'
complete -c copilot -n __fish_use_subcommand -f -a login -d 'Authenticate with Copilot'
complete -c copilot -n __fish_use_subcommand -f -a help -d 'Display help information'
complete -c copilot -n __fish_use_subcommand -f -a init -d 'Initialize Copilot instructions'
complete -c copilot -n __fish_use_subcommand -f -a update -d 'Download the latest version'
complete -c copilot -n __fish_use_subcommand -f -a version -d 'Display version information'
complete -c copilot -n __fish_use_subcommand -f -a plugin -d 'Manage plugins'
complete -c copilot -n __fish_use_subcommand -f -a mcp -d 'Manage MCP servers'
complete -c copilot -n __fish_use_subcommand -f -a completion -d 'Generate a shell completion script'
complete -c copilot -l version -s v -f -d 'show version information'
complete -c copilot -l interactive -s i -r -d 'Start interactive mode and automatically execute this prompt'
complete -c copilot -l prompt -s p -r -d 'Execute a prompt in non-interactive mode (exits after completion)'
@@ -75,22 +75,22 @@ complete -c copilot -l acp -f -d 'Start as Agent Client Protocol server'
complete -c copilot -l remote -f -d 'Enable remote control of your session from GitHub web and mobile'
complete -c copilot -l no-remote -f -d 'Disable remote control of your session from GitHub web and mobile'
complete -c copilot -n '__fish_seen_subcommand_from login' -l host -r -d 'GitHub host URL (default: https://github.com)'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'install' -d 'Install a plugin'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'uninstall' -d 'Uninstall a plugin'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'update' -d 'Update a plugin'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'list' -d 'List installed plugins'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'marketplace' -d 'Manage plugin marketplaces'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a install -d 'Install a plugin'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a uninstall -d 'Uninstall a plugin'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a update -d 'Update a plugin'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a list -d 'List installed plugins'
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a marketplace -d 'Manage plugin marketplaces'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l all -f -d 'Update all installed plugins'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'add' -d 'Add a marketplace'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'remove' -d 'Remove a marketplace'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'list' -d 'List registered marketplaces'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'browse' -d 'Browse plugins in a marketplace'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'update' -d 'Update marketplace plugin catalogs'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a add -d 'Add a marketplace'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a remove -d 'Remove a marketplace'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a list -d 'List registered marketplaces'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a browse -d 'Browse plugins in a marketplace'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a update -d 'Update marketplace plugin catalogs'
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace; and __fish_seen_subcommand_from remove' -l force -s f -f -d 'Force removal even if plugins are installed'
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'list' -d 'List configured MCP servers'
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'get' -d 'Show server details'
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'add' -d 'Add an MCP server'
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'remove' -d 'Remove an MCP server'
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a list -d 'List configured MCP servers'
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a get -d 'Show server details'
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a add -d 'Add an MCP server'
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a remove -d 'Remove an MCP server'
complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from list' -l json -f -d 'Output as JSON'
complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from get' -l json -f -d 'Output as JSON'
complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from get' -l show-secrets -f -d 'Show full environment variable and header values (masked by default)'
+60 -60
View File
@@ -1,38 +1,38 @@
# Print an optspec for argparse to handle cmd's options that are independent of any subcommand.
function __fish_deadbranch_global_optspecs
string join \n h/help V/version
string join \n h/help V/version
end
function __fish_deadbranch_needs_command
# Figure out if the current invocation already has a command.
set -l cmd (commandline -opc)
set -e cmd[1]
argparse -s (__fish_deadbranch_global_optspecs) -- $cmd 2>/dev/null
or return
if set -q argv[1]
# Also print the command, so this can be used to figure out what it is.
echo $argv[1]
return 1
end
return 0
# Figure out if the current invocation already has a command.
set -l cmd (commandline -opc)
set -e cmd[1]
argparse -s (__fish_deadbranch_global_optspecs) -- $cmd 2>/dev/null
or return
if set -q argv[1]
# Also print the command, so this can be used to figure out what it is.
echo $argv[1]
return 1
end
return 0
end
function __fish_deadbranch_using_subcommand
set -l cmd (__fish_deadbranch_needs_command)
test -z "$cmd"
and return 1
contains -- $cmd[1] $argv
set -l cmd (__fish_deadbranch_needs_command)
test -z "$cmd"
and return 1
contains -- $cmd[1] $argv
end
complete -c deadbranch -n "__fish_deadbranch_needs_command" -s h -l help -d 'Print help'
complete -c deadbranch -n "__fish_deadbranch_needs_command" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "list" -d 'List stale branches'
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "clean" -d 'Delete stale branches (merged only by default, use --force for unmerged)'
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "config" -d 'Manage configuration'
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "backup" -d 'Manage backups'
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "stats" -d 'Show repository branch statistics'
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "completions" -d 'Generate shell completion scripts'
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n __fish_deadbranch_needs_command -s h -l help -d 'Print help'
complete -c deadbranch -n __fish_deadbranch_needs_command -s V -l version -d 'Print version'
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a list -d 'List stale branches'
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a clean -d 'Delete stale branches (merged only by default, use --force for unmerged)'
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a config -d 'Manage configuration'
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a backup -d 'Manage backups'
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a stats -d 'Show repository branch statistics'
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a completions -d 'Generate shell completion scripts'
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a help -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -s d -l days -d 'Only show branches older than N days (default: from config or 30)' -r
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -l local -d 'Only show local branches'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -l remote -d 'Only show remote branches'
@@ -51,11 +51,11 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand clean" -s h -l hel
complete -c deadbranch -n "__fish_deadbranch_using_subcommand clean" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -s h -l help -d 'Print help'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "set" -d 'Set a configuration value'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "show" -d 'Show current configuration'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "edit" -d 'Open config file in $EDITOR'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "reset" -d 'Reset configuration to defaults'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a set -d 'Set a configuration value'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a show -d 'Show current configuration'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a edit -d 'Open config file in $EDITOR'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a reset -d 'Reset configuration to defaults'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from set" -s h -l help -d 'Print help'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from set" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from show" -s h -l help -d 'Print help'
@@ -64,18 +64,18 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from edit" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from reset" -s h -l help -d 'Print help'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from reset" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "set" -d 'Set a configuration value'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "show" -d 'Show current configuration'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "edit" -d 'Open config file in $EDITOR'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "reset" -d 'Reset configuration to defaults'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a set -d 'Set a configuration value'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a show -d 'Show current configuration'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a edit -d 'Open config file in $EDITOR'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a reset -d 'Reset configuration to defaults'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -s h -l help -d 'Print help'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "list" -d 'List available backups'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "restore" -d 'Restore a branch from backup'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "stats" -d 'Show backup storage statistics'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "clean" -d 'Remove old backups, keeping the most recent ones'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a list -d 'List available backups'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a restore -d 'Restore a branch from backup'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a stats -d 'Show backup storage statistics'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a clean -d 'Remove old backups, keeping the most recent ones'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -l repo -d 'Show backups for a specific repository by name' -r
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -l current -d 'Only show backups for current repository'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help'
@@ -94,28 +94,28 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s y -l yes -d 'Skip confirmation prompt'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s h -l help -d 'Print help'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "list" -d 'List available backups'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "restore" -d 'Restore a branch from backup'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "stats" -d 'Show backup storage statistics'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "clean" -d 'Remove old backups, keeping the most recent ones'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a list -d 'List available backups'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a restore -d 'Restore a branch from backup'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a stats -d 'Show backup storage statistics'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a clean -d 'Remove old backups, keeping the most recent ones'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s d -l days -d 'Treat branches older than N days as stale (default: from config or 30)' -r
complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s h -l help -d 'Print help'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand completions" -s h -l help -d 'Print help'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand completions" -s V -l version -d 'Print version'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "list" -d 'List stale branches'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "clean" -d 'Delete stale branches (merged only by default, use --force for unmerged)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "config" -d 'Manage configuration'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "backup" -d 'Manage backups'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "stats" -d 'Show repository branch statistics'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "completions" -d 'Generate shell completion scripts'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "set" -d 'Set a configuration value'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "show" -d 'Show current configuration'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "edit" -d 'Open config file in $EDITOR'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "reset" -d 'Reset configuration to defaults'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "list" -d 'List available backups'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "restore" -d 'Restore a branch from backup'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "stats" -d 'Show backup storage statistics'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "clean" -d 'Remove old backups, keeping the most recent ones'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a list -d 'List stale branches'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a clean -d 'Delete stale branches (merged only by default, use --force for unmerged)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a config -d 'Manage configuration'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a backup -d 'Manage backups'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a stats -d 'Show repository branch statistics'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a completions -d 'Generate shell completion scripts'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a set -d 'Set a configuration value'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a show -d 'Show current configuration'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a edit -d 'Open config file in $EDITOR'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a reset -d 'Reset configuration to defaults'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a list -d 'List available backups'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a restore -d 'Restore a branch from backup'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a stats -d 'Show backup storage statistics'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a clean -d 'Remove old backups, keeping the most recent ones'
+4 -5
View File
@@ -3,7 +3,7 @@
function __ov_debug
set -l file "$BASH_COMP_DEBUG_FILE"
if test -n "$file"
echo "$argv" >> $file
echo "$argv" >>$file
end
end
@@ -108,7 +108,6 @@ function __ov_requires_order_preservation
return 1
end
# This function does two things:
# - Obtain the completions and store them in the global __ov_comp_results
# - Return false if file completion should be performed
@@ -215,18 +214,18 @@ end
# so we can properly delete any completions provided by another script.
# Only do this if the program can be found, or else fish may print some errors; besides,
# the existing completions will only be loaded if the program can be found.
if type -q "ov"
if type -q ov
# The space after the program name is essential to trigger completion for the program
# and not completion of the program name itself.
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
complete --do-complete "ov " > /dev/null 2>&1
complete --do-complete "ov " >/dev/null 2>&1
end
# Remove any pre-existing completions for the program since we will be handling all of them.
complete -c ov -e
# this will get called after the two calls below and clear the $__ov_perform_completion_once_result global
complete -c ov -n '__ov_clear_perform_completion_once_result'
complete -c ov -n __ov_clear_perform_completion_once_result
# The call to __ov_prepare_completions will setup __ov_comp_results
# which provides the program's completion choices.
# If this doesn't require order preservation, we don't use the -k flag
+2 -3
View File
@@ -21,7 +21,7 @@ end
function __tailscale_debug
set -l file "$BASH_COMP_DEBUG_FILE"
if test -n "$file"
echo "$argv" >> $file
echo "$argv" >>$file
end
end
@@ -125,7 +125,6 @@ function __tailscale_requires_order_preservation
return 1
end
# This function does two things:
# - Obtain the completions and store them in the global __tailscale_comp_results
# - Return false if file completion should be performed
@@ -242,7 +241,7 @@ end
complete -c tailscale -e
# this will get called after the two calls below and clear the $__tailscale_perform_completion_once_result global
complete -c tailscale -n '__tailscale_clear_perform_completion_once_result'
complete -c tailscale -n __tailscale_clear_perform_completion_once_result
# The call to __tailscale_prepare_completions will setup __tailscale_comp_results
# which provides the program's completion choices.
# If this doesn't require order preservation, we don't use the -k flag
+65 -66
View File
@@ -80,69 +80,68 @@ set -g __fish_config_op_registry_keys \
"zoxide:"
set -g __fish_config_op_registry_values \
"autoexec/venv" \
"logging/terminal-capture" \
"autoexec/sync" \
"logging/multiplexer-capture" \
"integrations/terminal-abbrs" \
"overrides/key-bindings" \
"aliases/dev-tools" \
"autoexec/sync" \
"overrides/key-bindings" \
"aliases/shell-tools" \
"overrides/key-bindings" \
"aliases/filesystem" \
"aliases/dev-tools" \
"aliases/filesystem" \
"overrides/key-bindings" \
"overrides/environment" \
"overrides/environment" \
"overrides/key-bindings" \
"greeting/greeting-message" \
"overrides/environment" \
"overrides/environment" \
"overrides/privacy" \
"overrides/key-bindings" \
"integrations/notifications" \
"aliases/filesystem" \
"aliases/dev-tools" \
"autoexec/plugin-management" \
"greeting/first-run" \
"overrides/prompt" \
"aliases/shell-tools" \
"integrations/history-logs" \
"overrides/key-bindings" \
"logging/terminal-capture" \
"logging/terminal-capture" \
"aliases/shell-tools" \
"integrations/history-logs" \
"aliases/filesystem" \
"aliases/filesystem" \
"aliases/filesystem" \
"autoexec/pkg-wrappers" \
"logging/pkg-logs" \
"aliases/network" \
"overrides/key-bindings" \
"aliases/search" \
"aliases/filesystem" \
"overrides/key-bindings" \
"logging/terminal-capture" \
"integrations/window-mgmt" \
"integrations/window-mgmt" \
"aliases/network" \
"overrides/prompt" \
"integrations/window-mgmt" \
"overrides/prompt" \
"logging/multiplexer-capture" \
"aliases/monitor" \
"aliases/filesystem" \
"overrides/key-bindings" \
"overrides/environment" \
"integrations/pkg-upgrade" \
"autoexec/telemetry" \
"integrations/notifications" \
"autoexec/pkg-wrappers" \
"logging/pkg-logs" \
"aliases/network" \
"aliases/filesystem"
autoexec/venv \
logging/terminal-capture \
autoexec/sync \
logging/multiplexer-capture \
integrations/terminal-abbrs \
overrides/key-bindings \
aliases/dev-tools \
autoexec/sync \
overrides/key-bindings \
aliases/shell-tools \
overrides/key-bindings \
aliases/filesystem \
aliases/dev-tools \
aliases/filesystem \
overrides/key-bindings \
overrides/environment \
overrides/environment \
overrides/key-bindings \
greeting/greeting-message \
overrides/environment \
overrides/environment \
overrides/privacy \
overrides/key-bindings \
integrations/notifications \
aliases/filesystem \
aliases/dev-tools \
autoexec/plugin-management \
greeting/first-run \
overrides/prompt \
aliases/shell-tools \
integrations/history-logs \
overrides/key-bindings \
logging/terminal-capture \
logging/terminal-capture \
aliases/shell-tools \
integrations/history-logs \
aliases/filesystem \
aliases/filesystem \
aliases/filesystem \
autoexec/pkg-wrappers \
logging/pkg-logs \
aliases/network \
overrides/key-bindings \
aliases/search \
aliases/filesystem \
overrides/key-bindings \
logging/terminal-capture \
integrations/window-mgmt \
integrations/window-mgmt \
aliases/network \
overrides/prompt \
integrations/window-mgmt \
overrides/prompt \
logging/multiplexer-capture \
aliases/monitor \
aliases/filesystem \
overrides/key-bindings \
overrides/environment \
integrations/pkg-upgrade \
autoexec/telemetry \
integrations/notifications \
autoexec/pkg-wrappers \
logging/pkg-logs \
aliases/network \
aliases/filesystem
+19 -15
View File
@@ -17,8 +17,10 @@ function expand_bang_all --description 'Execute expand_bang_all'
__fish_config_op_enabled (status basename); or return 1
set -l token $argv[1]
if test -z "$token"; set token (commandline -t); end
if test -z "$token"
set token (commandline -t)
end
set -l tokens (string split -n " " -- $history[1])
if test (count $tokens) -gt 1
echo -- (string join " " -- $tokens[2..-1])
@@ -46,12 +48,14 @@ function expand_bang_minus_n --description 'Execute expand_bang_minus_n'
__fish_config_op_enabled (status basename); or return 1
set -l token $argv[1]
if test -z "$token"; set token (commandline -t); end
if test -z "$token"
set token (commandline -t)
end
# Extract the number from the regex match
if string match -qr '!-(\d+)' -- "$token"
set -l n (string match -r '!-(\d+)' -- "$token")[2]
if test (count $history) -ge $n
echo -- $history[$n]
else
@@ -71,20 +75,20 @@ function expand_bang_search --description 'Execute expand_bang_search'
if test -z "$token"
set token (commandline -t)
end
# Extract query: looks for text after !? and before an optional ?
set -l query (string match -r '!\?([^?]+)' -- $token)[2]
if test -n "$query"
# Search history for a match anywhere in the command
set -l match (builtin history search --contains --max=1 -- $query)
if test -n "$match"
echo -- $match
return
end
end
echo -- $token
end
@@ -98,20 +102,20 @@ function expand_bang_string --description 'Execute expand_bang_string'
if test -z "$token"
set token (commandline -t)
end
# Remove the '!' to get the search query
set -l query (string sub -s 2 -- $token)
if test -n "$query"
# Search history for a prefix match
set -l match (builtin history search --prefix --max=1 -- $query)
if test -n "$match"
echo -- $match
return
end
end
# If no match or empty query, return the token so it doesn't vanish
echo -- $token
end
@@ -128,12 +132,12 @@ function expand_typo_sub --description 'Execute expand_typo_sub'
if test -z "$current_token"
set current_token (commandline -t)
end
if string match -qr '\^([^^]+)\^([^^]*)' -- "$current_token"
set -l captured (string match -r '\^([^^]+)\^([^^]*)' -- "$current_token")
set -l old $captured[2]
set -l new $captured[3]
if test -n "$old"
# Using -- to ensure strings starting with '-' aren't treated as flags
echo -- (string replace -a -- "$old" "$new" "$last_cmd")
+5 -6
View File
@@ -60,7 +60,7 @@ printf '%s\n' \
"cmd_str=\"$_paru_real\"" \
'for arg in "$@"; do' \
' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \
'done' \
done \
'script -q -e -c "$cmd_str" "$log_file"' \
'exit_code=$?' \
'' \
@@ -70,19 +70,18 @@ printf '%s\n' \
'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \
'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \
' python3 "$cleaner" < "$log_file" > "${log_file}.tmp" 2>/dev/null && mv "${log_file}.tmp" "$log_file" || rm -f "${log_file}.tmp"' \
'else' \
else \
' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \
'fi' \
fi \
'' \
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
'mapfile -t logs < <(ls -1t "$log_dir"/paru_*.log 2>/dev/null)' \
'excess=$(( ${#logs[@]} - max_files ))' \
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
' rm -f "${logs[$i]}"' \
'done' \
done \
'' \
'exit $exit_code' \
> $_paru_wrapper
'exit $exit_code' >$_paru_wrapper
chmod +x $_paru_wrapper
+27 -26
View File
@@ -20,37 +20,38 @@ __fish_config_op_enabled (status basename) wakatime-autoexec; or exit
__fish_config_op_enabled (status basename) wakatime-hook; or exit
function __register_wakatime_fish_before_exec -e fish_postexec
if set -q FISH_WAKATIME_DISABLED
return 0
end
set -l exec_command_str
if set -q FISH_WAKATIME_DISABLED
return 0
end
set exec_command_str (string split -f1 ' ' "$argv")
set -l exec_command_str
if test "$exec_command_str" = 'exit'
return 0
end
set exec_command_str (string split -f1 ' ' "$argv")
set -l PLUGIN_NAME "ik11235/wakatime.fish"
set -l PLUGIN_VERSION "0.0.6"
if test "$exec_command_str" = exit
return 0
end
set -l project
set -l wakatime_path
set -l PLUGIN_NAME "ik11235/wakatime.fish"
set -l PLUGIN_VERSION "0.0.6"
if type -p wakatime 2>&1 > /dev/null
set wakatime_path (type -p wakatime)
else if type -p ~/.wakatime/wakatime-cli 2>&1 > /dev/null
set wakatime_path (type -p ~/.wakatime/wakatime-cli)
else
return 1
end
set -l project
set -l wakatime_path
if git rev-parse --is-inside-work-tree &> /dev/null
set project (basename (git rev-parse --show-toplevel))
else
set project "Terminal"
end
if type -p wakatime 2>&1 >/dev/null
set wakatime_path (type -p wakatime)
else if type -p ~/.wakatime/wakatime-cli 2>&1 >/dev/null
set wakatime_path (type -p ~/.wakatime/wakatime-cli)
else
return 1
end
$wakatime_path --write --plugin "$PLUGIN_NAME/$PLUGIN_VERSION" --entity-type app --project "$project" --entity "$exec_command_str" &> /dev/null&; disown
if git rev-parse --is-inside-work-tree &>/dev/null
set project (basename (git rev-parse --show-toplevel))
else
set project Terminal
end
$wakatime_path --write --plugin "$PLUGIN_NAME/$PLUGIN_VERSION" --entity-type app --project "$project" --entity "$exec_command_str" &>/dev/null &
disown
end
+5 -6
View File
@@ -60,7 +60,7 @@ printf '%s\n' \
"cmd_str=\"$_yay_real\"" \
'for arg in "$@"; do' \
' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \
'done' \
done \
'script -q -e -c "$cmd_str" "$log_file"' \
'exit_code=$?' \
'' \
@@ -70,19 +70,18 @@ printf '%s\n' \
'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \
'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \
' python3 "$cleaner" < "$log_file" > "${log_file}.tmp" 2>/dev/null && mv "${log_file}.tmp" "$log_file" || rm -f "${log_file}.tmp"' \
'else' \
else \
' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \
'fi' \
fi \
'' \
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
'mapfile -t logs < <(ls -1t "$log_dir"/yay_*.log 2>/dev/null)' \
'excess=$(( ${#logs[@]} - max_files ))' \
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
' rm -f "${logs[$i]}"' \
'done' \
done \
'' \
'exit $exit_code' \
> $_yay_wrapper
'exit $exit_code' >$_yay_wrapper
chmod +x $_yay_wrapper
+1 -1
View File
@@ -253,7 +253,7 @@ if status is-interactive
#
# Resolve user-dots path. Customize via: set -U __fish_user_dots_path /your/path
set -q __fish_user_dots_path
or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish"
or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish"
# ────────────────────── user-dots convenience symlink ───────────────────
# Keep $__fish_config_dir/user-dots tracking the resolved path so it can be
# browsed from the fish config dir. Git-ignored. Controlled by the
+8 -2
View File
@@ -99,10 +99,16 @@ def render(registry: dict[str, list[str]]) -> str:
lines += [f" {k} \\" for k in quoted_keys[:-1]] + [f" {quoted_keys[-1]}"]
lines.append("")
values = ['"' + " ".join(registry[k]) + '"' for k in keys]
values = [
(
'"' + " ".join(registry[k]) + '"'
if any(c in " ".join(registry[k]) for c in ' \t*?[]"\'\\$')
else " ".join(registry[k])
)
for k in keys
]
lines.append("set -g __fish_config_op_registry_values \\")
lines += [f" {v} \\" for v in values[:-1]] + [f" {values[-1]}"]
lines.append("")
return "\n".join(lines) + "\n"
+11 -11
View File
@@ -25,19 +25,19 @@ function __auto_source_fallback_venv --on-variable PWD
if set -q DIRENV_DIR; or test -e ".envrc"
return
end
# 2. If we are already in a venv, check if we've left its tree
if set -q VIRTUAL_ENV
# Check if the current PWD is still within the directory that owns the venv
# (Assuming the venv is at the root of the project)
set -l venv_root (string replace -r '/.venv$' '' $VIRTUAL_ENV)
if not string match -q "$venv_root*" "$PWD"
type -q deactivate; and deactivate
end
return
if set -q VIRTUAL_ENV
# Check if the current PWD is still within the directory that owns the venv
# (Assuming the venv is at the root of the project)
set -l venv_root (string replace -r '/.venv$' '' $VIRTUAL_ENV)
if not string match -q "$venv_root*" "$PWD"
type -q deactivate; and deactivate
end
# 3. Only source the venv if we aren't already in one
return
end
# 3. Only source the venv if we aren't already in one
if test -e ".venv/bin/activate.fish"
source .venv/bin/activate.fish
end
+2 -2
View File
@@ -27,8 +27,8 @@
# __config_settings_apply __fish_config_op_greeting session DEFAULT
function __config_settings_apply
set -l varname $argv[1]
set -l scope $argv[2]
set -l value $argv[3]
set -l scope $argv[2]
set -l value $argv[3]
# stderr is suppressed because setting a value in one scope while the
# other scope already holds the same variable makes interactive fish
+2 -2
View File
@@ -27,8 +27,8 @@
# __config_settings_set_value __fish_user_dots_path path '' # reset
function __config_settings_set_value
set -l varname $argv[1]
set -l type $argv[2]
set -l value $argv[3]
set -l type $argv[2]
set -l value $argv[3]
# stderr suppressed: editing a universal while a global of the same name
# shadows it makes interactive fish emit a shadow warning that would
+3 -3
View File
@@ -28,9 +28,9 @@ function __config_settings_subcats --description 'List the sub-categories for on
case __fish_config_op_aliases
printf '%s\t%s\t%s\n' \
filesystem Filesystem "ls, cat, cd, du, mkdir, rm, mv, zoxide" \
search Search "rg" \
search Search rg \
network Network "ping, ssh, yt-dlp" \
monitor Monitor "top" \
monitor Monitor top \
shell-tools Shell-tools "bash, less, help" \
dev-tools Dev-tools "claude, edit, agy"
case __fish_config_op_autoexec
@@ -52,7 +52,7 @@ function __config_settings_subcats --description 'List the sub-categories for on
window-mgmt Window-mgmt "spwin, tab, split" \
notifications Notifications "done, WakaTime hook" \
history-logs History-logs "hist, logs" \
pkg-upgrade Pkg-upgrade "upgrade"
pkg-upgrade Pkg-upgrade upgrade
case __fish_config_op_logging
printf '%s\t%s\t%s\n' \
terminal-capture Term-capture "Kitty watcher, smart_exit scrollback" \
+2 -2
View File
@@ -34,7 +34,7 @@
function __fish_user_dots_link --description 'Manage the user-dots convenience symlink'
set -l link "$__fish_config_dir/user-dots"
set -q __fish_user_dots_path
or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish"
or set -l __fish_user_dots_path "$XDG_CONFIG_HOME/.user-dots/fish"
# Explicit opt-out: remove our symlink (never a real file/dir) and stop.
__fish_variable_check __fish_user_dots_symlink
@@ -49,7 +49,7 @@ function __fish_user_dots_link --description 'Manage the user-dots convenience s
test -d "$__fish_user_dots_path"; or return 0
if test -L "$link"
test (readlink "$link") != "$__fish_user_dots_path"
and ln -sfn "$__fish_user_dots_path" "$link"
and ln -sfn "$__fish_user_dots_path" "$link"
else if not test -e "$link"
ln -s "$__fish_user_dots_path" "$link"
end
+1 -1
View File
@@ -14,7 +14,7 @@
function __insert_previous_path_head
# Get the last command tokens
set -l tokens (string split -n " " -- $history[1])
# If there are tokens, take the last one and strip the 'tail'
if set -q tokens[-1]
set -l path_head (dirname -- $tokens[-1])
+4 -4
View File
@@ -14,19 +14,19 @@
function __interactive_history_sub
set -l current_line (commandline -b)
set -l last_cmd $history[1]
if string match -qr '(.+)/(.+)' -- "$current_line"
set -l parts (string split '/' -- "$current_line")
set -l old $parts[1]
set -l new $parts[2]
set -l history_index 1
if test (count $parts) -ge 3; and string match -qr '^[1-9][0-9]*$' -- "$parts[3]"
set history_index $parts[3]
end
set -l target_cmd $history[$history_index]
if test -n "$target_cmd"
set -l expanded (string replace -a -- "$old" "$new" "$target_cmd")
commandline -r "$expanded"
+5 -5
View File
@@ -16,21 +16,21 @@
function __substitute_typo
set -l cursor_pos (commandline -C)
set -l cmd (commandline)
# Check if the current line matches the ^old^new pattern
if string match -qr '\^([^^]+)\^([^^]*)' -- "$cmd"
set -l last_cmd $history[1]
set -l captured (string match -r '\^([^^]+)\^([^^]*)' -- "$cmd")
set -l old $captured[2]
set -l new $captured[3]
if test -n "$old"
set -l expanded (string replace -a -- "$old" "$new" "$last_cmd")
commandline -r "$expanded"
# No need to move cursor, it's a whole new line
end
else
# If it's just a normal caret (not part of a pattern), just insert it
end
else
# If it's just a normal caret (not part of a pattern), just insert it
commandline -i '^'
end
end
+2 -2
View File
@@ -38,8 +38,8 @@ function _agents_init_ensure_gitignore
return 1
end
set -l root $argv[1]
set -l label $argv[2]
set -l root $argv[1]
set -l label $argv[2]
set -l patterns $argv[3..]
set -l gitignore "$root/.gitignore"
+16 -8
View File
@@ -75,14 +75,14 @@ function _fish_deps_install
set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2]
if test -n "$_major"; and test "$_major" -lt 4
set needs_install 1
set upgrade_label "Upgrade"
set upgrade_label Upgrade
end
end
if test $needs_install -eq 1
set -l cargo_crate $_fdc_cargo[$i]
set -l pm_pkg $_fdc_pm[$i]
set -l special $_fdc_special[$i]
set -l cargo_crate $_fdc_cargo[$i]
set -l pm_pkg $_fdc_pm[$i]
set -l special $_fdc_special[$i]
# Build list of available install methods
set -l methods
@@ -193,7 +193,9 @@ function _fish_deps_install
echo " Available methods:"
set -l m 1
for lbl in $method_labels
set_color brblack; echo -n " $m) "; set_color normal
set_color brblack
echo -n " $m) "
set_color normal
echo $lbl
set m (math $m + 1)
end
@@ -202,7 +204,9 @@ function _fish_deps_install
set chosen_method $methods[$_choice]
end
else
set_color brblack; echo " "(string lower $upgrade_label)"ing via $method_labels[1]"; set_color normal
set_color brblack
echo " "(string lower $upgrade_label)"ing via $method_labels[1]"
set_color normal
end
# Execute chosen method
@@ -329,14 +333,18 @@ function _fish_deps_install
if test $status -eq 0
set installed_any 1
set_color green; echo " $bin "(string lower $upgrade_label)"ed."; set_color normal
set_color green
echo " $bin "(string lower $upgrade_label)"ed."
set_color normal
if test "$bin" = fish
set_color yellow
echo " Fish upgraded — restart your shell to use the new version."
set_color normal
end
else
set_color red; echo " $bin "(string lower $upgrade_label)" failed."; set_color normal
set_color red
echo " $bin "(string lower $upgrade_label)" failed."
set_color normal
end
end
set i (math $i + 1)
+34 -12
View File
@@ -23,35 +23,57 @@ function _fish_deps_status
set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2]
if test -n "$_major"; and test "$_major" -lt 4
set -l _ver (fish --version 2>&1 | string replace 'fish, ' '')
set_color yellow; echo -n " ⚠ "; set_color normal
set_color yellow
echo -n " ⚠ "
set_color normal
echo -n "$bin "
set_color brblack; echo "($_ver — upgrade to 4.0+ required)"; set_color normal
set_color brblack
echo "($_ver — upgrade to 4.0+ required)"
set_color normal
return
end
end
set_color green; echo -n " ✓ "; set_color normal
set_color green
echo -n " ✓ "
set_color normal
echo -n "$bin "
set_color brblack; echo "(Found at "(__fish_real_command $bin)")"; set_color normal
set_color brblack
echo "(Found at "(__fish_real_command $bin)")"
set_color normal
else if test "$tier" = req
set_color red; echo -n " ✗ "; set_color normal
set_color red
echo -n " ✗ "
set_color normal
echo -n "$bin "
set_color brblack; echo "(Not installed)"; set_color normal
set_color brblack
echo "(Not installed)"
set_color normal
else if test "$tier" = rec
set_color yellow; echo -n " ⚠ "; set_color normal
set_color yellow
echo -n " ⚠ "
set_color normal
echo -n "$bin "
set_color brblack; echo "(Not installed)"; set_color normal
set_color brblack
echo "(Not installed)"
set_color normal
else
# opt / term / int: absence is expected and not alarming
set_color brblack; echo -n " "; set_color normal
set_color brblack
echo -n " "
set_color normal
echo -n "$bin "
set_color brblack; echo "(Not installed)"; set_color normal
set_color brblack
echo "(Not installed)"
set_color normal
end
end
for tier_label in "Required Dependencies:req" "Recommended Dependencies:rec" "Optional Dependencies:opt" "Terminal Emulators:term" "Integrations:int"
set -l label (string split : $tier_label)[1]
set -l tier (string split : $tier_label)[2]
set_color cyan; echo $label; set_color normal
set -l tier (string split : $tier_label)[2]
set_color cyan
echo $label
set_color normal
set -l i 1
for bin in $_fdc_bins
if test "$_fdc_tiers[$i]" = $tier
+11 -7
View File
@@ -33,9 +33,9 @@ function _fish_deps_update
continue
end
set -l cargo_crate $_fdc_cargo[$i]
set -l pm_pkg $_fdc_pm[$i]
set -l special $_fdc_special[$i]
set -l cargo_crate $_fdc_cargo[$i]
set -l pm_pkg $_fdc_pm[$i]
set -l special $_fdc_special[$i]
# yay: update via paru if available, else system PM
if test "$special" = yay-build
@@ -101,10 +101,14 @@ function _fish_deps_update
echo "Updating $bin..."
set -l _arch (uname -m)
switch $_arch
case x86_64; set _arch amd64
case aarch64 arm64; set _arch arm64
case armv7l; set _arch arm
case '*'; set _arch amd64
case x86_64
set _arch amd64
case aarch64 arm64
set _arch arm64
case armv7l
set _arch arm
case '*'
set _arch amd64
end
set -l _zip "wakatime-cli-linux-$_arch.zip"
set -l _bin_src "wakatime-cli-linux-$_arch"
+1 -1
View File
@@ -46,7 +46,7 @@ function _fish_mkdir_p --description 'mkdir -p with configurable verbose output'
while not test -d $cursor
set -p to_create $cursor
set -l up (dirname $cursor)
test "$up" = "$cursor"; and break # filesystem root guard
test "$up" = "$cursor"; and break # filesystem root guard
set cursor $up
end
-1
View File
@@ -7,4 +7,3 @@ function _puffer_fish_expand_bang
commandline --insert '!'
end
end
+1 -1
View File
@@ -4,7 +4,7 @@ function _puffer_fish_expand_star
else if string match --quiet -- '!' "$(commandline --current-token)"
set -l prev_cmd $history[1]
set -l prev_args (string split ' ' $prev_cmd)
set -e prev_args[1] # remove command name
set -e prev_args[1] # remove command name
set -l arg_str (string join ' ' $prev_args)
# replace !* with all arguments
commandline --current-token ''
+3 -3
View File
@@ -28,9 +28,9 @@ function _smart_execute --description 'Execute different functions based on the
# If it ends in =, run qalc; fall back to normal execute if qalc is absent
_qalc_eval; or commandline -f execute
# case 'g *'
# # EXAMPLE FUTURE EXTENSION
# _some_git_helper
# case 'g *'
# # EXAMPLE FUTURE EXTENSION
# _some_git_helper
case '*'
# Default: execute the command line as-is
+2 -4
View File
@@ -893,8 +893,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
command cp -rn "$stash/." "$entry/" 2>/dev/null
rm -rf "$stash"
end
printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) \
>>"$entry/origin"
printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) >>"$entry/origin"
# Only a link is dropped here, and only so the relink below has
# somewhere to put the new one. Usually $live is exactly that: a
# symlink at the old entry, now dangling. But the migration is
@@ -1078,8 +1077,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
end
set -l reached 1
if git -C "$vault" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
if not $gitnet git -C "$vault" pull --rebase --autostash -q \
>/dev/null 2>/dev/null
if not $gitnet git -C "$vault" pull --rebase --autostash -q >/dev/null 2>/dev/null
# Two unrelated failures land here and reporting them as
# one sends the user hunting for a conflict that never
# existed. A rebase that genuinely started and stopped
+4 -4
View File
@@ -52,10 +52,10 @@ function agy --wraps=agy --description 'agy wrapper: auto-initializes AGENTS/ su
agents-vault --quiet
for i in (seq (count $argv))
if test "$argv[$i]" = "-r"
set argv[$i] "-c"
else if test "$argv[$i]" = "--resume"
set argv[$i] "--continue"
if test "$argv[$i]" = -r
set argv[$i] -c
else if test "$argv[$i]" = --resume
set argv[$i] --continue
else if string match -q -- "--resume=*" "$argv[$i]"
set argv[$i] (string replace -- "--resume=" "--continue=" "$argv[$i]")
end
+17 -8
View File
@@ -25,11 +25,20 @@
function bd-pull --description 'Pull new Gitea issues into local Beads and link them'
__fish_help_header (status current-function) $argv; and return 0
if not set -q argv[1]; echo "Need repo owner/name"; return 1; end
if not set -q GITEA_TOKEN; echo "\$GITEA_TOKEN not set"; return 1; end
if not set -q argv[1]
echo "Need repo owner/name"
return 1
end
if not set -q GITEA_TOKEN
echo "\$GITEA_TOKEN not set"
return 1
end
set -l REPO $argv[1]
if not set -q GITEA_URL; echo "\$GITEA_URL not set"; return 1; end
if not set -q GITEA_URL
echo "\$GITEA_URL not set"
return 1
end
set -l IMPORT_COUNT 0
echo (set_color blue)"📡 Checking Gitea: $REPO..."(set_color normal)
@@ -46,12 +55,12 @@ function bd-pull --description 'Pull new Gitea issues into local Beads and link
# If it doesn't have [ID] brackets, it's a "Web-Original" issue
if not string match -qr "^\[.*\]" "$title"
echo (set_color yellow)" Linking Web Issue #$number: $title"(set_color normal)
# A. Create local Bead and capture the new ID
# This captures the output of bd create to find the ID it generated
set -l bd_output (bd create --title "$title")
set -l bid (echo $bd_output | string match -r "bd-[a-z0-9]+" | head -n 1)
if test -z "$bid"
# Fallback: find the latest ID in the jsonl if regex fails
set bid (tail -n 1 .beads/issues.jsonl | jq -r '.id')
@@ -61,9 +70,9 @@ function bd-pull --description 'Pull new Gitea issues into local Beads and link
# This prevents the Gitea Action from creating a duplicate
set -l new_title "[$bid] $title"
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"title\":\"$new_title\"}" \
"$GITEA_URL/api/v1/repos/$REPO/issues/$number" > /dev/null
-H "Content-Type: application/json" \
-d "{\"title\":\"$new_title\"}" \
"$GITEA_URL/api/v1/repos/$REPO/issues/$number" >/dev/null
set IMPORT_COUNT (math $IMPORT_COUNT + 1)
end
+3 -3
View File
@@ -27,12 +27,12 @@ function branch --description 'Switch to or create a git branch'
echo "Not a git repo."
return 1
end
# Check if the branch already exists locally
if git show-ref --verify --quiet refs/heads/$argv[1]
git checkout $argv
else
# If it doesn't exist, create it
git checkout -b $argv
end
git checkout -b $argv
end
end
+1 -1
View File
@@ -26,5 +26,5 @@ function cheat --wraps='cheat' --description 'alias cheat=cheat -c'
else
man $argv
end
end
+3 -3
View File
@@ -23,9 +23,9 @@ function cleanup --description 'Log orphans to ~/.removed_orphans and remove the
set -l orphans (pacman -Qtdq)
if test -n "$orphans"
echo "📝 Logging orphans to ~/.removed_orphans..."
echo "--- Removed on $(date) ---" >> ~/.removed_orphans
pacman -Qi $orphans | grep -E '^(Name|Version)' >> ~/.removed_orphans
echo "--- Removed on $(date) ---" >>~/.removed_orphans
pacman -Qi $orphans | grep -E '^(Name|Version)' >>~/.removed_orphans
echo "🧹 Removing orphans..."
sudo pacman -Rns $orphans
else
+1 -1
View File
@@ -80,7 +80,7 @@ function config-update --description 'Pull latest fish config from upstream'
end
# ── compare local HEAD to upstream ───────────────────────────
set -l local_sha (git -C "$CONFIG_DIR" rev-parse HEAD 2>/dev/null)
set -l local_sha (git -C "$CONFIG_DIR" rev-parse HEAD 2>/dev/null)
set -l remote_sha (git -C "$CONFIG_DIR" rev-parse _config_update/main 2>/dev/null)
if test "$local_sha" = "$remote_sha"
+9 -9
View File
@@ -25,15 +25,15 @@ function fish_prompt
set -l last_status $status
# Catppuccin Mocha hex palette
set -l c_green '#a6e3a1'
set -l c_red '#f38ba8'
set -l c_green '#a6e3a1'
set -l c_red '#f38ba8'
set -l c_yellow '#f9e2af'
set -l c_text '#cdd6f4'
set -l c_blue '#89b4fa'
set -l c_teal '#94e2d5'
set -l c_pink '#f5c2e7'
set -l c_dim '#6c7086'
set -l c_mauve '#cba6f7'
set -l c_text '#cdd6f4'
set -l c_blue '#89b4fa'
set -l c_teal '#94e2d5'
set -l c_pink '#f5c2e7'
set -l c_dim '#6c7086'
set -l c_mauve '#cba6f7'
# Line/connector color tracks last exit status; brackets stay bold green
set -l retc $c_green
@@ -129,7 +129,7 @@ function fish_prompt
set_color normal
end
echo # newline
echo # newline
# Background jobs (one per line, dim)
for job in (jobs -c)
+3 -3
View File
@@ -8,8 +8,8 @@ function fisher --argument-names cmd --description "A plugin manager for Fish"
echo "fisher, version $fisher_version"
case "" -h --help
echo "Usage: fisher install <plugins...> Install plugins"
echo " fisher remove <plugins...> Remove installed plugins"
echo " fisher uninstall <plugins...> Remove installed plugins (alias)"
echo " fisher remove <plugins...> Remove installed plugins"
echo " fisher uninstall <plugins...> Remove installed plugins (alias)"
echo " fisher update <plugins...> Update installed plugins"
echo " fisher update Update all installed plugins"
echo " fisher list [<regex>] List installed plugins matching regex"
@@ -41,7 +41,7 @@ function fisher --argument-names cmd --description "A plugin manager for Fish"
echo "fisher: \"$fish_plugins\" file not found: \"$cmd\"" >&2 && return 1
end
set arg_plugins $file_plugins
else if test "$cmd" = install && ! set --query old_plugins[1]
else if test "$cmd" = install && ! set --query old_plugins[1]
set --append arg_plugins $file_plugins
end
+1 -1
View File
@@ -25,7 +25,7 @@ function gip6 --description 'Get public IPv6 address'
# Use -6 to force IPv6 and --fail to catch network errors
set -l ip (curl -6 -s --fail https://icanhazip.com 2>/dev/null)
if test $status -eq 0
echo $ip
else
+2 -2
View File
@@ -27,12 +27,12 @@ function gitup --description 'Fetch updates and show git status'
echo "Check your map! You aren't in a git repository."
return 1
end
if count $argv >/dev/null
git fetch $argv
else
git fetch
end
and git status
end
+1 -1
View File
@@ -35,7 +35,7 @@ function hist --description 'Search fish history and put it in the prompt'
if test -n "$selected"
# Strip the timestamp for the final output
set -l command (echo $selected | string replace -r '^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} ' '')
echo $command | wl-copy 2>/dev/null
commandline -r $command
end
+5 -6
View File
@@ -135,11 +135,11 @@ function logs --description 'Browse terminal log files interactively with fzf'
set -l togglescript (mktemp)
set -l deletescript (mktemp)
set -l helpflag "$tmpfile.help"
printf '%s\n' $fzf_lines > $tmpfile
printf 'Terminal Logs — Key Bindings\n\n Enter View selected log in pager\n Ctrl-E Edit selected log in editor\n Ctrl-D Delete selected log\n Ctrl-C Quit\n ? Toggle this help\n\nFiltering:\n Type to fuzzy-filter by date or category\n Use -c flag to limit: scrollback, paru, yay' > $helpfile
printf '%s\n' $fzf_lines >$tmpfile
printf 'Terminal Logs — Key Bindings\n\n Enter View selected log in pager\n Ctrl-E Edit selected log in editor\n Ctrl-D Delete selected log\n Ctrl-C Quit\n ? Toggle this help\n\nFiltering:\n Type to fuzzy-filter by date or category\n Use -c flag to limit: scrollback, paru, yay' >$helpfile
# Help toggle script — avoids nested parens inside transform()
printf '#!/bin/sh\nif test -f %s; then\n rm -f %s\n printf "change-preview(command cat {1})"\nelse\n touch %s\n printf "change-preview(command cat %s)"\nfi\n' \
$helpflag $helpflag $helpflag $helpfile > $togglescript
$helpflag $helpflag $helpflag $helpfile >$togglescript
chmod +x $togglescript
# Delete confirmation script — avoids nested parens inside execute(), and ensures
# tmpfile is updated before +reload fires (execute is synchronous, execute-silent is not)
@@ -151,8 +151,7 @@ function logs --description 'Browse terminal log files interactively with fzf'
'case "$confirm" in [nN]) exit 0 ;; esac' \
"fish -c \"rm \$FILE\"" \
"grep -vF \"\$FILE\" $tmpfile > $tmpfile.new" \
"mv $tmpfile.new $tmpfile" \
> $deletescript
"mv $tmpfile.new $tmpfile" >$deletescript
chmod +x $deletescript
set -l selected (command cat $tmpfile | fzf \
@@ -196,7 +195,7 @@ function logs --description 'Browse terminal log files interactively with fzf'
# is displayed without ov's default slateblue background, preserving
# the original ANSI colors of the starship prompt.
set -l ov_cfg (mktemp --suffix .yaml)
printf 'Mode:\n scrollback:\n Style:\n SectionLine:\n Background: ""\n Foreground: ""\n' > $ov_cfg
printf 'Mode:\n scrollback:\n Style:\n SectionLine:\n Background: ""\n Foreground: ""\n' >$ov_cfg
command ov --config $ov_cfg --view-mode scrollback \
--section-delimiter '\x1b\]133;A' --section-header --section-header-num 1 $file
rm -f $ov_cfg
+64 -16
View File
@@ -187,28 +187,52 @@ function pkg --description 'Install or remove packages via the system package ma
switch $pm
case paru yay
$pm -S $to_install
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case pacman
sudo pacman -S $to_install
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case apt
sudo apt install -y $to_install
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case dnf
sudo dnf install -y $to_install
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case zypper
sudo zypper install -y $to_install
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case brew
brew install $to_install
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case pkg
sudo pkg install -y $to_install
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case yum
sudo yum install -y $to_install
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
end
end
@@ -217,28 +241,52 @@ function pkg --description 'Install or remove packages via the system package ma
switch $pm
case paru yay
$pm -Rns $to_remove
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case pacman
sudo pacman -Rns $to_remove
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case apt
sudo apt remove -y $to_remove
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case dnf
sudo dnf remove -y $to_remove
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case zypper
sudo zypper remove -y $to_remove
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case brew
brew uninstall $to_remove
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case pkg
sudo pkg delete -y $to_remove
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
case yum
sudo yum remove -y $to_remove
or begin; functions -e __pkg_is_installed; return $status; end
or begin
functions -e __pkg_is_installed
return $status
end
end
end
end
+6 -6
View File
@@ -62,9 +62,9 @@ function rand_string --description 'Generate random, memorable strings from cura
return 0
end
set -l sep "dash"
set -l casing "lower"
set -l sep dash
set -l casing lower
# Locate the words directory relative to this function
set -l words_dir ""
if set -q __fish_config_dir
@@ -114,9 +114,9 @@ function rand_string --description 'Generate random, memorable strings from cura
set -l actual_sep $sep
switch $sep
case dash
set actual_sep "-"
set actual_sep -
case underscore
set actual_sep "_"
set actual_sep _
case dot
set actual_sep "."
case none empty
@@ -144,7 +144,7 @@ function rand_string --description 'Generate random, memorable strings from cura
echo "rand_string: unknown category or file missing for '$arg'" >&2
return 1
end
# Fetch random line (shuf is fastest, random choice is portable fallback)
if command -q shuf
set part (command shuf -n 1 "$db")
+2 -2
View File
@@ -17,6 +17,6 @@
# EXAMPLE
# rawfish
function rawfish --wraps='env NO_TMUX=1 fish' --description 'alias rawfish=env NO_TMUX=1 fish'
env NO_TMUX=1 fish $argv
env NO_TMUX=1 fish $argv
end
+4 -4
View File
@@ -90,12 +90,12 @@ function repo-open --description 'Open the origin remote of the current repo in
set had_scheme 1
end
set -l u (string replace -r '^[a-z0-9]+://' '' -- $remote) # strip scheme
set u (string replace -r '^[^@/]+@' '' -- $u) # strip user@
set u (string replace -r '\.git$' '' -- $u) # strip .git
set u (string replace -r '^[^@/]+@' '' -- $u) # strip user@
set u (string replace -r '\.git$' '' -- $u) # strip .git
if test $had_scheme -eq 0
set u (string replace ':' '/' -- $u) # scp: first colon → path sep
set u (string replace ':' '/' -- $u) # scp: first colon → path sep
else
set u (string replace -r ':[0-9]+/' '/' -- $u) # url: drop :port
set u (string replace -r ':[0-9]+/' '/' -- $u) # url: drop :port
end
set -l parts (string split -m1 '/' -- $u)
+43 -43
View File
@@ -24,53 +24,53 @@
# sbver
# sbver --brief
function sbver --description 'Verifies Secure Boot status of EFI binaries using sbctl'
__fish_help_header (status current-function) $argv; and return 0
__fish_help_header (status current-function) $argv; and return 0
if not type -q sbctl
echo "Error: 'sbctl' is not installed."
return 1
end
if not type -q sbctl
echo "Error: 'sbctl' is not installed."
return 1
end
# ANSI color codes (Fish uses set_color for easier management)
set RED (set_color red)
set GREEN (set_color green)
set NC (set_color normal)
# ANSI color codes (Fish uses set_color for easier management)
set RED (set_color red)
set GREEN (set_color green)
set NC (set_color normal)
# Flags
set brief_mode false
if test "$argv[1]" = "--brief"
set brief_mode true
end
# Flags
set brief_mode false
if test "$argv[1]" = --brief
set brief_mode true
end
# Counters
set pass_count 0
set fail_count 0
# Counters
set pass_count 0
set fail_count 0
# Run and process sbctl output
# Fish doesn't use 'done < <()'; we pipe directly into the while loop
sudo sbctl verify 2>&1 | grep -v -i 'invalid pe header' | while read -l line
if string match -q "*✓*" -- "$line"
set pass_count (math $pass_count + 1)
if not $brief_mode
echo -e "$GREEN$line$NC"
end
else if string match -q "*✗*" -- "$line"
set fail_count (math $fail_count + 1)
if not $brief_mode
echo -e "$RED$line$NC"
end
else
if not $brief_mode
echo "$line"
end
end
end
# Run and process sbctl output
# Fish doesn't use 'done < <()'; we pipe directly into the while loop
sudo sbctl verify 2>&1 | grep -v -i 'invalid pe header' | while read -l line
if string match -q "*✓*" -- "$line"
set pass_count (math $pass_count + 1)
if not $brief_mode
echo -e "$GREEN$line$NC"
end
else if string match -q "*✗*" -- "$line"
set fail_count (math $fail_count + 1)
if not $brief_mode
echo -e "$RED$line$NC"
end
else
if not $brief_mode
echo "$line"
end
end
end
# Summary
echo
if test $fail_count -eq 0
echo -e "$GREEN✅ All images are signed ($pass_count verified)$NC"
else
echo -e "$RED❌ Some images are not signed ($fail_count failed, $pass_count passed)$NC"
end
# Summary
echo
if test $fail_count -eq 0
echo -e "$GREEN✅ All images are signed ($pass_count verified)$NC"
else
echo -e "$RED❌ Some images are not signed ($fail_count failed, $pass_count passed)$NC"
end
end
+1 -1
View File
@@ -23,7 +23,7 @@
# spark 1 1 2 5 14 42
# seq 64 | sort --random-sort | spark
# echo "3 7 2 9 1" | spark
function spark --description 'Sparklines'
function spark --description Sparklines
argparse --ignore-unknown --name=spark v/version h/help m/min= M/max= -- $argv || return
if set --query _flag_version[1]
+1 -1
View File
@@ -33,7 +33,7 @@
# set -U -a sponge_filters sponge_filter_secrets
function sponge_filter_secrets --argument-names command
# Find all exported variables with security-sensitive names
set -l sensitive_vars (set --names --export | string match --regex -- \
set -l sensitive_vars (set --names --export | string match --entire --regex -- \
'(?i)(?:TOKEN|PASSWORD|PASSWD|SECRET|API[_-]KEY|PRIVATE[_-]KEY|ACCESS[_-]KEY|AUTH[_-]KEY|CREDENTIAL|KOPIA_PASSWORD)')
for var in $sensitive_vars
+2 -2
View File
@@ -23,7 +23,7 @@ function sudo-toggle --description 'Toggle sudo password requirement on/off'
# Check the file size using sudo stat to see if our bypass rule is active
set -l file_size (sudo stat -c %s /etc/sudoers.d/nofail-toggle 2>/dev/null)
if test -n "$file_size"; and test "$file_size" -gt 0
# 1. Toggle is currently OFF (Bypass is active). We want to turn security back ON.
# We use 'sudo -k' to clear the execution cache so it locks down instantly.
@@ -32,7 +32,7 @@ function sudo-toggle --description 'Toggle sudo password requirement on/off'
else
# 2. Toggle is currently ON (Security active). We want to BYPASS it.
# We write a high-priority user-specific NOPASSWD rule.
echo "$USER ALL=(ALL:ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/nofail-toggle > /dev/null
echo "$USER ALL=(ALL:ALL) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/nofail-toggle >/dev/null
echo "🔓 Sudo security: DISABLED (Bypass active)"
end
end
+2 -2
View File
@@ -22,9 +22,9 @@ function swapstat --description 'View colorized zRAM and swappiness status'
set -l swappiness (sysctl -n vm.swappiness)
set -l zdata (zramctl --bytes --noheadings --output DATA,TOTAL /dev/zram0 2>/dev/null)
echo (set_color --bold blue)"── Memory & zRAM Report ──"(set_color normal)
# Kernel & Compression Stats
if test -n "$zdata"
set -l raw (echo $zdata | awk '{print $1}')
+1 -1
View File
@@ -22,5 +22,5 @@ function view --wraps='nvim -R' --description 'alias view=nvim -R'
else
less $argv
end
end
+1 -1
View File
@@ -70,4 +70,4 @@ end
# ls | cb
function cb --wraps='y' --description 'Alias cb=y'
y $argv
end
end
+1 -1
View File
@@ -62,7 +62,7 @@ function yt-dlp --description 'yt-dlp with embedding + SponsorBlock defaults'
# --sponsorblock-remove all
# Skip if the user set their own remove (bare or --opt=value form), or
# disabled SponsorBlock entirely with --no-sponsorblock.
if not string match -q -- '--sponsorblock-remove' $argv
if not string match -q -- --sponsorblock-remove $argv
and not string match -q -- '--sponsorblock-remove=*' $argv
and not contains -- --no-sponsorblock $argv
set -a extra --sponsorblock-remove all
+441 -437
View File
@@ -13,7 +13,6 @@
# - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS
# Key bindings
# ------------
# The oldest supported fish version is 3.1b1. To maintain compatibility, the
@@ -21,289 +20,289 @@
# check, otherwise the source command will fail on fish versions older than 3.4.0.
function fzf_key_bindings
# Check fish version
if set -l -- fish_ver (string match -r '^(\d+)\.(\d+)' $version 2>/dev/null)
and test "$fish_ver[2]" -lt 3 -o "$fish_ver[2]" -eq 3 -a "$fish_ver[3]" -lt 1
echo "This script requires fish version 3.1b1 or newer." >&2
return 1
else if not type -q fzf
echo "fzf was not found in path." >&2
return 1
end
# bg-transform runs the preview toggle in a background thread (non-blocking).
# It was added after fzf 0.60; fall back to the synchronous transform on older builds.
set -l _fzf_ver (fzf --version | string match -r '^(\d+)\.(\d+)')
set -l _fzf_transform_action transform
if test -n "$_fzf_ver[3]"; and test "$_fzf_ver[3]" -ge 62
set _fzf_transform_action bg-transform
end
#----BEGIN INCLUDE common.fish
# NOTE: Do not directly edit this section, which is copied from "common.fish".
# To modify it, one can edit "common.fish" and run "./update.sh" to apply
# the changes. See code comments in "common.fish" for the implementation details.
function __fzf_defaults
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
string join ' ' -- \
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
$FZF_DEFAULT_OPTS $argv[2..-1]
end
function __fzfcmd
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
if test -n "$FZF_TMUX_OPTS"
echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if test "$FZF_TMUX" = "1"
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else
echo "fzf"
end
end
function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'
set -l tokens
if test (string match -r -- '^\d+' $version) -ge 4
set -- tokens (commandline -xpc)
else
set -- tokens (commandline -opc)
# Check fish version
if set -l -- fish_ver (string match -r '^(\d+)\.(\d+)' $version 2>/dev/null)
and test "$fish_ver[2]" -lt 3 -o "$fish_ver[2]" -eq 3 -a "$fish_ver[3]" -lt 1
echo "This script requires fish version 3.1b1 or newer." >&2
return 1
else if not type -q fzf
echo "fzf was not found in path." >&2
return 1
end
set -l -- var_count 0
for i in $tokens
if string match -qr -- '^[\w]+=' $i
set var_count (math $var_count + 1)
else
break
end
end
set -e -- tokens[0..$var_count]
while true
switch "$tokens[1]"
case builtin command
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
case env
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
while string match -qr -- '^[\w]+=' "$tokens[1]"
set -e -- tokens[1]
end
case '*'
break
end
# bg-transform runs the preview toggle in a background thread (non-blocking).
# It was added after fzf 0.60; fall back to the synchronous transform on older builds.
set -l _fzf_ver (fzf --version | string match -r '^(\d+)\.(\d+)')
set -l _fzf_transform_action transform
if test -n "$_fzf_ver[3]"; and test "$_fzf_ver[3]" -ge 62
set _fzf_transform_action bg-transform
end
string escape -n -- $tokens
end
#----BEGIN INCLUDE common.fish
# NOTE: Do not directly edit this section, which is copied from "common.fish".
# To modify it, one can edit "common.fish" and run "./update.sh" to apply
# the changes. See code comments in "common.fish" for the implementation details.
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l fzf_query ''
set -l prefix ''
set -l dir '.'
set -l -- fish_major (string match -r -- '^\d+' $version)
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
function __fzf_defaults
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
string join ' ' -- \
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
$FZF_DEFAULT_OPTS $argv[2..-1]
end
if test "$fish_major" -ge 4
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
else
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
set -- prefix (string match -r -- $prefix_regex $cl_token)
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
end
if test -n "$fzf_query"
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
set -- fzf_query (path normalize -- $fzf_query)
set -- dir $fzf_query
while not path is -d $dir
set -- dir (path dirname $dir)
end
else
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
function __fzfcmd
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
if test -n "$FZF_TMUX_OPTS"
echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if test "$FZF_TMUX" = 1
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
echo fzf
end
end
function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'
set -l tokens
if test (string match -r -- '^\d+' $version) -ge 4
set -- tokens (commandline -xpc)
else
set -- tokens (commandline -opc)
end
set -l -- var_count 0
for i in $tokens
if string match -qr -- '^[\w]+=' $i
set var_count (math $var_count + 1)
else
break
end
end
set -e -- tokens[0..$var_count]
while true
switch "$tokens[1]"
case builtin command
set -e -- tokens[1]
test "$tokens[1]" = --; and set -e -- tokens[1]
case env
set -e -- tokens[1]
test "$tokens[1]" = --; and set -e -- tokens[1]
while string match -qr -- '^[\w]+=' "$tokens[1]"
set -e -- tokens[1]
end
case '*'
break
end
end
string escape -n -- $tokens
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l fzf_query ''
set -l prefix ''
set -l dir '.'
set -l -- fish_major (string match -r -- '^\d+' $version)
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
end
set -- dir $fzf_query
while not test -d "$dir"
set -- dir (dirname -z -- "$dir" | string split0)
end
end
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
if test "$fish_major" -ge 4
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
(string replace -- "$dir" '' $fzf_query | string collect -N)
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
else
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
set -- prefix (string match -r -- $prefix_regex $cl_token)
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
end
end
if test -n "$fzf_query"
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
set -- fzf_query (path normalize -- $fzf_query)
set -- dir $fzf_query
while not path is -d $dir
set -- dir (path dirname $dir)
end
else
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
else
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
end
set -- dir $fzf_query
while not test -d "$dir"
set -- dir (dirname -z -- "$dir" | string split0)
end
end
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
if test "$fish_major" -ge 4
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
(string replace -- "$dir" '' $fzf_query | string collect -N)
else
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
end
end
end
string escape -n -- "$dir" "$fzf_query" "$prefix"
end
#----END INCLUDE
string escape -n -- "$dir" "$fzf_query" "$prefix"
end
#----END INCLUDE
# Store current token in $dir as root for the 'find' command
function fzf-file-widget -d "List files and folders"
set -l commandline (__fzf_parse_commandline)
set -lx dir $commandline[1]
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
# Store current token in $dir as root for the 'find' command
function fzf-file-widget -d "List files and folders"
set -l commandline (__fzf_parse_commandline)
set -lx dir $commandline[1]
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
"--reverse --walker=file,dir,follow,hidden --scheme=path" \
"--multi $FZF_CTRL_T_OPTS --print0")
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE
set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
and commandline -rt -- (string join -- ' ' $prefix(string escape --no-quoted -- $result))' '
set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
and commandline -rt -- (string join -- ' ' $prefix(string escape --no-quoted -- $result))' '
commandline -f repaint
end
function fzf-history-widget --inherit-variable _fzf_transform_action -d "Show command history"
set -l -- command_line (commandline)
set -l -- current_line (commandline -L)
set -l -- total_lines (count $command_line)
set -l -- fzf_query (string escape -- $command_line[$current_line])
# These options require newer fzf; omit them on older builds
set -l _fzf_preview_wrap_sign ''
set -l _fzf_toggle_raw ''
if test "$_fzf_transform_action" = bg-transform
set _fzf_preview_wrap_sign ' --preview-wrap-sign="↳ "'
set _fzf_toggle_raw ,alt-r:toggle-raw
commandline -f repaint
end
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults '' \
function fzf-history-widget --inherit-variable _fzf_transform_action -d "Show command history"
set -l -- command_line (commandline)
set -l -- current_line (commandline -L)
set -l -- total_lines (count $command_line)
set -l -- fzf_query (string escape -- $command_line[$current_line])
# These options require newer fzf; omit them on older builds
set -l _fzf_preview_wrap_sign ''
set -l _fzf_toggle_raw ''
if test "$_fzf_transform_action" = bg-transform
set _fzf_preview_wrap_sign ' --preview-wrap-sign="↳ "'
set _fzf_toggle_raw ,alt-r:toggle-raw
end
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults '' \
'--nth=2..,.. --scheme=history --multi --no-multi-line --no-wrap --wrap-sign="\t\t\t↳ "'$_fzf_preview_wrap_sign \
'--bind=\'shift-delete:execute-silent(for i in (string split0 -- <{+f}); eval builtin history delete --exact --case-sensitive -- (string escape -n -- $i | string replace -r "^\d*\\\\\\t" ""); end)+reload(eval $FZF_DEFAULT_COMMAND)\'' \
'--bind="alt-enter:become(string join0 -- (string collect -- {+2..} | fish_indent -i))"' \
"--bind=ctrl-r:toggle-sort$_fzf_toggle_raw --highlight-line $FZF_CTRL_R_OPTS" \
'--accept-nth=2.. --delimiter="\t" --tabstop=4 --read0 --print0 --with-shell='(status fish-path)\\ -c)
# Add dynamic preview options if preview command isn't already set by user
if string match -qvr -- '--preview[= ]' "$FZF_DEFAULT_OPTS"
# Convert the highlighted timestamp using the date command if available
set -l -- date_cmd '{1}'
if type -q date
if date -d @0 '+%s' 2>/dev/null | string match -q 0
# GNU date
set -- date_cmd '(date -d @{1} \\"+%F %a %T\\")'
else if date -r 0 '+%s' 2>/dev/null | string match -q 0
# BSD date
set -- date_cmd '(date -r {1} \\"+%F %a %T\\")'
# Add dynamic preview options if preview command isn't already set by user
if string match -qvr -- '--preview[= ]' "$FZF_DEFAULT_OPTS"
# Convert the highlighted timestamp using the date command if available
set -l -- date_cmd '{1}'
if type -q date
if date -d @0 '+%s' 2>/dev/null | string match -q 0
# GNU date
set -- date_cmd '(date -d @{1} \\"+%F %a %T\\")'
else if date -r 0 '+%s' 2>/dev/null | string match -q 0
# BSD date
set -- date_cmd '(date -r {1} \\"+%F %a %T\\")'
end
end
# Prepend the options to allow user customizations
if test "$_fzf_transform_action" = bg-transform
# Newer fzf: dynamic show/hide preview based on terminal width
set -p -- FZF_DEFAULT_OPTS \
'--bind="focus,resize:bg-transform:if test \\"$FZF_COLUMNS\\" -gt 100 -a \\\\( \\"$FZF_SELECT_COUNT\\" -gt 0 -o \\\\( -z \\"$FZF_WRAP\\" -a (string length -- {}) -gt (math $FZF_COLUMNS - 4) \\\\) -o (string collect -- {2..} | fish_indent | count) -gt 1 \\\\); echo show-preview; else echo hide-preview; end"' \
'--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \
'--preview-window="right,50%,wrap-word,follow,info,hidden"'
else
# Older fzf: static preview; focus/resize events not reliably supported
set -p -- FZF_DEFAULT_OPTS \
'--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \
'--preview-window="right,50%,wrap,follow,info"'
end
end
end
# Prepend the options to allow user customizations
if test "$_fzf_transform_action" = bg-transform
# Newer fzf: dynamic show/hide preview based on terminal width
set -p -- FZF_DEFAULT_OPTS \
'--bind="focus,resize:bg-transform:if test \\"$FZF_COLUMNS\\" -gt 100 -a \\\\( \\"$FZF_SELECT_COUNT\\" -gt 0 -o \\\\( -z \\"$FZF_WRAP\\" -a (string length -- {}) -gt (math $FZF_COLUMNS - 4) \\\\) -o (string collect -- {2..} | fish_indent | count) -gt 1 \\\\); echo show-preview; else echo hide-preview; end"' \
'--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \
'--preview-window="right,50%,wrap-word,follow,info,hidden"'
else
# Older fzf: static preview; focus/resize events not reliably supported
set -p -- FZF_DEFAULT_OPTS \
'--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \
'--preview-window="right,50%,wrap,follow,info"'
end
set -lx FZF_DEFAULT_OPTS_FILE
set -lx -- FZF_DEFAULT_COMMAND 'builtin history -z --show-time="%s%t"'
# Enable syntax highlighting colors on fish v4.3.3 and newer
if set -l -- v (string match -r -- '^(\d+)\.(\d+)(?:\.(\d+))?' $version)
and test "$v[2]" -gt 4 -o "$v[2]" -eq 4 -a \
\( "$v[3]" -gt 3 -o "$v[3]" -eq 3 -a \
\( -n "$v[4]" -a "$v[4]" -ge 3 \) \)
set -a -- FZF_DEFAULT_OPTS --ansi
set -a -- FZF_DEFAULT_COMMAND '--color=always'
end
# Merge history from other sessions before searching
test -z "$fish_private_mode"; and builtin history merge
if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
if test "$total_lines" -eq 1
commandline -- $result
else
set -l a (math $current_line - 1)
set -l b (math $current_line + 1)
commandline -- $command_line[1..$a] $result
commandline -a -- '' $command_line[$b..-1]
end
end
commandline -f repaint
end
set -lx FZF_DEFAULT_OPTS_FILE
function fzf-cd-widget -d "Change directory"
set -l commandline (__fzf_parse_commandline)
set -lx dir $commandline[1]
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
set -lx -- FZF_DEFAULT_COMMAND 'builtin history -z --show-time="%s%t"'
# Enable syntax highlighting colors on fish v4.3.3 and newer
if set -l -- v (string match -r -- '^(\d+)\.(\d+)(?:\.(\d+))?' $version)
and test "$v[2]" -gt 4 -o "$v[2]" -eq 4 -a \
\( "$v[3]" -gt 3 -o "$v[3]" -eq 3 -a \
\( -n "$v[4]" -a "$v[4]" -ge 3 \) \)
set -a -- FZF_DEFAULT_OPTS '--ansi'
set -a -- FZF_DEFAULT_COMMAND '--color=always'
end
# Merge history from other sessions before searching
test -z "$fish_private_mode"; and builtin history merge
if set -l result (eval $FZF_DEFAULT_COMMAND \| (__fzfcmd) --query=$fzf_query | string split0)
if test "$total_lines" -eq 1
commandline -- $result
else
set -l a (math $current_line - 1)
set -l b (math $current_line + 1)
commandline -- $command_line[1..$a] $result
commandline -a -- '' $command_line[$b..-1]
end
end
commandline -f repaint
end
function fzf-cd-widget -d "Change directory"
set -l commandline (__fzf_parse_commandline)
set -lx dir $commandline[1]
set -l fzf_query $commandline[2]
set -l prefix $commandline[3]
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
"--reverse --walker=dir,follow,hidden --scheme=path" \
"$FZF_ALT_C_OPTS --no-multi --print0")
set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0)
cd -- $result
commandline -rt -- $prefix
if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0)
cd -- $result
commandline -rt -- $prefix
end
commandline -f repaint
end
commandline -f repaint
end
if not set -q FZF_CTRL_R_COMMAND; or test -n "$FZF_CTRL_R_COMMAND"
if test -n "$FZF_CTRL_R_COMMAND"
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
if not set -q FZF_CTRL_R_COMMAND; or test -n "$FZF_CTRL_R_COMMAND"
if test -n "$FZF_CTRL_R_COMMAND"
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2
end
bind \cr fzf-history-widget
bind -M insert \cr fzf-history-widget
end
bind \cr fzf-history-widget
bind -M insert \cr fzf-history-widget
end
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind \ct fzf-file-widget
bind -M insert \ct fzf-file-widget
end
if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind \ct fzf-file-widget
bind -M insert \ct fzf-file-widget
end
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
bind \ec fzf-cd-widget
bind -M insert \ec fzf-cd-widget
end
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
bind \ec fzf-cd-widget
bind -M insert \ec fzf-cd-widget
end
end
@@ -321,233 +320,238 @@ fzf_key_bindings
function fzf_completion_setup
#----BEGIN INCLUDE common.fish
# NOTE: Do not directly edit this section, which is copied from "common.fish".
# To modify it, one can edit "common.fish" and run "./update.sh" to apply
# the changes. See code comments in "common.fish" for the implementation details.
#----BEGIN INCLUDE common.fish
# NOTE: Do not directly edit this section, which is copied from "common.fish".
# To modify it, one can edit "common.fish" and run "./update.sh" to apply
# the changes. See code comments in "common.fish" for the implementation details.
function __fzf_defaults
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
string join ' ' -- \
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
$FZF_DEFAULT_OPTS $argv[2..-1]
end
function __fzfcmd
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
if test -n "$FZF_TMUX_OPTS"
echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if test "$FZF_TMUX" = "1"
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else
echo "fzf"
end
end
function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'
set -l tokens
if test (string match -r -- '^\d+' $version) -ge 4
set -- tokens (commandline -xpc)
else
set -- tokens (commandline -opc)
function __fzf_defaults
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
string join ' ' -- \
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \
(test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
$FZF_DEFAULT_OPTS $argv[2..-1]
end
set -l -- var_count 0
for i in $tokens
if string match -qr -- '^[\w]+=' $i
set var_count (math $var_count + 1)
else
break
end
end
set -e -- tokens[0..$var_count]
while true
switch "$tokens[1]"
case builtin command
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
case env
set -e -- tokens[1]
test "$tokens[1]" = "--"; and set -e -- tokens[1]
while string match -qr -- '^[\w]+=' "$tokens[1]"
set -e -- tokens[1]
end
case '*'
break
end
end
string escape -n -- $tokens
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l fzf_query ''
set -l prefix ''
set -l dir '.'
set -l -- fish_major (string match -r -- '^\d+' $version)
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
end
if test "$fish_major" -ge 4
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
else
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
set -- prefix (string match -r -- $prefix_regex $cl_token)
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
end
if test -n "$fzf_query"
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
set -- fzf_query (path normalize -- $fzf_query)
set -- dir $fzf_query
while not path is -d $dir
set -- dir (path dirname $dir)
end
else
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
function __fzfcmd
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
if test -n "$FZF_TMUX_OPTS"
echo "fzf-tmux $FZF_TMUX_OPTS -- "
else if test "$FZF_TMUX" = 1
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
else
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
echo fzf
end
end
function __fzf_cmd_tokens -d 'Return command line tokens, skipping leading env assignments and command prefixes'
set -l tokens
if test (string match -r -- '^\d+' $version) -ge 4
set -- tokens (commandline -xpc)
else
set -- tokens (commandline -opc)
end
set -l -- var_count 0
for i in $tokens
if string match -qr -- '^[\w]+=' $i
set var_count (math $var_count + 1)
else
break
end
end
set -e -- tokens[0..$var_count]
while true
switch "$tokens[1]"
case builtin command
set -e -- tokens[1]
test "$tokens[1]" = --; and set -e -- tokens[1]
case env
set -e -- tokens[1]
test "$tokens[1]" = --; and set -e -- tokens[1]
while string match -qr -- '^[\w]+=' "$tokens[1]"
set -e -- tokens[1]
end
case '*'
break
end
end
string escape -n -- $tokens
end
function __fzf_parse_commandline -d 'Parse the current command line token and return split of existing filepath, fzf query, and optional -option= prefix'
set -l fzf_query ''
set -l prefix ''
set -l dir '.'
set -l -- fish_major (string match -r -- '^\d+' $version)
set -l -- fish_minor (string match -r -- '^\d+\.(\d+)' $version)[2]
set -l -- match_regex '(?<fzf_query>[\s\S]*?(?=\n?$)$)'
set -l -- prefix_regex '^-[^\s=]+=|^-(?!-)\S'
if test "$fish_major" -eq 3 -a "$fish_minor" -lt 3
or string match -q -v -- '* -- *' (string sub -l (commandline -Cp) -- (commandline -p))
set -- match_regex "(?<prefix>$prefix_regex)?$match_regex"
end
set -- dir $fzf_query
while not test -d "$dir"
set -- dir (dirname -z -- "$dir" | string split0)
end
end
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
if test "$fish_major" -ge 4
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N)
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
(string replace -- "$dir" '' $fzf_query | string collect -N)
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
else
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
set -- prefix (string match -r -- $prefix_regex $cl_token)
set -- fzf_query (string replace -- "$prefix" '' $cl_token | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)|\\\n\\\n$' '')
end
end
if test -n "$fzf_query"
if test \( "$fish_major" -ge 4 \) -o \( "$fish_major" -eq 3 -a "$fish_minor" -ge 5 \)
set -- fzf_query (path normalize -- $fzf_query)
set -- dir $fzf_query
while not path is -d $dir
set -- dir (path dirname $dir)
end
else
if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '(?<fzf_query>^[\s\S]*?(?=\n?$)$)' \
(string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
else
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '')
end
set -- dir $fzf_query
while not test -d "$dir"
set -- dir (dirname -z -- "$dir" | string split0)
end
end
if not string match -q -- '.' $dir; or string match -q -r -- '^\./|^\.$' $fzf_query
if test "$fish_major" -ge 4
string match -q -r -- '^'(string escape --style=regex -- $dir)'/?(?<fzf_query>[\s\S]*)' $fzf_query
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \
(string replace -- "$dir" '' $fzf_query | string collect -N)
else
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\n$' '')
end
end
end
string escape -n -- "$dir" "$fzf_query" "$prefix"
end
#----END INCLUDE
string escape -n -- "$dir" "$fzf_query" "$prefix"
end
#----END INCLUDE
# Use complete builtin for specific commands
function __fzf_complete_native
set -l -- token (commandline -t)
set -l -- completions (eval complete -C \"$argv[1]\")
test -n "$completions"; or begin
commandline -f repaint
return
end
# Use complete builtin for specific commands
function __fzf_complete_native
set -l -- token (commandline -t)
set -l -- completions (eval complete -C \"$argv[1]\")
test -n "$completions"; or begin commandline -f repaint; return; end
# Calculate tabstop based on longest completion item (sample first 500 for performance)
set -l -- tabstop 20
set -l -- sample_size (math "min(500, "(count $completions)")")
for c in $completions[1..$sample_size]
set -l -- len (string length -V -- (string split -- \t $c))
test -n "$len[2]" -a "$len[1]" -gt "$tabstop"
and set -- tabstop $len[1]
end
# limit to 120 to prevent long lines
set -- tabstop (math "min($tabstop + 4, 120)")
# Calculate tabstop based on longest completion item (sample first 500 for performance)
set -l -- tabstop 20
set -l -- sample_size (math "min(500, "(count $completions)")")
for c in $completions[1..$sample_size]
set -l -- len (string length -V -- (string split -- \t $c))
test -n "$len[2]" -a "$len[1]" -gt "$tabstop"
and set -- tabstop $len[1]
end
# limit to 120 to prevent long lines
set -- tabstop (math "min($tabstop + 4, 120)")
set -l result
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults \
set -l result
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults \
"--reverse --delimiter=\\t --nth=1 --tabstop=$tabstop --color=fg:dim,nth:regular" \
$FZF_COMPLETION_OPTS $argv[2..-1] --accept-nth=1 --read0 --print0)
set -- result (string join0 -- $completions | eval (__fzfcmd) | string split0)
and begin
set -l -- tail ' '
# Append / to bare ~username results (fish omits it unlike other shells)
set -- result (string replace -r -- '^(~\w+)\s?$' '$1/' $result)
# Don't add trailing space if single result is a directory
test (count $result) -eq 1
and string match -q -- '*/' "$result"; and set -- tail ''
set -- result (string join0 -- $completions | eval (__fzfcmd) | string split0)
and begin
set -l -- tail ' '
# Append / to bare ~username results (fish omits it unlike other shells)
set -- result (string replace -r -- '^(~\w+)\s?$' '$1/' $result)
# Don't add trailing space if single result is a directory
test (count $result) -eq 1
and string match -q -- '*/' "$result"; and set -- tail ''
set -l -- result (string escape -n -- $result)
set -l -- result (string escape -n -- $result)
string match -q -- '~*' "$token"
and set result (string replace -r -- '^\\\\~' '~' $result)
string match -q -- '~*' "$token"
and set result (string replace -r -- '^\\\\~' '~' $result)
string match -q -- '$*' "$token"
and set result (string replace -r -- '^\\\\\$' '\$' $result)
string match -q -- '$*' "$token"
and set result (string replace -r -- '^\\\\\$' '\$' $result)
commandline -rt -- (string join ' ' -- $result)$tail
commandline -rt -- (string join ' ' -- $result)$tail
end
commandline -f repaint
end
commandline -f repaint
end
function _fzf_complete
set -l -- args (string escape -- $argv | string join ' ' | string split -- ' -- ')
set -l -- post_func (status function)_(string split -- ' ' $args[2])[1]_post
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS $args[1])
set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND
set -l -- fzf_query (commandline -t | string escape)
set -l result
eval (__fzfcmd) --query=$fzf_query | while read -l r; set -a -- result $r; end
and if functions -q $post_func
commandline -rt -- (string collect -- $result | eval $post_func $args[2] | string join ' ')' '
else
commandline -rt -- (string join -- ' ' (string escape -- $result))' '
function _fzf_complete
set -l -- args (string escape -- $argv | string join ' ' | string split -- ' -- ')
set -l -- post_func (status function)_(string split -- ' ' $args[2])[1]_post
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS $args[1])
set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND
set -l -- fzf_query (commandline -t | string escape)
set -l result
eval (__fzfcmd) --query=$fzf_query | while read -l r
set -a -- result $r
end
and if functions -q $post_func
commandline -rt -- (string collect -- $result | eval $post_func $args[2] | string join ' ')' '
else
commandline -rt -- (string join -- ' ' (string escape -- $result))' '
end
commandline -f repaint
end
commandline -f repaint
end
# Kill completion (process selection)
function _fzf_complete_kill
set -l -- fzf_query (commandline -t | string escape)
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS \
# Kill completion (process selection)
function _fzf_complete_kill
set -l -- fzf_query (commandline -t | string escape)
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS \
--accept-nth=2 -m --header-lines=1 --no-preview --wrap)
set -lx FZF_DEFAULT_OPTS_FILE
if type -q ps
set -l -- ps_cmd 'begin command ps -eo user,pid,ppid,start,time,command 2>/dev/null;' \
'or command ps -eo user,pid,ppid,time,args 2>/dev/null;' \
'or command ps --everyone --full --windows 2>/dev/null; end'
set -l -- result (eval $ps_cmd \| (__fzfcmd) --query=$fzf_query)
and commandline -rt -- (string join ' ' -- $result)" "
else
__fzf_complete_native "kill " --multi --query=$fzf_query
set -lx FZF_DEFAULT_OPTS_FILE
if type -q ps
set -l -- ps_cmd 'begin command ps -eo user,pid,ppid,start,time,command 2>/dev/null;' \
'or command ps -eo user,pid,ppid,time,args 2>/dev/null;' \
'or command ps --everyone --full --windows 2>/dev/null; end'
set -l -- result (eval $ps_cmd \| (__fzfcmd) --query=$fzf_query)
and commandline -rt -- (string join ' ' -- $result)" "
else
__fzf_complete_native "kill " --multi --query=$fzf_query
end
commandline -f repaint
end
commandline -f repaint
end
# Main completion function
function fzf-completion
set -l -- tokens (__fzf_cmd_tokens)
set -l -- current_token (commandline -t)
set -l -- cmd_name $tokens[1]
# Main completion function
function fzf-completion
set -l -- tokens (__fzf_cmd_tokens)
set -l -- current_token (commandline -t)
set -l -- cmd_name $tokens[1]
# Route to appropriate completion function
if test -n "$tokens"; and functions -q _fzf_complete_$cmd_name
_fzf_complete_$cmd_name $tokens
else
set -l -- fzf_opt --query=$current_token --multi
__fzf_complete_native "$tokens $current_token" $fzf_opt
# Route to appropriate completion function
if test -n "$tokens"; and functions -q _fzf_complete_$cmd_name
_fzf_complete_$cmd_name $tokens
else
set -l -- fzf_opt --query=$current_token --multi
__fzf_complete_native "$tokens $current_token" $fzf_opt
end
end
end
# Bind Shift-Tab to fzf-completion (Tab retains native Fish behavior)
if test (string match -r -- '^\d+' $version) -ge 4
bind shift-tab fzf-completion
bind -M insert shift-tab fzf-completion
else
bind -k btab fzf-completion
bind -M insert -k btab fzf-completion
end
# Bind Shift-Tab to fzf-completion (Tab retains native Fish behavior)
if test (string match -r -- '^\d+' $version) -ge 4
bind shift-tab fzf-completion
bind -M insert shift-tab fzf-completion
else
bind -k btab fzf-completion
bind -M insert -k btab fzf-completion
end
end
# Run setup
+9 -6
View File
@@ -24,9 +24,12 @@ set -l mode bytes
set -l baseline main
for i in (seq (count $argv))
switch $argv[$i]
case --structural; set mode structural
case --startup; set mode startup
case --baseline; set baseline $argv[(math $i + 1)]
case --structural
set mode structural
case --startup
set mode startup
case --baseline
set baseline $argv[(math $i + 1)]
end
end
@@ -36,8 +39,8 @@ function __pb_cleanup --on-event fish_exit --inherit-variable tmp
end
# ── Build the two sandboxes ────────────────────────────────────────────
set -l A $tmp/base/fish # pristine baseline ref
set -l B $tmp/work/fish # current working tree
set -l A $tmp/base/fish # pristine baseline ref
set -l B $tmp/work/fish # current working tree
mkdir -p $A $B
git -C $repo archive $baseline | tar -x -C $A
or begin
@@ -71,7 +74,7 @@ set -l cases \
"p --help" "pkg --help" "play-media --help" "qc --help" \
"rand_string --help" "replay --help" "repo-open --help" "scrub --help" \
"smart_exit --help" "spark --help" "y --help" \
"mkcd" "auto-pull remove __no_such_repo__" \
mkcd "auto-pull remove __no_such_repo__" \
"agents-init --no-such-flag" "pkg __no_such_subcommand__"
function __pb_run --argument-names cfg stub cmd out
+17 -11
View File
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# CI test runner for this fish configuration.
# 1. Syntax-lints every tracked .fish file (fish -n).
# 1. Syntax-lints and indent-checks every tracked .fish file (fish -n, fish_indent --check).
# 2. Discovers tests/test-*.fish and reads the mode each suite declares
# in its own header (`# MODE: isolated` or `# MODE: in-session`).
# 3. Runs each isolated suite as its own --no-config fish process with
@@ -24,25 +24,32 @@ set -l script_dir (realpath (dirname (status filename)))
set -l repo_root (realpath $script_dir/..)
set -l overall_failed 0
# ---- Phase 1: syntax lint ------------------------------------------------
echo "== Syntax lint =="
# ---- Phase 1: syntax & indent lint ---------------------------------------
echo "== Syntax & indent lint =="
set -l lint_files $repo_root/config.fish
for dir in functions conf.d completions integrations
for dir in functions conf.d completions integrations tests
set -a lint_files (find $repo_root/$dir -name '*.fish' | sort)
end
set -l lint_failed 0
set -l syntax_failed 0
set -l indent_failed 0
for f in $lint_files
set -l out (fish -n $f 2>&1)
if test $status -ne 0
echo " FAIL "(string replace $repo_root/ '' $f)
echo " FAIL (syntax) "(string replace $repo_root/ '' $f)
printf '%s\n' $out
set lint_failed (math $lint_failed + 1)
set syntax_failed (math $syntax_failed + 1)
end
if not fish_indent --check $f >/dev/null 2>&1
echo " FAIL (indent) "(string replace $repo_root/ '' $f)
set indent_failed (math $indent_failed + 1)
end
end
set -l lint_total (count $lint_files)
echo (math $lint_total - $lint_failed)"/$lint_total files passed lint"
if test $lint_failed -ne 0
echo (math $lint_total - $syntax_failed)"/$lint_total files passed syntax check"
echo (math $lint_total - $indent_failed)"/$lint_total files passed indent check"
if test $syntax_failed -ne 0 -o $indent_failed -ne 0
set overall_failed 1
end
@@ -160,8 +167,7 @@ if test (count $session_suites) -gt 0
__fish_config_op_autoexec=off \
FISH_CONFIG_TEST_ROOT=$repo_root \
FISH_CONFIG_TEST_COUNTS=$counts \
fish -i -c "source $repo_root/tests/lib.fish; $srcs report" \
2>$err_file
fish -i -c "source $repo_root/tests/lib.fish; $srcs report" 2>$err_file
set -l session_status $status
set -l stderr_out (command cat $err_file)
+197 -109
View File
@@ -70,7 +70,7 @@ function failing_shim --argument-names name match
'#!/bin/sh' \
'for a in "$@"; do' \
" case \"\$a\" in *$match*) exit 1 ;; esac" \
'done' \
done \
"exec $real \"\$@\"" >$d/$name
chmod +x $d/$name
printf '%s\n' $d
@@ -166,7 +166,8 @@ git -C $r3 remote add upstream https://git.rootiest.dev/rootiest/fish-config.git
check "falls back to first remote" $want (_agents_repo_slug $r3)
# Slug sanitization test: special chars in fallback (local-) branch.
set -l dirt (mktemp -d); set -ga TMPDIRS $dirt
set -l dirt (mktemp -d)
set -ga TMPDIRS $dirt
mkdir -p "$dirt/projects/My Project!"
git -C "$dirt/projects/My Project!" init -q
git -C "$dirt/projects/My Project!" config user.email t@t
@@ -179,7 +180,8 @@ check "sanitizes special chars in local slug" false "$has_bad_chars"
echo ""
echo "== _agents_repo_ensure_symlink =="
set -l w (mktemp -d); set -ga TMPDIRS $w
set -l w (mktemp -d)
set -ga TMPDIRS $w
mkdir -p $w/target $w/live
# Fresh link onto an empty live parent.
@@ -200,7 +202,8 @@ check "refuses missing target" 1 (_agents_repo_ensure_symlink $w/live/f3 $w/nope
check "no dangling link left" false (test -L $w/live/f3; and echo true; or echo false)
# Non-destructive adoption: content on both sides, nothing overwritten.
set -l a (mktemp -d); set -ga TMPDIRS $a
set -l a (mktemp -d)
set -ga TMPDIRS $a
mkdir -p $a/vault $a/live/memory
echo vault-version >$a/vault/shared.md
echo vault-only >$a/vault/vaultonly.md
@@ -213,7 +216,8 @@ check "adoption keeps vault-only file" vault-only (cat $a/vault/vaultonly.md)
check "adoption replaced dir with link" true (test -L $a/live/memory; and echo true; or echo false)
# Repins a link that points somewhere else.
set -l p (mktemp -d); set -ga TMPDIRS $p
set -l p (mktemp -d)
set -ga TMPDIRS $p
mkdir -p $p/one $p/two
ln -s $p/one $p/link
_agents_repo_ensure_symlink $p/link $p/two >/dev/null
@@ -239,12 +243,14 @@ check "idempotent, silent" "" "$out"
# guarantee, and the old pull-first shape broke it: a failed fetch took
# the local commit down with it. The divergence is still built here so
# that guarantee is tested against the case that used to fail.
set -l origin (mktemp -d); set -ga TMPDIRS $origin
set -l origin (mktemp -d)
set -ga TMPDIRS $origin
git -C $origin init -q --bare
git -C $s remote add origin $origin
git -C $s push -q -u origin HEAD:refs/heads/main 2>/dev/null
set -l clone (mktemp -d); set -ga TMPDIRS $clone
set -l clone (mktemp -d)
set -ga TMPDIRS $clone
git clone -q $origin $clone
git -C $clone config user.email t@t
git -C $clone config user.name t
@@ -297,7 +303,8 @@ set -l h (new_repo)
echo first >$h/a.md
_agents_repo_sync $h "chore: init" >/dev/null
set -l hooks (mktemp -d); set -ga TMPDIRS $hooks
set -l hooks (mktemp -d)
set -ga TMPDIRS $hooks
printf '#!/bin/sh\nexit 1\n' >$hooks/pre-commit
chmod +x $hooks/pre-commit
git -C $h config core.hooksPath $hooks
@@ -312,8 +319,10 @@ check "commit hook rejection commits nothing" $before_hook_count (git -C $h rev-
echo ""
echo "== agents-vault (scaffold + link) =="
set -l vroot (mktemp -d); set -ga TMPDIRS $vroot
set -l croot (mktemp -d); set -ga TMPDIRS $croot
set -l vroot (mktemp -d)
set -ga TMPDIRS $vroot
set -l croot (mktemp -d)
set -ga TMPDIRS $croot
set -g __fish_agent_vault_dir $vroot/agent-vault
set -g __fish_agent_vault_claude_root $croot
@@ -359,8 +368,10 @@ set -e __fish_agent_vault_claude_root
echo ""
echo "== agents-vault (emergent restore) =="
set -l vroot2 (mktemp -d); set -ga TMPDIRS $vroot2
set -l croot2 (mktemp -d); set -ga TMPDIRS $croot2
set -l vroot2 (mktemp -d)
set -ga TMPDIRS $vroot2
set -l croot2 (mktemp -d)
set -ga TMPDIRS $croot2
set -g __fish_agent_vault_dir $vroot2/agent-vault
set -g __fish_agent_vault_claude_root $croot2
@@ -393,8 +404,10 @@ set -e __fish_agent_vault_claude_root
echo ""
echo "== agents-vault (link failure surfaces as exit 1) =="
set -l vroot3 (mktemp -d); set -ga TMPDIRS $vroot3
set -l croot3 (mktemp -d); set -ga TMPDIRS $croot3
set -l vroot3 (mktemp -d)
set -ga TMPDIRS $vroot3
set -l croot3 (mktemp -d)
set -ga TMPDIRS $croot3
set -g __fish_agent_vault_dir $vroot3/agent-vault
set -g __fish_agent_vault_claude_root $croot3
@@ -420,8 +433,10 @@ set -e __fish_agent_vault_claude_root
echo ""
echo "== agents-vault (slug migration) =="
set -l vroot2 (mktemp -d); set -ga TMPDIRS $vroot2
set -l croot2 (mktemp -d); set -ga TMPDIRS $croot2
set -l vroot2 (mktemp -d)
set -ga TMPDIRS $vroot2
set -l croot2 (mktemp -d)
set -ga TMPDIRS $croot2
set -g __fish_agent_vault_dir $vroot2/agent-vault
set -g __fish_agent_vault_claude_root $croot2
@@ -429,7 +444,7 @@ set -g __fish_agent_vault_claude_root $croot2
set -l mp (new_repo)
set -l mmangled (string replace -a '/' '-' -- $mp | string replace -a '.' '-')
mkdir -p $croot2/$mmangled/memory
echo "precious" >$croot2/$mmangled/memory/keep.md
echo precious >$croot2/$mmangled/memory/keep.md
pushd $mp >/dev/null
agents-vault --silent
@@ -495,8 +510,7 @@ set -l enew_slug git.rootiest.dev-rootiest-emptycase
# hand-built shape is covered separately just below.
mkdir -p $vroot2/agent-vault/projects/$enew_slug
printf 'remote: %s\npath: %s\nhost: %s\n' \
https://git.rootiest.dev/rootiest/emptycase.git /gone/elsewhere othermachine \
>$vroot2/agent-vault/projects/$enew_slug/origin
https://git.rootiest.dev/rootiest/emptycase.git /gone/elsewhere othermachine >$vroot2/agent-vault/projects/$enew_slug/origin
pushd $emp >/dev/null
set -l erc (agents-vault --silent 2>/dev/null; echo $status)
@@ -579,7 +593,8 @@ check "removal: old remote entry removed" false (test -d $vroot2/agent-vault/pro
# still find and adopt it. Before the fix, the fallback only lowercased
# the basename instead of sanitizing it like _agents_repo_slug does, so it
# could never match the real entry directory for a name like this.
set -l dirty_root (mktemp -d); set -ga TMPDIRS $dirty_root
set -l dirty_root (mktemp -d)
set -ga TMPDIRS $dirty_root
set -l dp "$dirty_root/My Project!"
mkdir -p "$dp"
git -C "$dp" init -q
@@ -590,7 +605,7 @@ git -C "$dp" config core.hooksPath /dev/null
set -l dmangled (string replace -a '/' '-' -- $dp | string replace -a '.' '-')
mkdir -p $croot2/$dmangled/memory
echo "dirty-precious" >$croot2/$dmangled/memory/keep.md
echo dirty-precious >$croot2/$dmangled/memory/keep.md
pushd $dp >/dev/null
agents-vault --silent
@@ -621,7 +636,8 @@ check "fallback old entry removed" false (test -d $vroot2/agent-vault/projects/$
# is not: a --silent run that did the right thing and returned 0 still
# printed "rm: cannot remove ...: Is a directory", which is the only
# thing the user sees and reads as a failure.
set -l real_root (mktemp -d); set -ga TMPDIRS $real_root
set -l real_root (mktemp -d)
set -ga TMPDIRS $real_root
set -l rp "$real_root/proj"
mkdir -p "$rp"
git -C "$rp" init -q
@@ -632,7 +648,7 @@ git -C "$rp" config core.hooksPath /dev/null
set -l rmangled (string replace -a '/' '-' -- $rp | string replace -a '.' '-')
mkdir -p $croot2/$rmangled/memory
echo "banked" >$croot2/$rmangled/memory/old.md
echo banked >$croot2/$rmangled/memory/old.md
pushd $rp >/dev/null
agents-vault --silent
@@ -643,10 +659,11 @@ popd >/dev/null
# never seen, then change the slug so the migration runs.
rm -f $croot2/$rmangled/memory
mkdir -p $croot2/$rmangled/memory
echo "written-live" >$croot2/$rmangled/memory/fresh.md
echo written-live >$croot2/$rmangled/memory/fresh.md
git -C $rp remote add origin https://git.rootiest.dev/rootiest/realdir.git
set -l rerr (mktemp); set -ga TMPDIRS $rerr
set -l rerr (mktemp)
set -ga TMPDIRS $rerr
pushd $rp >/dev/null
set -l real_rc (agents-vault --silent 2>$rerr; echo $status)
popd >/dev/null
@@ -673,8 +690,10 @@ echo ""
echo "== agents-vault (a real git clone) =="
# Machine A: populate a vault and let agents-vault commit it.
set -l cl_vroot (mktemp -d); set -ga TMPDIRS $cl_vroot
set -l cl_croot (mktemp -d); set -ga TMPDIRS $cl_croot
set -l cl_vroot (mktemp -d)
set -ga TMPDIRS $cl_vroot
set -l cl_croot (mktemp -d)
set -ga TMPDIRS $cl_croot
set -g __fish_agent_vault_dir $cl_vroot/agent-vault
set -g __fish_agent_vault_claude_root $cl_croot
@@ -698,7 +717,8 @@ agents-vault --silent
popd >/dev/null
# The clone, exactly as the README tells a user to make it.
set -l cl_new (mktemp -d); set -ga TMPDIRS $cl_new
set -l cl_new (mktemp -d)
set -ga TMPDIRS $cl_new
git clone -q $cl_vroot/agent-vault $cl_new/agent-vault
git -C $cl_new/agent-vault config user.email t@t
git -C $cl_new/agent-vault config user.name t
@@ -709,7 +729,8 @@ check "clone: the empty entry has no claude/ subtree" false (test -d $cl_new/age
check "clone: the empty entry is its origin file alone" true (test -f $cl_new/agent-vault/projects/$cl_empty_slug/origin; and echo true; or echo false)
# Machine B, case 1: an ordinary run against the clone restores memory.
set -l cl_croot2 (mktemp -d); set -ga TMPDIRS $cl_croot2
set -l cl_croot2 (mktemp -d)
set -ga TMPDIRS $cl_croot2
set -g __fish_agent_vault_dir $cl_new/agent-vault
set -g __fish_agent_vault_claude_root $cl_croot2
@@ -762,10 +783,14 @@ set -e __fish_agent_vault_claude_root
echo ""
echo "== agents-vault (global state) =="
set -l vroot5 (mktemp -d); set -ga TMPDIRS $vroot5
set -l croot5 (mktemp -d); set -ga TMPDIRS $croot5
set -l chome5 (mktemp -d); set -ga TMPDIRS $chome5
set -l agy5 (mktemp -d); set -ga TMPDIRS $agy5
set -l vroot5 (mktemp -d)
set -ga TMPDIRS $vroot5
set -l croot5 (mktemp -d)
set -ga TMPDIRS $croot5
set -l chome5 (mktemp -d)
set -ga TMPDIRS $chome5
set -l agy5 (mktemp -d)
set -ga TMPDIRS $agy5
set -g __fish_agent_vault_dir $vroot5/agent-vault
set -g __fish_agent_vault_claude_root $croot5
set -g __fish_agent_vault_claude_home $chome5
@@ -802,7 +827,8 @@ printf 'transcript\n' >$agy5/knowledge/session.jsonl
# the store boundary has to hold on its own. $outside5 stands in for that
# home: a directory the store has no business reaching into, planted with
# exactly the shapes that qualify.
set -l outside5 (mktemp -d); set -ga TMPDIRS $outside5
set -l outside5 (mktemp -d)
set -ga TMPDIRS $outside5
mkdir -p $outside5/nested
echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/leaked.json
echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/target.md
@@ -905,10 +931,14 @@ set -e __fish_agent_vault_claude_root
echo ""
echo "== agents-vault (global emergent restore) =="
set -l vroot6 (mktemp -d); set -ga TMPDIRS $vroot6
set -l croot6 (mktemp -d); set -ga TMPDIRS $croot6
set -l chome6 (mktemp -d); set -ga TMPDIRS $chome6
set -l agy6 (mktemp -d); set -ga TMPDIRS $agy6
set -l vroot6 (mktemp -d)
set -ga TMPDIRS $vroot6
set -l croot6 (mktemp -d)
set -ga TMPDIRS $croot6
set -l chome6 (mktemp -d)
set -ga TMPDIRS $chome6
set -l agy6 (mktemp -d)
set -ga TMPDIRS $agy6
set -g __fish_agent_vault_dir $vroot6/agent-vault
set -g __fish_agent_vault_claude_root $croot6
set -g __fish_agent_vault_claude_home $chome6
@@ -927,8 +957,10 @@ check "global restore: vault content readable through the link" restored-global
# A home with neither side populated must not have a memory/ invented for
# it: ~/.claude/memory does not exist by default.
set -l chome7 (mktemp -d); set -ga TMPDIRS $chome7
set -l vroot7 (mktemp -d); set -ga TMPDIRS $vroot7
set -l chome7 (mktemp -d)
set -ga TMPDIRS $chome7
set -l vroot7 (mktemp -d)
set -ga TMPDIRS $vroot7
set -g __fish_agent_vault_dir $vroot7/agent-vault
set -g __fish_agent_vault_claude_home $chome7
set -l gp7 (new_repo https://git.rootiest.dev/rootiest/globals-absent.git)
@@ -951,10 +983,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo ""
echo "== agents-vault (global link failure is non-fatal) =="
set -l vroot8 (mktemp -d); set -ga TMPDIRS $vroot8
set -l croot8 (mktemp -d); set -ga TMPDIRS $croot8
set -l chome8 (mktemp -d); set -ga TMPDIRS $chome8
set -l agy8 (mktemp -d); set -ga TMPDIRS $agy8
set -l vroot8 (mktemp -d)
set -ga TMPDIRS $vroot8
set -l croot8 (mktemp -d)
set -ga TMPDIRS $croot8
set -l chome8 (mktemp -d)
set -ga TMPDIRS $chome8
set -l agy8 (mktemp -d)
set -ga TMPDIRS $agy8
set -g __fish_agent_vault_dir $vroot8/agent-vault
set -g __fish_agent_vault_claude_root $croot8
set -g __fish_agent_vault_claude_home $chome8
@@ -972,7 +1008,8 @@ set -l fmangled (string replace -a '/' '-' -- $fp | string replace -a '.' '-')
mkdir -p $croot8/$fmangled/memory
echo project-memory >$croot8/$fmangled/memory/p.md
set -l ferr (mktemp); set -ga TMPDIRS $ferr
set -l ferr (mktemp)
set -ga TMPDIRS $ferr
pushd $fp >/dev/null
set -l frc (agents-vault --silent 2>$ferr; echo $status)
popd >/dev/null
@@ -991,7 +1028,8 @@ check "global link failure: per-project memory is committed" true (git -C $vroot
# A failed global link must not be recorded as done: the next run has to
# re-enter the block and warn again, not treat the vault as correct.
pushd $fp >/dev/null
set -l ferr2 (mktemp); set -ga TMPDIRS $ferr2
set -l ferr2 (mktemp)
set -ga TMPDIRS $ferr2
agents-vault --silent 2>$ferr2
popd >/dev/null
check "global link failure: retried on the next run" true (string match -q "*$chome8/memory*" -- (cat $ferr2); and echo true; or echo false)
@@ -1008,17 +1046,22 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo ""
echo "== agents-vault (status, remote, adopt) =="
set -l vroot9 (mktemp -d); set -ga TMPDIRS $vroot9
set -l croot9 (mktemp -d); set -ga TMPDIRS $croot9
set -l chome9 (mktemp -d); set -ga TMPDIRS $chome9
set -l agy9 (mktemp -d); set -ga TMPDIRS $agy9
set -l vroot9 (mktemp -d)
set -ga TMPDIRS $vroot9
set -l croot9 (mktemp -d)
set -ga TMPDIRS $croot9
set -l chome9 (mktemp -d)
set -ga TMPDIRS $chome9
set -l agy9 (mktemp -d)
set -ga TMPDIRS $agy9
set -g __fish_agent_vault_dir $vroot9/agent-vault
set -g __fish_agent_vault_claude_root $croot9
set -g __fish_agent_vault_claude_home $chome9
set -g __fish_agent_vault_agy_root $agy9
# A report asked for before the vault exists must say so, not scaffold one.
set -l s0out (mktemp); set -ga TMPDIRS $s0out
set -l s0out (mktemp)
set -ga TMPDIRS $s0out
set -l s0rc (agents-vault --status >$s0out; echo $status)
check "status without a vault exits 0" 0 "$s0rc"
check "status without a vault says so" true (string match -q '*no vault*' -- (cat $s0out); and echo true; or echo false)
@@ -1068,7 +1111,8 @@ check "status reports the remote" true (string match -q '*rootiest/agent-vault.g
# A *failed* remote update must return non-zero. Reporting success after a
# git command that did not run is the same silent-false-success shape that
# a hook-rejected commit produced earlier in this project.
set -l rerr (mktemp); set -ga TMPDIRS $rerr
set -l rerr (mktemp)
set -ga TMPDIRS $rerr
set -l rshim (failing_shim git set-url)
set -l rpath $PATH
set PATH $rshim $PATH
@@ -1106,10 +1150,11 @@ agents-vault --silent
popd >/dev/null
set -l projects_before (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
set -l bad_slugs ../escape has/slash . .. 'UPPER' 'sp ace' ''
set -l bad_slugs ../escape has/slash . .. UPPER 'sp ace' ''
pushd $bp >/dev/null
for bad in $bad_slugs
set -l berr (mktemp); set -ga TMPDIRS $berr
set -l berr (mktemp)
set -ga TMPDIRS $berr
set -l brc (agents-vault --adopt=$bad --silent 2>$berr; echo $status)
check "adopt refuses '$bad'" 1 "$brc"
check "adopt refuses '$bad' out loud" true (string match -q '*invalid*' -- (cat $berr); and echo true; or echo false)
@@ -1150,7 +1195,8 @@ set -l pre_head (git -C $vroot9/agent-vault rev-list --count HEAD)
set -l pre_porcelain (git -C $vroot9/agent-vault status --porcelain | string join ',')
set -l pre_link (path resolve $croot9/$tmang/memory)
set -l terr (mktemp); set -ga TMPDIRS $terr
set -l terr (mktemp)
set -ga TMPDIRS $terr
set -l tshim (failing_shim rm $tmang)
set -l tpath $PATH
set PATH $tshim $PATH
@@ -1203,8 +1249,7 @@ popd >/dev/null
# The target must be *tracked* for this to bite: stashing it takes files
# git knows about out from under the index, which is the whole hazard.
mkdir -p $vroot9/agent-vault/projects/stash-target/claude/memory
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere \
>$vroot9/agent-vault/projects/stash-target/origin
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere >$vroot9/agent-vault/projects/stash-target/origin
pushd $sa >/dev/null
agents-vault --silent
popd >/dev/null
@@ -1245,8 +1290,7 @@ agents-vault --silent
popd >/dev/null
mkdir -p $vroot9/agent-vault/projects/stashfail-target/claude/memory
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere-else \
>$vroot9/agent-vault/projects/stashfail-target/origin
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere-else >$vroot9/agent-vault/projects/stashfail-target/origin
pushd $sf >/dev/null
agents-vault --silent
popd >/dev/null
@@ -1257,7 +1301,8 @@ set -l sf_porcelain (git -C $vroot9/agent-vault status --porcelain | string join
set -l sf_link (path resolve $croot9/$sfmang/memory)
check "stash adopt rollback: vault clean before the adopt" "" "$sf_porcelain"
set -l sferr (mktemp); set -ga TMPDIRS $sferr
set -l sferr (mktemp)
set -ga TMPDIRS $sferr
set -l sfshim (failing_shim rm $sfmang)
set -l sfpath $PATH
set PATH $sfshim $PATH
@@ -1359,10 +1404,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo ""
echo "== agents-vault (restore) =="
set -l vroot10 (mktemp -d); set -ga TMPDIRS $vroot10
set -l croot10 (mktemp -d); set -ga TMPDIRS $croot10
set -l chome10 (mktemp -d); set -ga TMPDIRS $chome10
set -l agy10 (mktemp -d); set -ga TMPDIRS $agy10
set -l vroot10 (mktemp -d)
set -ga TMPDIRS $vroot10
set -l croot10 (mktemp -d)
set -ga TMPDIRS $croot10
set -l chome10 (mktemp -d)
set -ga TMPDIRS $chome10
set -l agy10 (mktemp -d)
set -ga TMPDIRS $agy10
set -g __fish_agent_vault_dir $vroot10/agent-vault
set -g __fish_agent_vault_claude_root $croot10
set -g __fish_agent_vault_claude_home $chome10
@@ -1389,9 +1438,9 @@ check "restore: names what it restored" true (string match -q '*restoreme*' -- "
# succeeds and says which entry needs --adopt.
mkdir -p $vroot10/agent-vault/projects/ghost-entry/claude/memory
echo x >$vroot10/agent-vault/projects/ghost-entry/claude/memory/x.md
printf 'remote: (none)\npath: %s\nhost: t\n' $vroot10/gone-forever \
>$vroot10/agent-vault/projects/ghost-entry/origin
set -l r2out (mktemp); set -ga TMPDIRS $r2out
printf 'remote: (none)\npath: %s\nhost: t\n' $vroot10/gone-forever >$vroot10/agent-vault/projects/ghost-entry/origin
set -l r2out (mktemp)
set -ga TMPDIRS $r2out
set -l r2rc (agents-vault --restore >$r2out; echo $status)
check "restore: exits 0 with an unplaceable entry" 0 "$r2rc"
check "restore: reports the unplaceable entry" true (string match -q '*ghost-entry*' -- (cat $r2out); and echo true; or echo false)
@@ -1406,8 +1455,7 @@ set -l dotp (new_repo https://git.rootiest.dev/rootiest/dotted.git)
set -l dotmang (string replace -a '/' '-' -- $dotp | string replace -a '.' '-')
mkdir -p $vroot10/agent-vault/projects/.dot-entry/claude/memory
echo dot-precious >$vroot10/agent-vault/projects/.dot-entry/claude/memory/keep.md
printf 'remote: (none)\npath: %s\nhost: t\n' $dotp \
>$vroot10/agent-vault/projects/.dot-entry/origin
printf 'remote: (none)\npath: %s\nhost: t\n' $dotp >$vroot10/agent-vault/projects/.dot-entry/origin
check "dot-led entry: a glob really does skip it" false (string match -q '*.dot-entry*' -- (echo $vroot10/agent-vault/projects/*); and echo true; or echo false)
set -l dotreport (agents-vault --status)
@@ -1425,7 +1473,8 @@ rm -f $croot10/$rmang/memory
set -l r3shim (failing_shim ln $rmang)
set -l r3path $PATH
set PATH $r3shim $PATH
set -l r3err (mktemp); set -ga TMPDIRS $r3err
set -l r3err (mktemp)
set -ga TMPDIRS $r3err
set -l r3rc (agents-vault --restore >/dev/null 2>$r3err; echo $status)
set PATH $r3path
check "restore: a failed relink returns non-zero" 1 "$r3rc"
@@ -1440,11 +1489,16 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo ""
echo "== agents-vault (push) =="
set -l vroot11 (mktemp -d); set -ga TMPDIRS $vroot11
set -l croot11 (mktemp -d); set -ga TMPDIRS $croot11
set -l chome11 (mktemp -d); set -ga TMPDIRS $chome11
set -l agy11 (mktemp -d); set -ga TMPDIRS $agy11
set -l bare (mktemp -d); set -ga TMPDIRS $bare
set -l vroot11 (mktemp -d)
set -ga TMPDIRS $vroot11
set -l croot11 (mktemp -d)
set -ga TMPDIRS $croot11
set -l chome11 (mktemp -d)
set -ga TMPDIRS $chome11
set -l agy11 (mktemp -d)
set -ga TMPDIRS $agy11
set -l bare (mktemp -d)
set -ga TMPDIRS $bare
git init -q --bare $bare
set -g __fish_agent_vault_dir $vroot11/agent-vault
set -g __fish_agent_vault_claude_root $croot11
@@ -1459,7 +1513,8 @@ echo pushed >$croot11/$pmang/memory/p.md
# --push with no remote must fail loudly. The commit still happened, so
# silently returning 0 would read as "backed up off this machine".
set -l perr (mktemp); set -ga TMPDIRS $perr
set -l perr (mktemp)
set -ga TMPDIRS $perr
pushd $pp >/dev/null
set -l prc0 (agents-vault --push --silent 2>$perr; echo $status)
popd >/dev/null
@@ -1498,7 +1553,8 @@ check "autopush pushes when enabled" pushed-auto (git -C $bare show $vbranch:pro
set -l deadremote $vroot11/not-a-repo.git
agents-vault --remote=$deadremote --silent
echo pushed-never >$croot11/$pmang/memory/p4.md
set -l fperr (mktemp); set -ga TMPDIRS $fperr
set -l fperr (mktemp)
set -ga TMPDIRS $fperr
pushd $pp >/dev/null
set -l fprc (agents-vault --push --silent 2>$fperr; echo $status)
popd >/dev/null
@@ -1510,7 +1566,8 @@ check "failing push still committed locally" true (git -C $vroot11/agent-vault l
# saying "Synced" must not come with a zero exit when the push failed.
echo pushed-never-2 >$croot11/$pmang/memory/p5.md
set -g __fish_agent_vault_autopush 1
set -l fp2err (mktemp); set -ga TMPDIRS $fp2err
set -l fp2err (mktemp)
set -ga TMPDIRS $fp2err
pushd $pp >/dev/null
set -l fp2rc (agents-vault --quiet 2>$fp2err >/dev/null; echo $status)
popd >/dev/null
@@ -1561,10 +1618,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo ""
echo "== agents-vault (the user's ssh command wins) =="
set -l vroot14 (mktemp -d); set -ga TMPDIRS $vroot14
set -l croot14 (mktemp -d); set -ga TMPDIRS $croot14
set -l chome14 (mktemp -d); set -ga TMPDIRS $chome14
set -l agy14 (mktemp -d); set -ga TMPDIRS $agy14
set -l vroot14 (mktemp -d)
set -ga TMPDIRS $vroot14
set -l croot14 (mktemp -d)
set -ga TMPDIRS $croot14
set -l chome14 (mktemp -d)
set -ga TMPDIRS $chome14
set -l agy14 (mktemp -d)
set -ga TMPDIRS $agy14
set -g __fish_agent_vault_dir $vroot14/agent-vault
set -g __fish_agent_vault_claude_root $croot14
set -g __fish_agent_vault_claude_home $chome14
@@ -1574,7 +1635,8 @@ set -g __fish_agent_vault_agy_root $agy14
# plain `ssh` (what the injected default resolves to) and one named
# explicitly by the user. Both refuse the connection, so nothing leaves
# the machine and no test waits on a network.
set -l sbin (mktemp -d); set -ga TMPDIRS $sbin
set -l sbin (mktemp -d)
set -ga TMPDIRS $sbin
set -l pathssh_log $sbin/path-ssh.log
set -l usessh $sbin/user-ssh
set -l usessh_log $sbin/user-ssh.log
@@ -1655,11 +1717,16 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo ""
echo "== agents-vault (autopush without timeout) =="
set -l vroot15 (mktemp -d); set -ga TMPDIRS $vroot15
set -l croot15 (mktemp -d); set -ga TMPDIRS $croot15
set -l chome15 (mktemp -d); set -ga TMPDIRS $chome15
set -l agy15 (mktemp -d); set -ga TMPDIRS $agy15
set -l bare15 (mktemp -d); set -ga TMPDIRS $bare15
set -l vroot15 (mktemp -d)
set -ga TMPDIRS $vroot15
set -l croot15 (mktemp -d)
set -ga TMPDIRS $croot15
set -l chome15 (mktemp -d)
set -ga TMPDIRS $chome15
set -l agy15 (mktemp -d)
set -ga TMPDIRS $agy15
set -l bare15 (mktemp -d)
set -ga TMPDIRS $bare15
git init -q --bare $bare15
set -g __fish_agent_vault_dir $vroot15/agent-vault
set -g __fish_agent_vault_claude_root $croot15
@@ -1678,7 +1745,8 @@ popd >/dev/null
# rev-parse would report the literal string HEAD with a fatal on stderr.
set -l vb15 (git -C $vroot15/agent-vault symbolic-ref --short HEAD)
set -l shimroot (mktemp -d); set -ga TMPDIRS $shimroot
set -l shimroot (mktemp -d)
set -ga TMPDIRS $shimroot
set -l nopath
set -l shimn 0
for d in $PATH
@@ -1696,7 +1764,8 @@ end
set -l realpath15 $PATH
echo t2 >$croot15/$tmang15/memory/t2.md
set -l terr (mktemp); set -ga TMPDIRS $terr
set -l terr (mktemp)
set -ga TMPDIRS $terr
set -g __fish_agent_vault_autopush 1
set -g PATH $nopath
check "the shimmed PATH really has no timeout" false (type -q timeout; and echo true; or echo false)
@@ -1737,10 +1806,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo ""
echo "== agents-vault (a failed vault commit is fatal) =="
set -l vroot13 (mktemp -d); set -ga TMPDIRS $vroot13
set -l croot13 (mktemp -d); set -ga TMPDIRS $croot13
set -l chome13 (mktemp -d); set -ga TMPDIRS $chome13
set -l agy13 (mktemp -d); set -ga TMPDIRS $agy13
set -l vroot13 (mktemp -d)
set -ga TMPDIRS $vroot13
set -l croot13 (mktemp -d)
set -ga TMPDIRS $croot13
set -l chome13 (mktemp -d)
set -ga TMPDIRS $chome13
set -l agy13 (mktemp -d)
set -ga TMPDIRS $agy13
set -g __fish_agent_vault_dir $vroot13/agent-vault
set -g __fish_agent_vault_claude_root $croot13
set -g __fish_agent_vault_claude_home $chome13
@@ -1763,7 +1836,8 @@ printf '#!/bin/sh\nexit 1\n' >$vroot13/agent-vault/.agents-tools/hooks/pre-commi
chmod +x $vroot13/agent-vault/.agents-tools/hooks/pre-commit
echo hook-more >$croot13/$hmang13/memory/keep2.md
set -l hhead13 (git -C $vroot13/agent-vault rev-list --count HEAD)
set -l herr13 (mktemp); set -ga TMPDIRS $herr13
set -l herr13 (mktemp)
set -ga TMPDIRS $herr13
pushd $hp13 >/dev/null
set -l hrc13 (agents-vault --silent 2>$herr13; echo $status)
popd >/dev/null
@@ -1782,16 +1856,21 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
# moves ahead (or goes out of reach) is not a backup; --push is where the
# divergence has to be reckoned with, and where the two ways it can fail
# have to be told apart.
set -l vroot14 (mktemp -d); set -ga TMPDIRS $vroot14
set -l croot14 (mktemp -d); set -ga TMPDIRS $croot14
set -l chome14 (mktemp -d); set -ga TMPDIRS $chome14
set -l agy14 (mktemp -d); set -ga TMPDIRS $agy14
set -l vroot14 (mktemp -d)
set -ga TMPDIRS $vroot14
set -l croot14 (mktemp -d)
set -ga TMPDIRS $croot14
set -l chome14 (mktemp -d)
set -ga TMPDIRS $chome14
set -l agy14 (mktemp -d)
set -ga TMPDIRS $agy14
set -g __fish_agent_vault_dir $vroot14/agent-vault
set -g __fish_agent_vault_claude_root $croot14
set -g __fish_agent_vault_claude_home $chome14
set -g __fish_agent_vault_agy_root $agy14
set -l bare14 (mktemp -d); set -ga TMPDIRS $bare14
set -l bare14 (mktemp -d)
set -ga TMPDIRS $bare14
git init -q --bare $bare14
set -l cp14 (new_repo https://git.rootiest.dev/rootiest/conflict.git)
@@ -1806,7 +1885,8 @@ agents-vault --remote=$bare14 --silent
git -C $vroot14/agent-vault push -q -u origin HEAD:refs/heads/main
# Another machine records a conflicting change to the same line...
set -l cclone14 (mktemp -d); set -ga TMPDIRS $cclone14
set -l cclone14 (mktemp -d)
set -ga TMPDIRS $cclone14
git clone -q $bare14 $cclone14
git -C $cclone14 config user.email t@t
git -C $cclone14 config user.name t
@@ -1819,7 +1899,8 @@ git -C $cclone14 push -q origin HEAD:main
# ... while this one writes conflicting memory of its own on the same
# line. The ordinary run has to commit it: it never fetches, so the
# divergence is invisible to it and irrelevant.
set -l cerr14 (mktemp); set -ga TMPDIRS $cerr14
set -l cerr14 (mktemp)
set -ga TMPDIRS $cerr14
echo ours >$croot14/$cmang14/memory/keep.md
set -l chead14 (git -C $vroot14/agent-vault rev-list --count HEAD)
pushd $cp14 >/dev/null
@@ -1885,10 +1966,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo ""
echo "== agents-vault (dangling global memory link) =="
set -l vroot12 (mktemp -d); set -ga TMPDIRS $vroot12
set -l croot12 (mktemp -d); set -ga TMPDIRS $croot12
set -l chome12 (mktemp -d); set -ga TMPDIRS $chome12
set -l agy12 (mktemp -d); set -ga TMPDIRS $agy12
set -l vroot12 (mktemp -d)
set -ga TMPDIRS $vroot12
set -l croot12 (mktemp -d)
set -ga TMPDIRS $croot12
set -l chome12 (mktemp -d)
set -ga TMPDIRS $chome12
set -l agy12 (mktemp -d)
set -ga TMPDIRS $agy12
set -g __fish_agent_vault_dir $vroot12/agent-vault
set -g __fish_agent_vault_claude_root $croot12
set -g __fish_agent_vault_claude_home $chome12
@@ -1900,7 +1985,8 @@ check "dangling: -L is the only signal" true (test -L $chome12/memory; and echo
check "dangling: the vault side is empty" false (test -e $vroot12/agent-vault/global/claude/memory; and echo true; or echo false)
set -l dp (new_repo https://git.rootiest.dev/rootiest/dangling.git)
set -l derr (mktemp); set -ga TMPDIRS $derr
set -l derr (mktemp)
set -ga TMPDIRS $derr
pushd $dp >/dev/null
set -l drc (agents-vault --silent 2>$derr; echo $status)
popd >/dev/null
@@ -1934,7 +2020,8 @@ check "agents-init: committed the AGENTS repo" true (test (git -C $ip/AGENTS rev
# Offline. The pull that used to run here blocked the launch until the
# remote timed out and then took the commit down with it, so an agent's
# edits went unrecorded on every launch away from the network.
set -l ibare (mktemp -d); set -ga TMPDIRS $ibare
set -l ibare (mktemp -d)
set -ga TMPDIRS $ibare
git init -q --bare $ibare
git -C $ip/AGENTS remote add origin $ibare
git -C $ip/AGENTS push -q -u origin HEAD 2>/dev/null
@@ -1957,7 +2044,8 @@ printf '#!/bin/sh\nexit 1\n' >$ip/AGENTS/.agents-tools/hooks/pre-commit
chmod +x $ip/AGENTS/.agents-tools/hooks/pre-commit
echo blocked >$ip/AGENTS/devlogs/blocked.md
set -l ibhead (git -C $ip/AGENTS rev-list --count HEAD)
set -l ierr (mktemp); set -ga TMPDIRS $ierr
set -l ierr (mktemp)
set -ga TMPDIRS $ierr
pushd $ip >/dev/null
set -l ibrc (agents-init --silent 2>$ierr; echo $status)
popd >/dev/null
+357
View File
@@ -0,0 +1,357 @@
#!/usr/bin/env fish
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# MODE: isolated
source (realpath (dirname (status filename)))/lib.fish
set -p fish_function_path $repo_root/functions
set -gx TERM xterm-256color
set -g __fish_config_dir $repo_root 2>/dev/null; or true
# =============================================================================
# 1. sponge_filter_secrets: Credential Leakage Prevention
# =============================================================================
section sponge_filter_secrets
# Export test variables matching sensitive heuristics
set -gx MY_AWS_SECRET_ACCESS_KEY wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
set -gx GITHUB_TOKEN ghp_0123456789abcdefghijklmnopqrstuv
set -gx KOPIA_PASSWORD MyLongPassword123
# Secret appears in command -> returns 0 (filtered from history)
sponge_filter_secrets "aws s3 cp file.txt s3://bucket/ --key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
check "aws secret in command filtered" 0 $status
sponge_filter_secrets "curl -H 'Authorization: token ghp_0123456789abcdefghijklmnopqrstuv' https://api.github.com"
check "github token in command filtered" 0 $status
sponge_filter_secrets "kopia repository connect --password MyLongPassword123"
check "kopia password in command filtered" 0 $status
# Clean command with no secret -> returns 1 (kept in history)
sponge_filter_secrets "git status -s"
check "clean command retained" 1 $status
sponge_filter_secrets "echo 'Hello World'"
check "ordinary echo retained" 1 $status
# Short secret (<= 8 chars) -> ignored, returns 1 (kept)
set -gx SHORT_API_KEY 12345678
set -gx SHORT_SECRET short
sponge_filter_secrets "curl -H 'X-Key: 12345678' https://example.com"
check "8-char secret ignored" 1 $status
sponge_filter_secrets "echo short"
check "short secret ignored" 1 $status
# Path-like secret (starts with / or ~) -> ignored, returns 1 (kept)
set -gx SECRET_FILE_PATH "/home/rootiest/.token"
set -gx HOME_SECRET_PATH "~/secrets/token.txt"
sponge_filter_secrets "cat /home/rootiest/.token"
check "path-like secret starting with / ignored" 1 $status
sponge_filter_secrets "source ~/secrets/token.txt"
check "path-like secret starting with ~ ignored" 1 $status
# Empty or unset variable -> handled without error, returns 1
set -gx DUMMY_API_KEY ""
sponge_filter_secrets "echo normal command"
check "empty sensitive var does not cause crash or false filter" 1 $status
# Clean up exported test variables
set -e MY_AWS_SECRET_ACCESS_KEY
set -e GITHUB_TOKEN
set -e KOPIA_PASSWORD
set -e SHORT_API_KEY
set -e SHORT_SECRET
set -e SECRET_FILE_PATH
set -e HOME_SECRET_PATH
set -e DUMMY_API_KEY
# =============================================================================
# 2. _fish_mkdir_p: Directory Creation with Feedback
# =============================================================================
section _fish_mkdir_p
set -l mkdir_sandbox (mktemp -d)
# Empty argument returns status 1
_fish_mkdir_p
check "empty argument returns 1" 1 $status
# Existing directory returns status 0
_fish_mkdir_p $mkdir_sandbox
check "existing directory returns 0" 0 $status
# --path: creates directory and prints single line
set -l out_path (_fish_mkdir_p --path $mkdir_sandbox/dirA/dirB)
check "--path returns 0" 0 $status
check "--path creates directory" true (test -d $mkdir_sandbox/dirA/dirB; and echo true; or echo false)
check "--path outputs 'Created directory:'" true (string match -q "*Created directory:*" -- "$out_path"; and echo true; or echo false)
# --silent: creates directory and suppresses stdout
set -l out_silent (_fish_mkdir_p --silent $mkdir_sandbox/dirC/dirD)
check "--silent returns 0" 0 $status
check "--silent creates directory" true (test -d $mkdir_sandbox/dirC/dirD; and echo true; or echo false)
check "--silent produces empty stdout" "" "$out_silent"
# -s short flag
set -l out_silent_short (_fish_mkdir_p -s $mkdir_sandbox/dirC/dirD/sub)
check "-s returns 0" 0 $status
check "-s creates directory" true (test -d $mkdir_sandbox/dirC/dirD/sub; and echo true; or echo false)
check "-s produces empty stdout" "" "$out_silent_short"
# --tree: creates directory and displays tree view with branch glyph
set -l out_tree (_fish_mkdir_p --tree $mkdir_sandbox/dirE/dirF)
check "--tree returns 0" 0 $status
check "--tree creates directory" true (test -d $mkdir_sandbox/dirE/dirF; and echo true; or echo false)
check "--tree outputs 'Created directories:'" true (string match -q "*Created directories:*" -- "$out_tree"; and echo true; or echo false)
check "--tree outputs branch glyph └──" true (string match -q "*└──*" -- "$out_tree"; and echo true; or echo false)
# Default mode without mode flag uses path mode
set -l out_default (_fish_mkdir_p $mkdir_sandbox/dirG/dirH)
check "default mode returns 0" 0 $status
check "default mode creates directory" true (test -d $mkdir_sandbox/dirG/dirH; and echo true; or echo false)
check "default mode outputs 'Created directory:'" true (string match -q "*Created directory:*" -- "$out_default"; and echo true; or echo false)
# Cleanup
rm -rf $mkdir_sandbox
# =============================================================================
# 3. _scrollback_prune_junk: History Log Pruning
# =============================================================================
section _scrollback_prune_junk
set -l prune_sandbox (mktemp -d)
# Create test files in prune sandbox
# Empty files (must be deleted)
touch $prune_sandbox/empty.log
touch $prune_sandbox/empty.txt
# 1-line noise files (must be deleted)
echo "lone command prompt" >$prune_sandbox/single_line.log
echo "[exited]" >$prune_sandbox/single_line.txt
printf "\n\n \n single line with surrounding whitespace \n\n" >$prune_sandbox/whitespace_single.log
# Kitty tab-rename UI noise captures (must be deleted)
printf "%s\n" "Prompt start" "Enter the new title for this tab below" Done >$prune_sandbox/scrollback_kitty.log
printf "%s\n" Header "Enter the new title for this tab below" Tail >$prune_sandbox/scrollback_rename.txt
# Valid multi-line log files (>1 meaningful lines) (MUST BE PRESERVED)
printf "%s\n" "Session started at 12:00" "Command: ls -la" "Output: total 0" >$prune_sandbox/valid_session.log
printf "%s\n" "Build step 1 passed" "Build step 2 passed" >$prune_sandbox/valid_session.txt
printf "%s\n" "scrollback line 1" "scrollback line 2" "scrollback line 3" >$prune_sandbox/scrollback_valid.log
# Run prune
_scrollback_prune_junk $prune_sandbox
# Assertions: empty files deleted
check "empty .log deleted" false (test -f $prune_sandbox/empty.log; and echo true; or echo false)
check "empty .txt deleted" false (test -f $prune_sandbox/empty.txt; and echo true; or echo false)
# Assertions: 1-line noise files deleted
check "single-line .log deleted" false (test -f $prune_sandbox/single_line.log; and echo true; or echo false)
check "single-line .txt deleted" false (test -f $prune_sandbox/single_line.txt; and echo true; or echo false)
check "whitespace single-line .log deleted" false (test -f $prune_sandbox/whitespace_single.log; and echo true; or echo false)
# Assertions: kitty tab rename prompt files deleted
check "kitty tab rename scrollback_*.log deleted" false (test -f $prune_sandbox/scrollback_kitty.log; and echo true; or echo false)
check "kitty tab rename scrollback_*.txt deleted" false (test -f $prune_sandbox/scrollback_rename.txt; and echo true; or echo false)
# Assertions: valid multi-line files preserved
check "valid multi-line .log preserved" true (test -f $prune_sandbox/valid_session.log; and echo true; or echo false)
check "valid multi-line .txt preserved" true (test -f $prune_sandbox/valid_session.txt; and echo true; or echo false)
check "valid multi-line scrollback_valid.log preserved" true (test -f $prune_sandbox/scrollback_valid.log; and echo true; or echo false)
# Assertions: non-existent directory handled safely
_scrollback_prune_junk $prune_sandbox/nonexistent
check "nonexistent directory returns 0" 0 $status
# Cleanup
rm -rf $prune_sandbox
# =============================================================================
# 4. sudo-toggle: Sudo NOPASSWD Security Toggle
# =============================================================================
section sudo-toggle
set -l sudo_sandbox (mktemp -d)
set -g _mock_sudoers_file "$sudo_sandbox/nofail-toggle"
# Mock sudo function to intercept stat, truncate, and tee for /etc/sudoers.d/nofail-toggle
function sudo
set -l cmd_args $argv
while test (count $cmd_args) -gt 0; and string match -qr '^-[a-zA-Z]' -- $cmd_args[1]
set -e cmd_args[1]
end
switch $cmd_args[1]
case stat
if test -f "$_mock_sudoers_file"
command stat -c %s "$_mock_sudoers_file"
else
return 1
end
case truncate
if test -f "$_mock_sudoers_file"
command truncate -s 0 "$_mock_sudoers_file"
end
case tee
command tee "$_mock_sudoers_file"
case '*'
return 1
end
end
# Case 1: When file is missing -> writes rule, outputs DISABLED (Bypass active)
rm -f "$_mock_sudoers_file"
set -l out_missing (sudo-toggle)
check "missing file: returns 0" 0 $status
check "missing file: displays DISABLED" true (string match -q "*🔓 Sudo security: DISABLED*" -- "$out_missing"; and echo true; or echo false)
check "missing file: creates sudoers file" true (test -f "$_mock_sudoers_file"; and echo true; or echo false)
check "missing file: writes content to file" true (test -s "$_mock_sudoers_file"; and echo true; or echo false)
# Case 2: When file has size > 0 (bypass active) -> truncates to 0, outputs ENABLED
set -l out_active (sudo-toggle)
check "active bypass: returns 0" 0 $status
check "active bypass: displays ENABLED" true (string match -q "*🔒 Sudo security: ENABLED*" -- "$out_active"; and echo true; or echo false)
check "active bypass: truncates file to 0 bytes" false (test -s "$_mock_sudoers_file"; and echo true; or echo false)
# Case 3: When file exists but has size 0 -> writes rule, outputs DISABLED
set -l out_empty (sudo-toggle)
check "empty file: returns 0" 0 $status
check "empty file: displays DISABLED" true (string match -q "*🔓 Sudo security: DISABLED*" -- "$out_empty"; and echo true; or echo false)
check "empty file: writes content to file" true (test -s "$_mock_sudoers_file"; and echo true; or echo false)
# Case 4: --help flag displays header documentation
set -l out_help (sudo-toggle --help)
check "sudo-toggle --help returns 0" 0 $status
check "sudo-toggle --help contains USAGE" true (string match -q "*USAGE*" -- "$out_help"; and echo true; or echo false)
# Cleanup
functions -e sudo
set -e _mock_sudoers_file
rm -rf $sudo_sandbox
# =============================================================================
# 5. spark: Sparkline Bar Chart Generation
# =============================================================================
section spark
# Number array 1 2 3 4 5 produces sparkline characters
set -l out_seq (spark 1 2 3 4 5)
check "spark 1 2 3 4 5 generates sparkline" "▁▃▄▆█" "$out_seq"
# Numbers via stdin
set -l out_stdin (printf "%s\n" 1 2 3 4 5 | spark)
check "spark via stdin generates sparkline" "▁▃▄▆█" "$out_stdin"
# Clamping with --min and --max
set -l out_clamped (spark --min=0 --max=10 0 5 10)
check "spark clamped with --min and --max" "▁▄█" "$out_clamped"
# Version flag
set -l out_ver (spark --version)
check "spark --version contains version 1.1.0" true (string match -q "*spark, version 1.1.0*" -- "$out_ver"; and echo true; or echo false)
set -l out_ver_s (spark -v)
check "spark -v contains version 1.1.0" true (string match -q "*spark, version 1.1.0*" -- "$out_ver_s"; and echo true; or echo false)
# Help flag
set -l out_spark_help (spark --help)
check "spark --help contains Usage:" true (string match -q "*Usage:*" -- "$out_spark_help"; and echo true; or echo false)
set -l out_spark_help_s (spark -h)
check "spark -h contains Usage:" true (string match -q "*Usage:*" -- "$out_spark_help_s"; and echo true; or echo false)
# =============================================================================
# 6. pkg: System Package Manager Abstraction
# =============================================================================
section pkg
# Test missing package manager detection
function _fish_deps_detect_pm
return 1
end
set -l err_no_pm (pkg somepkg 2>&1 >/dev/null)
check "missing package manager returns 1" 1 $status
check "missing package manager reports error on stderr" true (string match -q "*error: no supported package manager found*" -- "$err_no_pm"; and echo true; or echo false)
# Test pacman integration with mock detection and mock commands
function _fish_deps_detect_pm
echo pacman
end
set -g _pacman_log
function pacman
set -ga _pacman_log (string join -- " " $argv)
if test "$argv[1]" = -Qi
if test "$argv[2]" = installed-pkg
return 0
else
return 1
end
end
return 0
end
function sudo
$argv
end
# Auto mode with installed package -> invokes pacman -Qi and then removes (-Rns)
set -g _pacman_log
set -l out_installed (pkg installed-pkg)
check "pkg installed-pkg returns 0" 0 $status
check "pkg installed-pkg outputs Removing" true (string match -q "*Removing*installed-pkg*" -- "$out_installed"; and echo true; or echo false)
check "pkg installed-pkg runs pacman -Qi query" true (string match -q "*-Qi installed-pkg*" -- "$_pacman_log"; and echo true; or echo false)
check "pkg installed-pkg runs pacman -Rns removal" true (string match -q "*-Rns installed-pkg*" -- "$_pacman_log"; and echo true; or echo false)
# Auto mode with uninstalled package -> invokes pacman -Qi and then installs (-S)
set -g _pacman_log
set -l out_uninstalled (pkg uninstalled-pkg)
check "pkg uninstalled-pkg returns 0" 0 $status
check "pkg uninstalled-pkg outputs Installing" true (string match -q "*Installing*uninstalled-pkg*" -- "$out_uninstalled"; and echo true; or echo false)
check "pkg uninstalled-pkg runs pacman -Qi query" true (string match -q "*-Qi uninstalled-pkg*" -- "$_pacman_log"; and echo true; or echo false)
check "pkg uninstalled-pkg runs pacman -S installation" true (string match -q "*-S uninstalled-pkg*" -- "$_pacman_log"; and echo true; or echo false)
# Explicit install mode (-i) -> directly installs
set -g _pacman_log
set -l out_force_install (pkg -i new-pkg)
check "pkg -i returns 0" 0 $status
check "pkg -i outputs Installing" true (string match -q "*Installing*new-pkg*" -- "$out_force_install"; and echo true; or echo false)
check "pkg -i runs pacman -S" true (string match -q "*-S new-pkg*" -- "$_pacman_log"; and echo true; or echo false)
# Explicit uninstall mode (-u) -> directly removes
set -g _pacman_log
set -l out_force_uninstall (pkg -u old-pkg)
check "pkg -u returns 0" 0 $status
check "pkg -u outputs Removing" true (string match -q "*Removing*old-pkg*" -- "$out_force_uninstall"; and echo true; or echo false)
check "pkg -u runs pacman -Rns" true (string match -q "*-Rns old-pkg*" -- "$_pacman_log"; and echo true; or echo false)
# Help flag
set -l out_pkg_help (pkg --help)
check "pkg --help returns 0" 0 $status
check "pkg --help outputs Usage" true (string match -q "*Usage:*pkg*" -- "$out_pkg_help"; and echo true; or echo false)
# No arguments -> prints usage and returns 0
set -l out_pkg_empty (pkg)
check "pkg with no arguments returns 0" 0 $status
check "pkg with no arguments outputs Usage" true (string match -q "*Usage:*pkg*" -- "$out_pkg_empty"; and echo true; or echo false)
# Unknown flag -> returns 1 and prints error to stderr
set -l err_pkg_flag (pkg --unrecognized-option 2>&1 >/dev/null)
check "pkg unknown flag returns 1" 1 $status
check "pkg unknown flag outputs error on stderr" true (string match -q "*error:*unknown flag*--unrecognized-option*" -- "$err_pkg_flag"; and echo true; or echo false)
# Cleanup
functions -e _fish_deps_detect_pm pacman sudo
set -e _pacman_log
# =============================================================================
# Report
# =============================================================================
report
+4 -4
View File
@@ -245,10 +245,10 @@ section "op_enabled: always/* and AND, via a synthetic registry"
# this process reads them, so overriding them is free.
set -g __fish_config_op_registry_keys "syn_on:" "syn_off:" "syn_and:" "syn_bare:" "syn_multi:"
set -g __fish_config_op_registry_values \
"always/on" \
"always/off" \
always/on \
always/off \
"aliases/filesystem integrations/notifications" \
"aliases" \
aliases \
"always/off always/on"
set -e __fish_config_op_aliases __fish_config_op_integrations __fish_config_opinionated
@@ -312,7 +312,7 @@ section "op_enabled: C5 through the guard"
# The path production code actually takes, as opposed to calling the cascade
# directly. Same rule: unset means off and the master cannot enable it.
set -g __fish_config_op_registry_keys "syn_log:"
set -g __fish_config_op_registry_values "logging/terminal-capture"
set -g __fish_config_op_registry_values logging/terminal-capture
set -e __fish_config_op_logging __fish_config_op_logging_terminal_capture
__fish_config_op_enabled syn_log
+3 -3
View File
@@ -45,7 +45,7 @@ function test_help_renderer
'function fixturefn' \
' __fish_help_header (status current-function) $argv; and return 0' \
' echo RAN-BODY' \
'end' >$tmp/fixturefn.fish
end >$tmp/fixturefn.fish
set -l out (_help_probe $tmp 'fixturefn --help')
set -l code $status
@@ -106,14 +106,14 @@ function test_help_renderer_degrades_safely
'function headerless' \
' __fish_help_header (status current-function) $argv; and return 0' \
" touch $tmp/BODY-RAN" \
'end' >$tmp/headerless.fish
end >$tmp/headerless.fish
# A comment run carrying no `# LABEL` line at all.
printf '%s\n' \
'# just an ordinary comment, no labels here' \
'function malformed' \
' __fish_help_header (status current-function) $argv; and return 0' \
" touch $tmp/BODY-RAN" \
'end' >$tmp/malformed.fish
end >$tmp/malformed.fish
set -l failed 0
for fn in headerless malformed
+683
View File
@@ -0,0 +1,683 @@
#!/usr/bin/env fish
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Hermetic unit tests for network-dependent fish functions with full mock coverage
# and failure edge-case testing:
# - gi (gitignore generation, API fetch, deduplication, error handling)
# - gip, gip4, gip6 (public IP resolution, timeouts, IPv4/IPv6 fallback, failure notices)
# - qr (terminal QR code generator, qrencode local bypass, curl fallback & network drop)
# - bd-pull (Gitea issues sync, missing tokens, API failures, malformed JSON, linking)
# - _auto_pull_sync (background safe fast-forward, dirty trees, network drop on fetch)
# - gitup (remote fetch & status, network drop handling)
# - git-clean (remote fetch/prune, orphaned branch detection & deletion)
# - config-update (upstream fetch, up-to-date check, dry-run, force stash & restore)
# - repo-open (origin URL parsing, offline local tracking, ls-remote fallback & drop)
# - fzf-update (git pull/clone network failure handling)
#
# MODE: isolated
source (realpath (dirname (status filename)))/lib.fish
set -p fish_function_path $repo_root/functions
set -gx TERM xterm-256color
set -g TMPDIRS
# Ensure git operations inside tests are hermetic and do not prompt
set -gx GIT_AUTHOR_NAME t
set -gx GIT_AUTHOR_EMAIL t@t
set -gx GIT_COMMITTER_NAME t
set -gx GIT_COMMITTER_EMAIL t@t
set -gx GIT_CONFIG_COUNT 2
set -gx GIT_CONFIG_KEY_0 commit.gpgsign
set -gx GIT_CONFIG_VALUE_0 false
set -gx GIT_CONFIG_KEY_1 init.defaultBranch
set -gx GIT_CONFIG_VALUE_1 main
set -gx GIT_TERMINAL_PROMPT 0
# Helper to create temporary git repos
function new_repo
set -l d (mktemp -d)
set -ga TMPDIRS $d
git -C $d init -q
git -C $d config user.email t@t
git -C $d config user.name t
git -C $d config commit.gpgsign false
git -C $d config core.hooksPath /dev/null
printf '%s\n' $d
end
# Build a hermetic mock bin directory
set -g MOCK_DIR (mktemp -d)
set -ga TMPDIRS $MOCK_DIR
set -l real_git (command -s git)
# 1. Mock curl
printf '%s\n' \
'#!/bin/sh' \
'if [ -n "$MOCK_CURL_LOG" ]; then' \
' printf "%s\n" "$*" >> "$MOCK_CURL_LOG"' \
fi \
'if [ -n "$MOCK_CURL_HANDLER" ] && [ -x "$MOCK_CURL_HANDLER" ]; then' \
' exec "$MOCK_CURL_HANDLER" "$@"' \
fi \
'if [ -n "$MOCK_CURL_DELAY" ]; then' \
' sleep "$MOCK_CURL_DELAY"' \
fi \
'if [ -n "$MOCK_CURL_STDERR" ]; then' \
' printf "%s\n" "$MOCK_CURL_STDERR" >&2' \
fi \
'if [ -f "$MOCK_CURL_BODY_FILE" ]; then' \
' cat "$MOCK_CURL_BODY_FILE"' \
'elif [ -n "$MOCK_CURL_BODY" ]; then' \
' printf "%s\n" "$MOCK_CURL_BODY"' \
fi \
'exit "${MOCK_CURL_STATUS:-0}"' >$MOCK_DIR/curl
chmod +x $MOCK_DIR/curl
# 2. Mock git shim
printf '%s\n' \
'#!/bin/sh' \
'if [ -n "$MOCK_GIT_LOG" ]; then' \
' printf "%s\n" "$*" >> "$MOCK_GIT_LOG"' \
fi \
'if [ -n "$MOCK_GIT_HANDLER" ] && [ -x "$MOCK_GIT_HANDLER" ]; then' \
' exec "$MOCK_GIT_HANDLER" "$@"' \
fi \
'for a in "$@"; do' \
' if [ "$a" = "fetch" ] && [ -n "$MOCK_GIT_FAIL_FETCH" ]; then' \
' echo "fatal: unable to access: Could not resolve host" >&2' \
' exit "${MOCK_GIT_FETCH_STATUS:-128}"' \
' fi' \
' if [ "$a" = "pull" ] && [ -n "$MOCK_GIT_FAIL_PULL" ]; then' \
' echo "fatal: unable to access: Could not resolve host" >&2' \
' exit "${MOCK_GIT_PULL_STATUS:-128}"' \
' fi' \
' if [ "$a" = "clone" ] && [ -n "$MOCK_GIT_FAIL_CLONE" ]; then' \
' echo "fatal: unable to access: Could not resolve host" >&2' \
' exit "${MOCK_GIT_CLONE_STATUS:-128}"' \
' fi' \
' if [ "$a" = "ls-remote" ] && [ -n "$MOCK_GIT_FAIL_LS_REMOTE" ]; then' \
' echo "fatal: unable to access: Could not resolve host" >&2' \
' exit "${MOCK_GIT_LS_REMOTE_STATUS:-128}"' \
' fi' \
done \
"exec $real_git \"\$@\"" >$MOCK_DIR/git
chmod +x $MOCK_DIR/git
# 3. Mock bd CLI
printf '%s\n' \
'#!/bin/sh' \
'if [ "$1" = "create" ]; then' \
' echo "Created issue bd-99"' \
' echo "{\"id\":\"bd-99\"}" >> .beads/issues.jsonl' \
' exit 0' \
'elif [ "$1" = "sync" ]; then' \
' exit 0' \
fi \
'exit 0' >$MOCK_DIR/bd
chmod +x $MOCK_DIR/bd
# 4. Mock qrencode
printf '%s\n' \
'#!/bin/sh' \
'if [ -n "$MOCK_QRENCODE_LOG" ]; then' \
' printf "%s\n" "$*" >> "$MOCK_QRENCODE_LOG"' \
fi \
'echo "LOCAL_QRENCODE: $*"' \
'exit 0' >$MOCK_DIR/qrencode
chmod +x $MOCK_DIR/qrencode
# Prepend MOCK_DIR to PATH so mocks take precedence
set -gx PATH $MOCK_DIR $PATH
function reset_mocks
set -e MOCK_CURL_STATUS
set -e MOCK_CURL_BODY
set -e MOCK_CURL_BODY_FILE
set -e MOCK_CURL_STDERR
set -e MOCK_CURL_DELAY
set -e MOCK_CURL_LOG
set -e MOCK_CURL_HANDLER
set -e MOCK_GIT_FAIL_FETCH
set -e MOCK_GIT_FETCH_STATUS
set -e MOCK_GIT_FAIL_PULL
set -e MOCK_GIT_PULL_STATUS
set -e MOCK_GIT_FAIL_CLONE
set -e MOCK_GIT_CLONE_STATUS
set -e MOCK_GIT_FAIL_LS_REMOTE
set -e MOCK_GIT_LS_REMOTE_STATUS
set -e MOCK_GIT_LOG
set -e MOCK_GIT_HANDLER
set -e MOCK_QRENCODE_LOG
end
function cleanup
for d in $TMPDIRS
test -n "$d"; and command rm -rf $d
end
end
# ─────────────────────────────────────────────────────────────────────────────
# 1. gi (gitignore generator)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: gi"
reset_mocks
set -gx MOCK_CURL_BODY "c,cpp,python,rust"
set -l list_out (gi -l)
check "gi -l: returns API target list" "c,cpp,python,rust" "$list_out"
# Complete network drop on list
set -gx MOCK_CURL_STATUS 7
set -gx MOCK_CURL_BODY ""
gi -l >/dev/null 2>&1
check "gi -l: returns 0 per contract" 0 $status
# stdout mode with valid content
reset_mocks
set -gx MOCK_CURL_BODY "# Python gitignore\n*.pyc\n__pycache__/"
set -l stdout_out (gi -s python)
check "gi -s: prints fetched patterns to stdout" "# Python gitignore\n*.pyc\n__pycache__/" "$stdout_out"
# stdout mode with network failure / 404 (curl exit 22)
reset_mocks
set -gx MOCK_CURL_STATUS 22
set -gx MOCK_CURL_BODY ""
gi -s invalid_target >/dev/null 2>&1
check "gi -s: API failure returns 1" 1 $status
# Append mode outside git repository
reset_mocks
set -l non_git_dir (mktemp -d)
set -ga TMPDIRS $non_git_dir
begin
set -l prev_pwd $PWD
builtin cd $non_git_dir
gi python >/dev/null 2>&1
set -l rc $status
builtin cd $prev_pwd
check "gi <target>: outside git repo returns 1" 1 $rc
end
# Append mode inside git repo + deduplication
reset_mocks
set -l repo (new_repo)
set -gx MOCK_CURL_BODY "# Python gitignore\n*.pyc"
begin
set -l prev_pwd $PWD
builtin cd $repo
gi python >/dev/null 2>&1
check "gi python: first run returns 0" 0 $status
check "gi python: creates .gitignore" true (test -f "$repo/.gitignore"; and echo true; or echo false)
set -l first_lines (count (command cat "$repo/.gitignore"))
# Second run with identical pattern triggers deduplication
gi python >/dev/null 2>&1
check "gi python: second run returns 0" 0 $status
set -l second_lines (count (command cat "$repo/.gitignore"))
check "gi python: deduplication prevents duplicate append" $first_lines $second_lines
# Network failure during append skips modifying file
set -gx MOCK_CURL_STATUS 7
gi ruby >/dev/null 2>&1
set -l third_lines (count (command cat "$repo/.gitignore"))
check "gi: network drop during append preserves file" $second_lines $third_lines
builtin cd $prev_pwd
end
# ─────────────────────────────────────────────────────────────────────────────
# 2. gip, gip4, gip6 (IP resolution)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: gip, gip4, gip6"
# Create a specialized curl handler for gip IPv4/IPv6 dispatch
set -l gip_handler $MOCK_DIR/gip_curl.sh
printf '%s\n' \
'#!/bin/sh' \
'for a in "$@"; do' \
' if [ "$a" = "-4" ]; then' \
' if [ -n "$MOCK_FAIL_IPV4" ]; then exit "${MOCK_IPV4_STATUS:-28}"; fi' \
' echo "198.51.100.1"' \
' exit 0' \
' fi' \
' if [ "$a" = "-6" ]; then' \
' if [ -n "$MOCK_FAIL_IPV6" ]; then exit "${MOCK_IPV6_STATUS:-28}"; fi' \
' echo "2001:db8::1"' \
' exit 0' \
' fi' \
done \
'exit 0' >$gip_handler
chmod +x $gip_handler
reset_mocks
set -gx MOCK_CURL_HANDLER $gip_handler
# gip: both IPv4 and IPv6 succeed
set -l out_both (gip)
check "gip: both succeed outputs IPv4" true (string match -q '*IPv4: 198.51.100.1*' -- $out_both; and echo true; or echo false)
check "gip: both succeed outputs IPv6" true (string match -q '*IPv6: 2001:db8::1*' -- $out_both; and echo true; or echo false)
# gip: IPv4 timeout / failure, IPv6 success
set -gx MOCK_FAIL_IPV4 1
set -l out_v4_fail (gip)
check "gip: IPv4 failure reports 'Not detected'" true (string match -q '*IPv4: Not detected*' -- $out_v4_fail; and echo true; or echo false)
check "gip: IPv4 failure still reports IPv6" true (string match -q '*IPv6: 2001:db8::1*' -- $out_v4_fail; and echo true; or echo false)
# gip: IPv4 success, IPv6 failure
set -e MOCK_FAIL_IPV4
set -gx MOCK_FAIL_IPV6 1
set -l out_v6_fail (gip)
check "gip: IPv6 failure reports IPv4" true (string match -q '*IPv4: 198.51.100.1*' -- $out_v6_fail; and echo true; or echo false)
check "gip: IPv6 failure reports 'Not detected'" true (string match -q '*IPv6: Not detected*' -- $out_v6_fail; and echo true; or echo false)
# gip: complete network drop (both fail)
set -gx MOCK_FAIL_IPV4 1
set -gx MOCK_FAIL_IPV6 1
set -l out_all_fail (gip)
check "gip: complete drop reports IPv4 Not detected" true (string match -q '*IPv4: Not detected*' -- $out_all_fail; and echo true; or echo false)
check "gip: complete drop reports IPv6 Not detected" true (string match -q '*IPv6: Not detected*' -- $out_all_fail; and echo true; or echo false)
# gip4: success
set -e MOCK_FAIL_IPV4
set -l out_gip4 (gip4)
check "gip4: success prints IP" "198.51.100.1" "$out_gip4"
check "gip4: success exits 0" 0 $status
# gip4: failure
set -gx MOCK_FAIL_IPV4 1
set -gx MOCK_IPV4_STATUS 7
gip4 >/dev/null 2>&1
check "gip4: failure returns curl status" 7 $status
# gip6: success
set -e MOCK_FAIL_IPV6
set -l out_gip6 (gip6)
check "gip6: success prints IPv6" "2001:db8::1" "$out_gip6"
check "gip6: success exits 0" 0 $status
# gip6: failure / unsupported network
set -gx MOCK_FAIL_IPV6 1
set -l out_gip6_err (gip6 2>&1)
check "gip6: failure exits 1" 1 $status
check "gip6: failure prints notice" true (string match -q '*IPv6 is currently unavailable*' -- $out_gip6_err; and echo true; or echo false)
# ─────────────────────────────────────────────────────────────────────────────
# 3. qr (QR code generator)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: qr"
reset_mocks
# Case A: Local qrencode tool is present
set -gx MOCK_QRENCODE_LOG (mktemp)
set -ga TMPDIRS $MOCK_QRENCODE_LOG
set -l qr_local (qr "hello-world")
check "qr: local qrencode is preferred offline" true (string match -q '*LOCAL_QRENCODE*' -- $qr_local; and echo true; or echo false)
check "qr: curl is never called when qrencode exists" 0 (test -f $MOCK_DIR/curl_log; and count (cat $MOCK_DIR/curl_log); or echo 0)
# Case B: Local qrencode is missing, falling back to curl
function type
if test "$argv[1]" = -q -a "$argv[2]" = qrencode
return 1
end
builtin type $argv
end
set -gx MOCK_CURL_BODY UTF8_QR_BODY
set -l qr_curl (qr "hello-curl")
check "qr: fallback to curl when qrencode is missing" UTF8_QR_BODY "$qr_curl"
# Network drop during curl fallback
set -gx MOCK_CURL_STATUS 7
set -gx MOCK_CURL_BODY ""
qr fail >/dev/null 2>&1
check "qr: curl network drop returns non-zero" 7 $status
# Argument-based curl fallback
set -gx MOCK_CURL_STATUS 0
set -gx MOCK_CURL_BODY TEXT_QR
set -l qr_arg (qr "arg-text")
check "qr: argument works via curl fallback" TEXT_QR "$qr_arg"
functions -e type
# ─────────────────────────────────────────────────────────────────────────────
# 4. bd-pull (Gitea issues sync)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: bd-pull"
reset_mocks
# Missing required arguments & env vars
bd-pull >/dev/null 2>&1
check "bd-pull: missing repo argument exits 1" 1 $status
begin
set -e GITEA_TOKEN
set -e GITEA_URL
bd-pull rootiest/test >/dev/null 2>&1
check "bd-pull: missing GITEA_TOKEN exits 1" 1 $status
set -gx GITEA_TOKEN test_token
bd-pull rootiest/test >/dev/null 2>&1
check "bd-pull: missing GITEA_URL exits 1" 1 $status
end
# Complete network drop / empty response
set -gx GITEA_TOKEN dummy_token
set -gx GITEA_URL "https://git.test"
set -gx MOCK_CURL_STATUS 7
set -gx MOCK_CURL_BODY ""
set -l out_drop (bd-pull rootiest/test)
check "bd-pull: network drop reports no unlinked issues" true (string match -q '*No unlinked issues found*' -- $out_drop; and echo true; or echo false)
# Empty JSON issue list
set -gx MOCK_CURL_STATUS 0
set -gx MOCK_CURL_BODY "[]"
set -l out_empty (bd-pull rootiest/test)
check "bd-pull: empty issue list handled cleanly" true (string match -q '*No unlinked issues found*' -- $out_empty; and echo true; or echo false)
# Issues already linked with [ID] prefix
set -gx MOCK_CURL_BODY '[{"number": 1, "title": "[bd-1] Already linked issue"}]'
set -l out_already (bd-pull rootiest/test)
check "bd-pull: already-linked issues skipped" true (string match -q '*No unlinked issues found*' -- $out_already; and echo true; or echo false)
# Unlinked issue found: creates bead, patches title, commits & pushes
set -l bd_remote (new_repo)
git -C $bd_remote config --bool core.bare true
set -l bd_repo (new_repo)
begin
set -l prev_pwd $PWD
builtin cd $bd_repo
git remote add origin $bd_remote
mkdir -p .beads
touch .beads/issues.jsonl
git add .beads/issues.jsonl
git commit -q -m "init beads"
git push -q -u origin main
set -gx MOCK_CURL_BODY '[{"number": 2, "title": "Web original issue without ID"}]'
set -l out_unlinked (bd-pull rootiest/test 2>&1)
check "bd-pull: unlinked issue is linked" true (string match -q '*Linked 1 issues*' -- $out_unlinked; and echo true; or echo false)
# Verify git commit was made
set -l last_msg (git log -1 --pretty=%s)
check "bd-pull: commits synced IDs to git" "chore: sync local IDs for web issues" "$last_msg"
builtin cd $prev_pwd
end
# ─────────────────────────────────────────────────────────────────────────────
# 5. _auto_pull_sync (background fast-forward)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: _auto_pull_sync"
reset_mocks
# Non-git directory
_auto_pull_sync /tmp >/dev/null 2>&1
check "_auto_pull_sync: non-git directory returns 1" 1 $status
# Git repo without upstream branch
set -l sync_repo (new_repo)
echo test >$sync_repo/file.txt
git -C $sync_repo add file.txt
git -C $sync_repo commit -q -m initial
_auto_pull_sync $sync_repo >/dev/null 2>&1
check "_auto_pull_sync: missing upstream returns 1" 1 $status
# Setup upstream branch for sync_repo
set -l sync_upstream (new_repo)
git -C $sync_upstream config --bool core.bare true
git -C $sync_repo remote add origin $sync_upstream
git -C $sync_repo push -q -u origin main >/dev/null 2>&1
# Dirty working tree (unstaged modifications)
echo dirty >>$sync_repo/file.txt
_auto_pull_sync $sync_repo >/dev/null 2>&1
check "_auto_pull_sync: dirty worktree returns 1" 1 $status
git -C $sync_repo checkout -q -- file.txt
# Dirty index (staged modifications)
echo staged >>$sync_repo/staged.txt
git -C $sync_repo add staged.txt
_auto_pull_sync $sync_repo >/dev/null 2>&1
check "_auto_pull_sync: dirty index returns 1" 1 $status
git -C $sync_repo reset -q HEAD staged.txt
command rm -f $sync_repo/staged.txt
# Clean repo, network failure on git fetch
set -gx MOCK_GIT_FAIL_FETCH 1
_auto_pull_sync $sync_repo >/dev/null 2>&1
check "_auto_pull_sync: fetch failure returns 1" 1 $status
# Clean repo, successful fast-forward
reset_mocks
# Add commit to upstream
set -l peer_clone (new_repo)
git -C $peer_clone clone -q $sync_upstream $peer_clone/work
echo "new commit" >$peer_clone/work/new.txt
git -C $peer_clone/work add new.txt
git -C $peer_clone/work commit -q -m "upstream work"
git -C $peer_clone/work push -q origin main
_auto_pull_sync $sync_repo >/dev/null 2>&1
check "_auto_pull_sync: clean fast-forward returns 0" 0 $status
check "_auto_pull_sync: changes merged into working tree" true (test -f $sync_repo/new.txt; and echo true; or echo false)
# ─────────────────────────────────────────────────────────────────────────────
# 6. gitup (fetch and status)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: gitup"
reset_mocks
begin
set -l prev_pwd $PWD
builtin cd /tmp
gitup >/dev/null 2>&1
check "gitup: outside git repo exits 1" 1 $status
set -l r (new_repo)
builtin cd $r
echo a >a && git add a && git commit -q -m a
# Network failure on git fetch
set -gx MOCK_GIT_FAIL_FETCH 1
gitup >/dev/null 2>&1
check "gitup: network drop during fetch exits non-zero" true (test $status -ne 0; and echo true; or echo false)
# Successful fetch
reset_mocks
set -l up_out (gitup 2>&1)
check "gitup: success exits 0" 0 $status
check "gitup: displays git status output" true (string match -q '*On branch main*' -- $up_out; and echo true; or echo false)
builtin cd $prev_pwd
end
# ─────────────────────────────────────────────────────────────────────────────
# 7. git-clean (fetch --prune and delete orphaned branches)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: git-clean"
reset_mocks
git-clean --help >/dev/null 2>&1
check "git-clean: --help exits 0" 0 $status
begin
set -l prev_pwd $PWD
set -l r (new_repo)
builtin cd $r
echo a >a && git add a && git commit -q -m a
# Network drop during git fetch --prune
set -gx MOCK_GIT_FAIL_FETCH 1
# git-clean continues to local cleanup even if fetch fails
set -l clean_out (git-clean 2>&1)
check "git-clean: network drop handled, reports tidy" true (string match -q '*No orphaned branches found*' -- $clean_out; and echo true; or echo false)
# Orphaned branch detection and cleanup
reset_mocks
# Simulate an orphaned branch with [gone] tracking
git branch orphaned-feat
# Create git-clean handler that simulates git branch -vv showing ': gone]'
set -l clean_git_handler $MOCK_DIR/git_clean_shim.sh
printf '%s\n' \
'#!/bin/sh' \
'for a in "$@"; do' \
' if [ "$a" = "-vv" ]; then' \
' echo " main 1234567 [origin/main] initial"' \
' echo " orphaned-feat abcdef0 [origin/orphaned-feat: gone] feature"' \
' exit 0' \
' fi' \
done \
"exec $real_git \"\$@\"" >$clean_git_handler
chmod +x $clean_git_handler
set -gx MOCK_GIT_HANDLER $clean_git_handler
set -l clean_del_out (git-clean 2>&1)
check "git-clean: detects and deletes orphaned branch" true (string match -q '*Deleting orphaned local branches*' -- $clean_del_out; and echo true; or echo false)
check "git-clean: orphaned branch was deleted" false (git rev-parse --verify --quiet orphaned-feat >/dev/null 2>&1; and echo true; or echo false)
builtin cd $prev_pwd
end
# ─────────────────────────────────────────────────────────────────────────────
# 8. config-update (configuration repository sync)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: config-update"
reset_mocks
config-update --help >/dev/null 2>&1
check "config-update: --help exits 0" 0 $status
# Non-git CONFIG_DIR (using mock handler)
set -l cfg_update_handler $MOCK_DIR/cfg_update_shim.sh
printf '%s\n' \
'#!/bin/sh' \
'for a in "$@"; do' \
' if [ "$a" = "fetch" ] && [ -n "$MOCK_CFG_FAIL_FETCH" ]; then' \
' exit 1' \
' fi' \
done \
"exec $real_git \"\$@\"" >$cfg_update_handler
chmod +x $cfg_update_handler
set -gx MOCK_GIT_HANDLER $cfg_update_handler
# Network failure on upstream fetch
set -l fake_home (mktemp -d)
set -ga TMPDIRS $fake_home
set -l fake_fish_cfg "$fake_home/.config/fish"
mkdir -p (dirname $fake_fish_cfg)
set -l cfg_repo (new_repo)
command cp -r $cfg_repo $fake_fish_cfg
# Test config-update against fake_fish_cfg by setting HOME
begin
set -lx HOME $fake_home
set -gx MOCK_CFG_FAIL_FETCH 1
config-update >/dev/null 2>&1
check "config-update: network fetch failure returns 1" 1 $status
end
# ─────────────────────────────────────────────────────────────────────────────
# 9. repo-open (origin URL normalization and browser deep-linking)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: repo-open"
reset_mocks
repo-open --help >/dev/null 2>&1
check "repo-open: --help exits 0" 0 $status
# Outside git repo
begin
set -l prev_pwd $PWD
builtin cd /tmp
repo-open -p >/dev/null 2>&1
check "repo-open: outside git repo returns 1" 1 $status
builtin cd $prev_pwd
end
# In git repo without origin
set -l r_no_origin (new_repo)
begin
set -l prev_pwd $PWD
builtin cd $r_no_origin
repo-open -p >/dev/null 2>&1
check "repo-open: without origin remote returns 1" 1 $status
builtin cd $prev_pwd
end
# In git repo with GitHub origin remote
set -l r_gh (new_repo)
git -C $r_gh remote add origin "https://github.com/rootiest/fish-config.git"
echo init >$r_gh/file && git -C $r_gh add file && git -C $r_gh commit -q -m init
begin
set -l prev_pwd $PWD
builtin cd $r_gh
# Remote ls-remote network failure falls back to default branch
set -gx MOCK_GIT_FAIL_LS_REMOTE 1
set -l url_fallback (repo-open -p)
check "repo-open: ls-remote network drop falls back to default branch" "https://github.com/rootiest/fish-config" "$url_fallback"
# With ls-remote success for current branch
reset_mocks
set -l gh_ls_handler $MOCK_DIR/gh_ls_shim.sh
printf '%s\n' \
'#!/bin/sh' \
'for a in "$@"; do' \
' if [ "$a" = "ls-remote" ]; then' \
' echo "abcdef01 refs/heads/main"' \
' exit 0' \
' fi' \
done \
"exec $real_git \"\$@\"" >$gh_ls_handler
chmod +x $gh_ls_handler
set -gx MOCK_GIT_HANDLER $gh_ls_handler
# Test gitlab provider normalization
git remote set-url origin "git@gitlab.com:rootiest/fish-config.git"
git checkout -q -b feat-test
set -l url_gl (repo-open -p)
check "repo-open: gitlab ssh url normalized with branch tree" "https://gitlab.com/rootiest/fish-config/-/tree/feat-test" "$url_gl"
# Test gitea provider normalization
git remote set-url origin "https://git.rootiest.dev/rootiest/fish-config.git"
git config browse.provider gitea
set -l url_gitea (repo-open -p)
check "repo-open: gitea url normalized with src/branch" "https://git.rootiest.dev/rootiest/fish-config/src/branch/feat-test" "$url_gitea"
builtin cd $prev_pwd
end
# ─────────────────────────────────────────────────────────────────────────────
# 10. fzf-update (fzf install / git pull)
# ─────────────────────────────────────────────────────────────────────────────
section "network isolation: fzf-update"
reset_mocks
# When ~/.fzf exists, git pull fails due to network drop
set -l fake_home_fzf (mktemp -d)
set -ga TMPDIRS $fake_home_fzf
mkdir -p $fake_home_fzf/.fzf
begin
set -lx HOME $fake_home_fzf
set -gx MOCK_GIT_FAIL_PULL 1
fzf-update >/dev/null 2>&1
check "fzf-update: git pull network drop returns non-zero" true (test $status -ne 0; and echo true; or echo false)
end
# When ~/.fzf does not exist, git clone fails due to network drop
set -l fake_home_no_fzf (mktemp -d)
set -ga TMPDIRS $fake_home_no_fzf
begin
set -lx HOME $fake_home_no_fzf
set -gx MOCK_GIT_FAIL_CLONE 1
fzf-update >/dev/null 2>&1
check "fzf-update: git clone network drop returns non-zero" true (test $status -ne 0; and echo true; or echo false)
end
# ─────────────────────────────────────────────────────────────────────────────
# Teardown and Final Report
# ─────────────────────────────────────────────────────────────────────────────
cleanup
report
+31
View File
@@ -0,0 +1,31 @@
#!/usr/bin/env fish
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# Isolated test suite for Python helper scripts network isolation and edge case mocking.
# Exercises scripts/sync-labels.py under offline conditions with full mock coverage.
#
# Runs isolated (no # MODE: marker, defaulting to isolated).
source (realpath (dirname (status filename)))/lib.fish
section "python network isolation: sync-labels.py"
set -l py_script $repo_root/tests/test_sync_labels.py
check "tests/test_sync_labels.py exists" true (test -f $py_script; and echo true; or echo false)
set -l py_lines (python3 $py_script -v 2>&1)
set -l py_status $status
for line in $py_lines
if string match -qr '^\s*(test_\w+)\s+\([^)]+\)\s+\.\.\.\s+(\w+)' -- $line
set -l match (string match -r '^\s*(test_\w+)\s+\([^)]+\)\s+\.\.\.\s+(\w+)' -- $line)
set -l tname $match[2]
set -l tres $match[3]
check "$tname" ok (string lower $tres)
end
end
check "test_sync_labels.py suite exited 0" 0 $py_status
report
+397
View File
@@ -0,0 +1,397 @@
#!/usr/bin/env fish
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
#
# MODE: isolated
source (realpath (dirname (status filename)))/lib.fish
set -p fish_function_path $repo_root/functions
set -gx TERM xterm-256color
set -g __fish_config_dir $repo_root 2>/dev/null; or true
# Ensure data/words is reachable via __fish_config_dir in isolated runs
if not test -d "$__fish_config_dir/data/words"
mkdir -p "$__fish_config_dir/data"
ln -sf "$repo_root/data/words" "$__fish_config_dir/data/words"
end
# -----------------------------------------------------------------------------
# rand_string: Components
# -----------------------------------------------------------------------------
section "rand_string: components"
check "literal component" FOO (rand_string literal=FOO)
check "string component" BAR (rand_string string=BAR)
set -l out_digits4 (rand_string digits=4)
check "digits=4 returns 4 digits" true (string match -qr '^\d{4}$' -- "$out_digits4"; and echo true; or echo false)
set -l out_digits1 (rand_string digits=1)
check "digits=1 returns 1 digit" true (string match -qr '^\d$' -- "$out_digits1"; and echo true; or echo false)
set -l out_digits_inv (rand_string digits=abc)
check "digits=non-numeric defaults to 1 digit" true (string match -qr '^\d$' -- "$out_digits_inv"; and echo true; or echo false)
set -l out_color (rand_string color)
check "category color generates non-empty word" true (test -n "$out_color"; and echo true; or echo false)
set -l out_animal (rand_string animal)
check "category animal generates non-empty word" true (test -n "$out_animal"; and echo true; or echo false)
set -l out_noun (rand_string noun)
check "category noun generates non-empty word" true (test -n "$out_noun"; and echo true; or echo false)
set -l out_verb (rand_string verb)
check "category verb generates non-empty word" true (test -n "$out_verb"; and echo true; or echo false)
set -l out_adj (rand_string adjective)
check "category adjective generates non-empty word" true (test -n "$out_adj"; and echo true; or echo false)
set -l out_name (rand_string name)
check "category name generates non-empty word" true (test -n "$out_name"; and echo true; or echo false)
# -----------------------------------------------------------------------------
# rand_string: Separators
# -----------------------------------------------------------------------------
section "rand_string: separators"
check "default separator is dash" A-B (rand_string literal=A literal=B)
check "--separator=underscore" A_B (rand_string --separator=underscore literal=A literal=B)
check "--separator=dot" "A.B" (rand_string --separator=dot literal=A literal=B)
check "--separator=none" AB (rand_string --separator=none literal=A literal=B)
check "--separator=empty" AB (rand_string --separator=empty literal=A literal=B)
check "-s dot" "A.B" (rand_string -s dot literal=A literal=B)
check "-s underscore" A_B (rand_string -s underscore literal=A literal=B)
check "-s none" AB (rand_string -s none literal=A literal=B)
check "custom literal separator ::" "A::B" (rand_string --separator=:: literal=A literal=B)
set -l out_multi (rand_string literal=test --separator=underscore literal=part digits=2)
check "multi-component with separators" true (string match -qr '^test_part_\d{2}$' -- "$out_multi"; and echo true; or echo false)
# -----------------------------------------------------------------------------
# rand_string: Casing
# -----------------------------------------------------------------------------
section "rand_string: casing"
check "--case=upper produces uppercase" true (string match -qr '^[A-Z]+$' -- (rand_string --case=upper color); and echo true; or echo false)
check "--case=lower produces lowercase" true (string match -qr '^[a-z]+$' -- (rand_string --case=lower color); and echo true; or echo false)
check "--case=title produces titlecase" true (string match -qr '^[A-Z][a-z]+$' -- (rand_string --case=title color); and echo true; or echo false)
check "-c upper flag" true (string match -qr '^[A-Z]+$' -- (rand_string -c upper color); and echo true; or echo false)
check "-c lower flag" true (string match -qr '^[a-z]+$' -- (rand_string -c lower color); and echo true; or echo false)
check "-c title flag" true (string match -qr '^[A-Z][a-z]+$' -- (rand_string -c title color); and echo true; or echo false)
check "literal preserves casing with --case=upper" foo (rand_string --case=upper literal=foo)
check "literal preserves casing with --case=lower" FOO (rand_string --case=lower literal=FOO)
check "literal preserves casing with --case=title" foo (rand_string --case=title literal=foo)
# -----------------------------------------------------------------------------
# rand_string: Errors and Help
# -----------------------------------------------------------------------------
section "rand_string: errors and help"
rand_string nonexistent_cat_xyz >/dev/null 2>&1
check "missing category returns 1" 1 $status
set -l err_msg (rand_string nonexistent_cat_xyz 2>&1 >/dev/null)
check "missing category reports error on stderr" true (string match -qr 'unknown category' -- "$err_msg"; and echo true; or echo false)
rand_string -h >/dev/null 2>&1
check "-h returns 0" 0 $status
set -l help_h (rand_string -h)
check "-h displays usage" true (string match -qr 'Usage:' -- "$help_h"; and echo true; or echo false)
rand_string --help >/dev/null 2>&1
check "--help returns 0" 0 $status
set -l help_long (rand_string --help)
check "--help displays usage" true (string match -qr 'Usage:' -- "$help_long"; and echo true; or echo false)
# -----------------------------------------------------------------------------
# Mock commandline infrastructure
# -----------------------------------------------------------------------------
set -g mock_cmd_buffer ""
set -g mock_cmd_cursor 0
set -g mock_cmd_token ""
set -g mock_cmd_inserted ""
set -g mock_cmd_in_search 0
function commandline
if test (count $argv) -eq 0
echo -n "$mock_cmd_buffer"
return 0
end
switch $argv[1]
case -C
if test (count $argv) -ge 2
set -g mock_cmd_cursor $argv[2]
else
echo -n "$mock_cmd_cursor"
end
return 0
case -r
if test (count $argv) -ge 2
set -g mock_cmd_buffer "$argv[2]"
else
set -g mock_cmd_buffer ""
end
return 0
case --
if test (count $argv) -ge 2
set -g mock_cmd_buffer "$argv[2]"
else
set -g mock_cmd_buffer ""
end
return 0
case -i --insert
if test (count $argv) -ge 2
set -g mock_cmd_inserted "$argv[2]"
set -g mock_cmd_buffer "$mock_cmd_buffer$argv[2]"
end
return 0
case --search-field
if contains -- --insert $argv
set -l idx (contains -i -- --insert $argv)
set -l val $argv[(math $idx + 1)]
set -g mock_cmd_inserted "$val"
set -g mock_cmd_buffer "$mock_cmd_buffer$val"
return 0
else
test "$mock_cmd_in_search" -eq 1
return $status
end
case -t --current-token
if test (count $argv) -ge 2
set -l old_tok "$mock_cmd_token"
set -g mock_cmd_token "$argv[2]"
if test -n "$old_tok"
set -g mock_cmd_buffer (string replace -- "$old_tok" "$argv[2]" "$mock_cmd_buffer")
else
set -g mock_cmd_buffer "$mock_cmd_buffer$argv[2]"
end
else
echo -n "$mock_cmd_token"
end
return 0
end
end
set -g _test_hist_files
function set_test_history --argument-names cmd
set -l session "test_hist_"(random)
set -l hist_dir ""
if test -n "$XDG_DATA_HOME"
set hist_dir "$XDG_DATA_HOME/fish"
else
set hist_dir "$HOME/.local/share/fish"
end
mkdir -p "$hist_dir"
set -l hist_file "$hist_dir/"$session"_history"
echo "- cmd: $cmd" >"$hist_file"
echo " when: "(date +%s) >>"$hist_file"
set -g fish_history "$session"
set -ga _test_hist_files "$hist_file"
end
function cleanup_test_history
if set -q _test_hist_files
for f in $_test_hist_files
if test -f "$f"
rm -f "$f"
end
end
set -e _test_hist_files
end
end
# -----------------------------------------------------------------------------
# _replace_command_token
# -----------------------------------------------------------------------------
section _replace_command_token
set mock_cmd_buffer "git status"
set mock_cmd_cursor -1
_replace_command_token
check "git status: buffer replaces command token" " status" "$mock_cmd_buffer"
check "git status: cursor placed at 0" 0 "$mock_cmd_cursor"
set mock_cmd_buffer "sudo rm -rf /tmp"
set mock_cmd_cursor -1
_replace_command_token
check "sudo rm -rf /tmp: buffer preserves sudo and replaces command" "sudo -rf /tmp" "$mock_cmd_buffer"
check "sudo rm -rf /tmp: cursor placed at 5" 5 "$mock_cmd_cursor"
set mock_cmd_buffer "sudo systemctl status nginx"
set mock_cmd_cursor -1
_replace_command_token
check "sudo systemctl status: buffer preserves sudo" "sudo status nginx" "$mock_cmd_buffer"
check "sudo systemctl status: cursor placed at 5" 5 "$mock_cmd_cursor"
set mock_cmd_buffer "python -m unittest"
set mock_cmd_cursor -1
_replace_command_token
check "python -m unittest: buffer replaces command token" " -m unittest" "$mock_cmd_buffer"
check "python -m unittest: cursor placed at 0" 0 "$mock_cmd_cursor"
set mock_cmd_buffer ls
set mock_cmd_cursor -1
_replace_command_token
check "single token ls: buffer becomes single space" " " "$mock_cmd_buffer"
check "single token ls: cursor placed at 0" 0 "$mock_cmd_cursor"
set mock_cmd_buffer "sudo reboot"
set mock_cmd_cursor -1
_replace_command_token
check "sudo reboot: buffer becomes sudo with two spaces" "sudo " "$mock_cmd_buffer"
check "sudo reboot: cursor placed at 5" 5 "$mock_cmd_cursor"
# -----------------------------------------------------------------------------
# __substitute_typo
# -----------------------------------------------------------------------------
section __substitute_typo
set_test_history 'git commit -m "feat"'
set mock_cmd_buffer "^feat^fix"
set mock_cmd_inserted ""
__substitute_typo
check "substitute ^feat^fix in git commit" 'git commit -m "fix"' "$mock_cmd_buffer"
set_test_history "docker run -d nginx"
set mock_cmd_buffer "^nginx^redis"
set mock_cmd_inserted ""
__substitute_typo
check "substitute ^nginx^redis in docker run" "docker run -d redis" "$mock_cmd_buffer"
set_test_history "echo foo foo"
set mock_cmd_buffer "^foo^bar"
set mock_cmd_inserted ""
__substitute_typo
check "substitute replaces all occurrences" "echo bar bar" "$mock_cmd_buffer"
set_test_history "git checkout main feat"
set mock_cmd_buffer "^feat^"
set mock_cmd_inserted ""
__substitute_typo
check "substitute ^feat^ with empty string deletes token" "git checkout main " "$mock_cmd_buffer"
set_test_history "git status"
set mock_cmd_buffer "^"
set mock_cmd_inserted ""
__substitute_typo
check "lone caret inserts literal caret" "^" "$mock_cmd_inserted"
check "lone caret buffer retains caret" "^^" "$mock_cmd_buffer"
set_test_history "git status"
set mock_cmd_buffer "git log"
set mock_cmd_inserted ""
__substitute_typo
check "non-matching buffer inserts literal caret" "^" "$mock_cmd_inserted"
cleanup_test_history
# -----------------------------------------------------------------------------
# _puffer_fish_expand_dot
# -----------------------------------------------------------------------------
section _puffer_fish_expand_dot
set mock_cmd_token ".."
set mock_cmd_inserted ""
set mock_cmd_buffer ""
set mock_cmd_in_search 0
_puffer_fish_expand_dot
check "token .. inserts /.." "/.." "$mock_cmd_inserted"
set mock_cmd_token "../.."
set mock_cmd_inserted ""
set mock_cmd_buffer ""
set mock_cmd_in_search 0
_puffer_fish_expand_dot
check "token ../.. inserts /.." "/.." "$mock_cmd_inserted"
set mock_cmd_token "../../.."
set mock_cmd_inserted ""
set mock_cmd_buffer ""
set mock_cmd_in_search 0
_puffer_fish_expand_dot
check "token ../../.. inserts /.." "/.." "$mock_cmd_inserted"
set mock_cmd_token "."
set mock_cmd_inserted ""
set mock_cmd_buffer ""
set mock_cmd_in_search 0
_puffer_fish_expand_dot
check "token . inserts ." "." "$mock_cmd_inserted"
set mock_cmd_token "..."
set mock_cmd_inserted ""
set mock_cmd_buffer ""
set mock_cmd_in_search 0
_puffer_fish_expand_dot
check "token ... inserts ." "." "$mock_cmd_inserted"
set mock_cmd_token foo
set mock_cmd_inserted ""
set mock_cmd_buffer ""
set mock_cmd_in_search 0
_puffer_fish_expand_dot
check "token foo inserts ." "." "$mock_cmd_inserted"
set mock_cmd_token ".."
set mock_cmd_inserted ""
set mock_cmd_buffer ""
set mock_cmd_in_search 1
_puffer_fish_expand_dot
check "search field mode inserts . even if token is .." "." "$mock_cmd_inserted"
# -----------------------------------------------------------------------------
# _puffer_fish_expand_bang
# -----------------------------------------------------------------------------
section _puffer_fish_expand_bang
set_test_history "cargo test --release"
set mock_cmd_token "!"
set mock_cmd_buffer "!"
set mock_cmd_inserted ""
set mock_cmd_in_search 0
_puffer_fish_expand_bang
check "token ! expands to history[1]" "cargo test --release" "$mock_cmd_token"
check "token ! updates buffer" "cargo test --release" "$mock_cmd_buffer"
set mock_cmd_token "!"
set mock_cmd_buffer "sudo !"
set mock_cmd_inserted ""
set mock_cmd_in_search 0
_puffer_fish_expand_bang
check "sudo ! expands token to history[1]" "sudo cargo test --release" "$mock_cmd_buffer"
set mock_cmd_token git
set mock_cmd_buffer git
set mock_cmd_inserted ""
set mock_cmd_in_search 0
_puffer_fish_expand_bang
check "non-bang token inserts literal !" "!" "$mock_cmd_inserted"
set mock_cmd_token ""
set mock_cmd_buffer ""
set mock_cmd_inserted ""
set mock_cmd_in_search 0
_puffer_fish_expand_bang
check "empty token inserts literal !" "!" "$mock_cmd_inserted"
set mock_cmd_token "!"
set mock_cmd_buffer ""
set mock_cmd_inserted ""
set mock_cmd_in_search 1
_puffer_fish_expand_bang
check "search field mode inserts literal !" "!" "$mock_cmd_inserted"
cleanup_test_history
# Unshadow commandline builtin
functions -e commandline
report
+429
View File
@@ -0,0 +1,429 @@
#!/usr/bin/env python3
# Copyright (C) 2026 Rootiest
# SPDX-License-Identifier: AGPL-3.0-or-later
"""Comprehensive unit tests for scripts/sync-labels.py with network isolation."""
import io
import json
import os
import socket
import sys
import tempfile
import unittest
import warnings
from pathlib import Path
from unittest.mock import MagicMock, patch, call
import urllib.error
import urllib.request
import importlib.util
# Load scripts/sync-labels.py via importlib (handling the hyphen in filename)
_script_path = Path(__file__).resolve().parent.parent / "scripts" / "sync-labels.py"
_spec = importlib.util.spec_from_file_location("sync_labels", _script_path)
sl = importlib.util.module_from_spec(_spec)
sys.modules["sync_labels"] = sl
_spec.loader.exec_module(sl)
def _make_http_error(url, code, msg, headers, body_bytes):
fp = io.BytesIO(body_bytes)
return urllib.error.HTTPError(url, code, msg, headers, fp)
class TestRequestNetworkDrop(unittest.TestCase):
"""Edge cases for complete network drop and connection failures."""
@patch("urllib.request.urlopen")
def test_request_connection_refused(self, mock_urlopen):
mock_urlopen.side_effect = urllib.error.URLError(
ConnectionRefusedError(111, "Connection refused")
)
with self.assertRaises(sl.SyncError) as ctx:
sl.request("https://api.github.com/test")
self.assertIn("Connection refused", str(ctx.exception))
@patch("urllib.request.urlopen")
def test_request_dns_resolution_failure(self, mock_urlopen):
mock_urlopen.side_effect = urllib.error.URLError(
socket.gaierror(-2, "Name or service not known")
)
with self.assertRaises(sl.SyncError) as ctx:
sl.request("https://nonexistent.invalid/test")
self.assertIn("Name or service not known", str(ctx.exception))
@patch("urllib.request.urlopen")
def test_request_network_unreachable(self, mock_urlopen):
mock_urlopen.side_effect = urllib.error.URLError(
OSError(101, "Network is unreachable")
)
with self.assertRaises(sl.SyncError) as ctx:
sl.request("https://api.github.com/test")
self.assertIn("Network is unreachable", str(ctx.exception))
class TestRequestTimeouts(unittest.TestCase):
"""Edge cases for slow or hung connections and timeouts."""
@patch("urllib.request.urlopen")
def test_request_socket_timeout(self, mock_urlopen):
mock_urlopen.side_effect = urllib.error.URLError(
socket.timeout("The read operation timed out")
)
with self.assertRaises(sl.SyncError) as ctx:
sl.request("https://api.github.com/test")
self.assertIn("timed out", str(ctx.exception))
@patch("urllib.request.urlopen")
def test_request_timeout_parameter_passed(self, mock_urlopen):
mock_resp = MagicMock()
mock_resp.read.return_value = b'{"ok": true}'
mock_resp.__enter__.return_value = mock_resp
mock_resp.__exit__.return_value = None
mock_urlopen.return_value = mock_resp
sl.request("https://api.github.com/test")
mock_urlopen.assert_called_once()
_, kwargs = mock_urlopen.call_args
self.assertEqual(kwargs.get("timeout"), sl.TIMEOUT)
class TestRequestHttpCodes(unittest.TestCase):
"""Edge cases for HTTP 4xx and 5xx responses, rate limiting, and errors."""
@patch("urllib.request.urlopen")
def test_request_http_401_unauthorized(self, mock_urlopen):
err = _make_http_error(
"https://api.github.com/test",
401,
"Unauthorized",
{},
b'{"message": "Bad credentials"}',
)
mock_urlopen.side_effect = err
with self.assertRaises(sl.SyncError) as ctx:
sl.request("https://api.github.com/test", token="invalid")
self.assertIn("HTTP 401", str(ctx.exception))
self.assertIn("Bad credentials", str(ctx.exception))
err.close()
@patch("urllib.request.urlopen")
def test_request_http_403_rate_limit(self, mock_urlopen):
err = _make_http_error(
"https://api.github.com/test",
403,
"Forbidden",
{"X-RateLimit-Remaining": "0"},
b'{"message": "API rate limit exceeded for user"}',
)
mock_urlopen.side_effect = err
with self.assertRaises(sl.SyncError) as ctx:
sl.request("https://api.github.com/test")
self.assertIn("HTTP 403", str(ctx.exception))
self.assertIn("rate limit exceeded", str(ctx.exception))
err.close()
@patch("urllib.request.urlopen")
def test_request_http_404_not_found(self, mock_urlopen):
err = _make_http_error(
"https://api.github.com/test",
404,
"Not Found",
{},
b'{"message": "Not Found"}',
)
mock_urlopen.side_effect = err
with self.assertRaises(sl.SyncError) as ctx:
sl.request("https://api.github.com/test")
self.assertIn("HTTP 404", str(ctx.exception))
err.close()
@patch("urllib.request.urlopen")
def test_request_http_500_server_error(self, mock_urlopen):
err = _make_http_error(
"https://api.github.com/test",
500,
"Internal Server Error",
{},
b"Internal Server Error",
)
mock_urlopen.side_effect = err
with self.assertRaises(sl.SyncError) as ctx:
sl.request("https://api.github.com/test")
self.assertIn("HTTP 500", str(ctx.exception))
err.close()
@patch("urllib.request.urlopen")
def test_request_http_502_bad_gateway(self, mock_urlopen):
err = _make_http_error(
"https://api.github.com/test",
502,
"Bad Gateway",
{},
b"<html><body>502 Bad Gateway</body></html>",
)
mock_urlopen.side_effect = err
with self.assertRaises(sl.SyncError) as ctx:
sl.request("https://api.github.com/test")
self.assertIn("HTTP 502", str(ctx.exception))
err.close()
class TestRequestPayloads(unittest.TestCase):
"""Edge cases for payloads, parsing, and request formatting."""
@patch("urllib.request.urlopen")
def test_request_valid_json(self, mock_urlopen):
mock_resp = MagicMock()
mock_resp.read.return_value = b'[{"name": "Kind/Bug", "color": "ee0701"}]'
mock_resp.__enter__.return_value = mock_resp
mock_resp.__exit__.return_value = None
mock_urlopen.return_value = mock_resp
res = sl.request("https://api.github.com/test")
self.assertEqual(res, [{"name": "Kind/Bug", "color": "ee0701"}])
@patch("urllib.request.urlopen")
def test_request_empty_body_204(self, mock_urlopen):
mock_resp = MagicMock()
mock_resp.read.return_value = b""
mock_resp.__enter__.return_value = mock_resp
mock_resp.__exit__.return_value = None
mock_urlopen.return_value = mock_resp
res = sl.request("https://api.github.com/test", method="DELETE")
self.assertIsNone(res)
@patch("urllib.request.urlopen")
def test_request_headers_and_auth(self, mock_urlopen):
mock_resp = MagicMock()
mock_resp.read.return_value = b"{}"
mock_resp.__enter__.return_value = mock_resp
mock_resp.__exit__.return_value = None
mock_urlopen.return_value = mock_resp
sl.request(
"https://api.github.com/test",
token="secret_token",
method="POST",
payload={"name": "test"},
)
req = mock_urlopen.call_args[0][0]
self.assertEqual(req.get_header("Authorization"), "Bearer secret_token")
self.assertEqual(req.get_header("Accept"), "application/json")
self.assertEqual(req.get_header("Content-type"), "application/json")
self.assertEqual(req.get_method(), "POST")
self.assertEqual(req.data, b'{"name": "test"}')
@patch("urllib.request.urlopen")
def test_request_corrupted_json_payload(self, mock_urlopen):
mock_resp = MagicMock()
mock_resp.read.return_value = b'{"name": "broken", invalid_json'
mock_resp.__enter__.return_value = mock_resp
mock_resp.__exit__.return_value = None
mock_urlopen.return_value = mock_resp
with self.assertRaises(json.JSONDecodeError):
sl.request("https://api.github.com/test")
class TestPaginate(unittest.TestCase):
"""Edge cases for pagination across API responses."""
@patch("sync_labels.request")
def test_paginate_single_page(self, mock_req):
mock_req.return_value = [{"name": "A"}, {"name": "B"}]
res = sl.paginate("https://api.test?page={page}&per_page={per_page}", per_page=50)
self.assertEqual(len(res), 2)
mock_req.assert_called_once_with(
"https://api.test?page=1&per_page=50", token=None
)
@patch("sync_labels.request")
def test_paginate_multiple_pages(self, mock_req):
page1 = [{"name": f"item{i}"} for i in range(50)]
page2 = [{"name": f"item{i}"} for i in range(50, 70)]
mock_req.side_effect = [page1, page2]
res = sl.paginate("https://api.test?page={page}&per_page={per_page}", per_page=50)
self.assertEqual(len(res), 70)
self.assertEqual(mock_req.call_count, 2)
@patch("sync_labels.request")
def test_paginate_exact_page_boundary(self, mock_req):
page1 = [{"name": f"item{i}"} for i in range(50)]
page2 = []
mock_req.side_effect = [page1, page2]
res = sl.paginate("https://api.test?page={page}&per_page={per_page}", per_page=50)
self.assertEqual(len(res), 50)
self.assertEqual(mock_req.call_count, 2)
@patch("sync_labels.request")
def test_paginate_empty(self, mock_req):
mock_req.return_value = []
res = sl.paginate("https://api.test?page={page}&per_page={per_page}", per_page=50)
self.assertEqual(res, [])
mock_req.assert_called_once()
class TestLabelOperations(unittest.TestCase):
"""Edge cases for label CRUD operations and usage querying."""
@patch("sync_labels.request")
def test_usage_count_zero(self, mock_req):
mock_req.return_value = []
count = sl.usage_count("Unused Label", "token")
self.assertEqual(count, 0)
mock_req.assert_called_once_with(
f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/issues"
f"?labels=Unused%20Label&state=all&per_page=100",
token="token",
)
@patch("sync_labels.request")
def test_usage_count_active(self, mock_req):
mock_req.return_value = [{"number": 1}, {"number": 2}]
count = sl.usage_count("Priority/High", "token")
self.assertEqual(count, 2)
mock_req.assert_called_once_with(
f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/issues"
f"?labels=Priority%2FHigh&state=all&per_page=100",
token="token",
)
@patch("sync_labels.request")
def test_create_label(self, mock_req):
label = {"name": "Test/Label", "color": "123456", "description": "Desc"}
sl.create_label(label, "tok")
mock_req.assert_called_once_with(
f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/labels",
token="tok",
method="POST",
payload=label,
)
@patch("sync_labels.request")
def test_update_label(self, mock_req):
label = {"name": "Area/Prompt & Theme", "color": "abcdef", "description": "New"}
sl.update_label(label, "tok")
mock_req.assert_called_once_with(
f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/labels/Area%2FPrompt%20%26%20Theme",
token="tok",
method="PATCH",
payload={
"new_name": "Area/Prompt & Theme",
"color": "abcdef",
"description": "New",
},
)
@patch("sync_labels.request")
def test_delete_label(self, mock_req):
sl.delete_label("Reviewed/Won't Fix", "tok")
mock_req.assert_called_once_with(
f"{sl.GITHUB_API}/repos/{sl.GITHUB_REPO}/labels/Reviewed%2FWon%27t%20Fix",
token="tok",
method="DELETE",
)
class TestRunAndMainWorkflow(unittest.TestCase):
"""End-to-end execution testing dry-run, live execution, and error handling."""
def setUp(self):
self.env_patcher = patch.dict(os.environ, {}, clear=True)
self.env_patcher.start()
def tearDown(self):
self.env_patcher.stop()
def test_run_missing_token_not_dry_run_raises(self):
with self.assertRaises(sl.SyncError) as ctx:
sl.run(dry_run=False)
self.assertIn("GH_MIRROR_TOKEN is not set", str(ctx.exception))
@patch("sync_labels.fetch_gitea_labels")
def test_run_gitea_empty_raises(self, mock_gitea):
os.environ[sl.TOKEN_ENV] = "dummy_token"
mock_gitea.return_value = []
with self.assertRaises(sl.SyncError) as ctx:
sl.run(dry_run=False)
self.assertIn("Gitea returned no labels", str(ctx.exception))
@patch("sys.stdout", new_callable=io.StringIO)
@patch("sync_labels.delete_label")
@patch("sync_labels.update_label")
@patch("sync_labels.create_label")
@patch("sync_labels.usage_count")
@patch("sync_labels.fetch_github_labels")
@patch("sync_labels.fetch_gitea_labels")
def test_run_dry_run_makes_no_mutating_calls(
self, mock_gitea, mock_gh, mock_usage, mock_create, mock_update, mock_delete, mock_stdout
):
mock_gitea.return_value = [
{"name": "Keep", "color": "111111", "description": "Same"},
{"name": "New", "color": "222222", "description": "Created"},
{"name": "Change", "color": "333333", "description": "Updated"},
]
mock_gh.return_value = [
{"name": "Keep", "color": "111111", "description": "Same"},
{"name": "Change", "color": "000000", "description": "Old"},
{"name": "ExtraUnused", "color": "444444", "description": "Deleted"},
{"name": "ExtraUsed", "color": "555555", "description": "Kept"},
]
mock_usage.side_effect = lambda name, token: 1 if name == "ExtraUsed" else 0
# In dry run, token is not strictly required
status = sl.run(dry_run=True)
self.assertEqual(status, 0)
mock_create.assert_not_called()
mock_update.assert_not_called()
mock_delete.assert_not_called()
@patch("sys.stdout", new_callable=io.StringIO)
@patch("sync_labels.delete_label")
@patch("sync_labels.update_label")
@patch("sync_labels.create_label")
@patch("sync_labels.usage_count")
@patch("sync_labels.fetch_github_labels")
@patch("sync_labels.fetch_gitea_labels")
def test_run_live_executes_plan(
self, mock_gitea, mock_gh, mock_usage, mock_create, mock_update, mock_delete, mock_stdout
):
os.environ[sl.TOKEN_ENV] = "my_token"
mock_gitea.return_value = [
{"name": "Keep", "color": "111111", "description": "Same"},
{"name": "New", "color": "222222", "description": "Created"},
{"name": "Change", "color": "333333", "description": "Updated"},
]
mock_gh.return_value = [
{"name": "Keep", "color": "111111", "description": "Same"},
{"name": "Change", "color": "000000", "description": "Old"},
{"name": "ExtraUnused", "color": "444444", "description": "Deleted"},
{"name": "ExtraUsed", "color": "555555", "description": "Kept"},
]
mock_usage.side_effect = lambda name, token: 1 if name == "ExtraUsed" else 0
status = sl.run(dry_run=False)
self.assertEqual(status, 0)
mock_create.assert_called_once_with(
{"name": "New", "color": "222222", "description": "Created"}, "my_token"
)
mock_update.assert_called_once()
mock_delete.assert_called_once_with("ExtraUnused", "my_token")
@patch("sys.stdout", new_callable=io.StringIO)
def test_main_self_test(self, mock_stdout):
self.assertEqual(sl.main(["--self-test"]), 0)
@patch("sync_labels.run")
def test_main_sync_error_exit_code(self, mock_run):
mock_run.side_effect = sl.SyncError("Boom")
with patch("sys.stderr", new=io.StringIO()) as fake_err:
rc = sl.main(["--dry-run"])
self.assertEqual(rc, 1)
self.assertIn("error: Boom", fake_err.getvalue())
if __name__ == "__main__":
unittest.main()