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 function __bd_debug
set -l file "$BASH_COMP_DEBUG_FILE" set -l file "$BASH_COMP_DEBUG_FILE"
if test -n "$file" if test -n "$file"
echo "$argv" >> $file echo "$argv" >>$file
end end
end end
@@ -108,7 +108,6 @@ function __bd_requires_order_preservation
return 1 return 1
end end
# This function does two things: # This function does two things:
# - Obtain the completions and store them in the global __bd_comp_results # - Obtain the completions and store them in the global __bd_comp_results
# - Return false if file completion should be performed # - Return false if file completion should be performed
@@ -215,18 +214,18 @@ end
# so we can properly delete any completions provided by another script. # 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, # 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. # 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 # The space after the program name is essential to trigger completion for the program
# and not completion of the program name itself. # and not completion of the program name itself.
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. # 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 end
# Remove any pre-existing completions for the program since we will be handling all of them. # Remove any pre-existing completions for the program since we will be handling all of them.
complete -c bd -e complete -c bd -e
# this will get called after the two calls below and clear the $__bd_perform_completion_once_result global # 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 # The call to __bd_prepare_completions will setup __bd_comp_results
# which provides the program's completion choices. # which provides the program's completion choices.
# If this doesn't require order preservation, we don't use the -k flag # 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 # fish completion for copilot
# Generated by `copilot completion fish`. Do not edit by hand. # 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 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 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 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 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 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 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 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 completion -d 'Generate a shell completion script'
complete -c copilot -l version -s v -f -d 'show version information' 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 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)' 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 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 -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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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)' 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. # Print an optspec for argparse to handle cmd's options that are independent of any subcommand.
function __fish_deadbranch_global_optspecs function __fish_deadbranch_global_optspecs
string join \n h/help V/version string join \n h/help V/version
end end
function __fish_deadbranch_needs_command function __fish_deadbranch_needs_command
# Figure out if the current invocation already has a command. # Figure out if the current invocation already has a command.
set -l cmd (commandline -opc) set -l cmd (commandline -opc)
set -e cmd[1] set -e cmd[1]
argparse -s (__fish_deadbranch_global_optspecs) -- $cmd 2>/dev/null argparse -s (__fish_deadbranch_global_optspecs) -- $cmd 2>/dev/null
or return or return
if set -q argv[1] if set -q argv[1]
# Also print the command, so this can be used to figure out what it is. # Also print the command, so this can be used to figure out what it is.
echo $argv[1] echo $argv[1]
return 1 return 1
end end
return 0 return 0
end end
function __fish_deadbranch_using_subcommand function __fish_deadbranch_using_subcommand
set -l cmd (__fish_deadbranch_needs_command) set -l cmd (__fish_deadbranch_needs_command)
test -z "$cmd" test -z "$cmd"
and return 1 and return 1
contains -- $cmd[1] $argv contains -- $cmd[1] $argv
end 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 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 -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 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 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 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 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 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 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 -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" -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 local -d 'Only show local branches'
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -l remote -d 'Only show remote 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 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 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" -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 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 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 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 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 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 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 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' 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 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 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 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 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 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 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 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 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 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" -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 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 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 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 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 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 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" -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' 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 __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 function __ov_debug
set -l file "$BASH_COMP_DEBUG_FILE" set -l file "$BASH_COMP_DEBUG_FILE"
if test -n "$file" if test -n "$file"
echo "$argv" >> $file echo "$argv" >>$file
end end
end end
@@ -108,7 +108,6 @@ function __ov_requires_order_preservation
return 1 return 1
end end
# This function does two things: # This function does two things:
# - Obtain the completions and store them in the global __ov_comp_results # - Obtain the completions and store them in the global __ov_comp_results
# - Return false if file completion should be performed # - Return false if file completion should be performed
@@ -215,18 +214,18 @@ end
# so we can properly delete any completions provided by another script. # 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, # 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. # 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 # The space after the program name is essential to trigger completion for the program
# and not completion of the program name itself. # and not completion of the program name itself.
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish. # 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 end
# Remove any pre-existing completions for the program since we will be handling all of them. # Remove any pre-existing completions for the program since we will be handling all of them.
complete -c ov -e complete -c ov -e
# this will get called after the two calls below and clear the $__ov_perform_completion_once_result global # 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 # The call to __ov_prepare_completions will setup __ov_comp_results
# which provides the program's completion choices. # which provides the program's completion choices.
# If this doesn't require order preservation, we don't use the -k flag # 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 function __tailscale_debug
set -l file "$BASH_COMP_DEBUG_FILE" set -l file "$BASH_COMP_DEBUG_FILE"
if test -n "$file" if test -n "$file"
echo "$argv" >> $file echo "$argv" >>$file
end end
end end
@@ -125,7 +125,6 @@ function __tailscale_requires_order_preservation
return 1 return 1
end end
# This function does two things: # This function does two things:
# - Obtain the completions and store them in the global __tailscale_comp_results # - Obtain the completions and store them in the global __tailscale_comp_results
# - Return false if file completion should be performed # - Return false if file completion should be performed
@@ -242,7 +241,7 @@ end
complete -c tailscale -e complete -c tailscale -e
# this will get called after the two calls below and clear the $__tailscale_perform_completion_once_result global # 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 # The call to __tailscale_prepare_completions will setup __tailscale_comp_results
# which provides the program's completion choices. # which provides the program's completion choices.
# If this doesn't require order preservation, we don't use the -k flag # 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:" "zoxide:"
set -g __fish_config_op_registry_values \ set -g __fish_config_op_registry_values \
"autoexec/venv" \ autoexec/venv \
"logging/terminal-capture" \ logging/terminal-capture \
"autoexec/sync" \ autoexec/sync \
"logging/multiplexer-capture" \ logging/multiplexer-capture \
"integrations/terminal-abbrs" \ integrations/terminal-abbrs \
"overrides/key-bindings" \ overrides/key-bindings \
"aliases/dev-tools" \ aliases/dev-tools \
"autoexec/sync" \ autoexec/sync \
"overrides/key-bindings" \ overrides/key-bindings \
"aliases/shell-tools" \ aliases/shell-tools \
"overrides/key-bindings" \ overrides/key-bindings \
"aliases/filesystem" \ aliases/filesystem \
"aliases/dev-tools" \ aliases/dev-tools \
"aliases/filesystem" \ aliases/filesystem \
"overrides/key-bindings" \ overrides/key-bindings \
"overrides/environment" \ overrides/environment \
"overrides/environment" \ overrides/environment \
"overrides/key-bindings" \ overrides/key-bindings \
"greeting/greeting-message" \ greeting/greeting-message \
"overrides/environment" \ overrides/environment \
"overrides/environment" \ overrides/environment \
"overrides/privacy" \ overrides/privacy \
"overrides/key-bindings" \ overrides/key-bindings \
"integrations/notifications" \ integrations/notifications \
"aliases/filesystem" \ aliases/filesystem \
"aliases/dev-tools" \ aliases/dev-tools \
"autoexec/plugin-management" \ autoexec/plugin-management \
"greeting/first-run" \ greeting/first-run \
"overrides/prompt" \ overrides/prompt \
"aliases/shell-tools" \ aliases/shell-tools \
"integrations/history-logs" \ integrations/history-logs \
"overrides/key-bindings" \ overrides/key-bindings \
"logging/terminal-capture" \ logging/terminal-capture \
"logging/terminal-capture" \ logging/terminal-capture \
"aliases/shell-tools" \ aliases/shell-tools \
"integrations/history-logs" \ integrations/history-logs \
"aliases/filesystem" \ aliases/filesystem \
"aliases/filesystem" \ aliases/filesystem \
"aliases/filesystem" \ aliases/filesystem \
"autoexec/pkg-wrappers" \ autoexec/pkg-wrappers \
"logging/pkg-logs" \ logging/pkg-logs \
"aliases/network" \ aliases/network \
"overrides/key-bindings" \ overrides/key-bindings \
"aliases/search" \ aliases/search \
"aliases/filesystem" \ aliases/filesystem \
"overrides/key-bindings" \ overrides/key-bindings \
"logging/terminal-capture" \ logging/terminal-capture \
"integrations/window-mgmt" \ integrations/window-mgmt \
"integrations/window-mgmt" \ integrations/window-mgmt \
"aliases/network" \ aliases/network \
"overrides/prompt" \ overrides/prompt \
"integrations/window-mgmt" \ integrations/window-mgmt \
"overrides/prompt" \ overrides/prompt \
"logging/multiplexer-capture" \ logging/multiplexer-capture \
"aliases/monitor" \ aliases/monitor \
"aliases/filesystem" \ aliases/filesystem \
"overrides/key-bindings" \ overrides/key-bindings \
"overrides/environment" \ overrides/environment \
"integrations/pkg-upgrade" \ integrations/pkg-upgrade \
"autoexec/telemetry" \ autoexec/telemetry \
"integrations/notifications" \ integrations/notifications \
"autoexec/pkg-wrappers" \ autoexec/pkg-wrappers \
"logging/pkg-logs" \ logging/pkg-logs \
"aliases/network" \ aliases/network \
"aliases/filesystem" aliases/filesystem
+6 -2
View File
@@ -17,7 +17,9 @@ function expand_bang_all --description 'Execute expand_bang_all'
__fish_config_op_enabled (status basename); or return 1 __fish_config_op_enabled (status basename); or return 1
set -l token $argv[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]) set -l tokens (string split -n " " -- $history[1])
if test (count $tokens) -gt 1 if test (count $tokens) -gt 1
@@ -46,7 +48,9 @@ function expand_bang_minus_n --description 'Execute expand_bang_minus_n'
__fish_config_op_enabled (status basename); or return 1 __fish_config_op_enabled (status basename); or return 1
set -l token $argv[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 # Extract the number from the regex match
if string match -qr '!-(\d+)' -- "$token" if string match -qr '!-(\d+)' -- "$token"
+5 -6
View File
@@ -60,7 +60,7 @@ printf '%s\n' \
"cmd_str=\"$_paru_real\"" \ "cmd_str=\"$_paru_real\"" \
'for arg in "$@"; do' \ 'for arg in "$@"; do' \
' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \ ' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \
'done' \ done \
'script -q -e -c "$cmd_str" "$log_file"' \ 'script -q -e -c "$cmd_str" "$log_file"' \
'exit_code=$?' \ 'exit_code=$?' \
'' \ '' \
@@ -70,19 +70,18 @@ printf '%s\n' \
'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \ 'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \
'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \ '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"' \ ' 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' \ ' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \
'fi' \ fi \
'' \ '' \
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \ 'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
'mapfile -t logs < <(ls -1t "$log_dir"/paru_*.log 2>/dev/null)' \ 'mapfile -t logs < <(ls -1t "$log_dir"/paru_*.log 2>/dev/null)' \
'excess=$(( ${#logs[@]} - max_files ))' \ 'excess=$(( ${#logs[@]} - max_files ))' \
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \ 'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
' rm -f "${logs[$i]}"' \ ' rm -f "${logs[$i]}"' \
'done' \ done \
'' \ '' \
'exit $exit_code' \ 'exit $exit_code' >$_paru_wrapper
> $_paru_wrapper
chmod +x $_paru_wrapper chmod +x $_paru_wrapper
+26 -25
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 __fish_config_op_enabled (status basename) wakatime-hook; or exit
function __register_wakatime_fish_before_exec -e fish_postexec function __register_wakatime_fish_before_exec -e fish_postexec
if set -q FISH_WAKATIME_DISABLED if set -q FISH_WAKATIME_DISABLED
return 0 return 0
end end
set -l exec_command_str set -l exec_command_str
set exec_command_str (string split -f1 ' ' "$argv") set exec_command_str (string split -f1 ' ' "$argv")
if test "$exec_command_str" = 'exit' if test "$exec_command_str" = exit
return 0 return 0
end end
set -l PLUGIN_NAME "ik11235/wakatime.fish" set -l PLUGIN_NAME "ik11235/wakatime.fish"
set -l PLUGIN_VERSION "0.0.6" set -l PLUGIN_VERSION "0.0.6"
set -l project set -l project
set -l wakatime_path set -l wakatime_path
if type -p wakatime 2>&1 > /dev/null if type -p wakatime 2>&1 >/dev/null
set wakatime_path (type -p wakatime) set wakatime_path (type -p wakatime)
else if type -p ~/.wakatime/wakatime-cli 2>&1 > /dev/null else if type -p ~/.wakatime/wakatime-cli 2>&1 >/dev/null
set wakatime_path (type -p ~/.wakatime/wakatime-cli) set wakatime_path (type -p ~/.wakatime/wakatime-cli)
else else
return 1 return 1
end end
if git rev-parse --is-inside-work-tree &> /dev/null if git rev-parse --is-inside-work-tree &>/dev/null
set project (basename (git rev-parse --show-toplevel)) set project (basename (git rev-parse --show-toplevel))
else else
set project "Terminal" set project Terminal
end end
$wakatime_path --write --plugin "$PLUGIN_NAME/$PLUGIN_VERSION" --entity-type app --project "$project" --entity "$exec_command_str" &> /dev/null&; disown $wakatime_path --write --plugin "$PLUGIN_NAME/$PLUGIN_VERSION" --entity-type app --project "$project" --entity "$exec_command_str" &>/dev/null &
disown
end end
+5 -6
View File
@@ -60,7 +60,7 @@ printf '%s\n' \
"cmd_str=\"$_yay_real\"" \ "cmd_str=\"$_yay_real\"" \
'for arg in "$@"; do' \ 'for arg in "$@"; do' \
' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \ ' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \
'done' \ done \
'script -q -e -c "$cmd_str" "$log_file"' \ 'script -q -e -c "$cmd_str" "$log_file"' \
'exit_code=$?' \ 'exit_code=$?' \
'' \ '' \
@@ -70,19 +70,18 @@ printf '%s\n' \
'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \ 'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \
'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \ '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"' \ ' 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' \ ' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \
'fi' \ fi \
'' \ '' \
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \ 'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
'mapfile -t logs < <(ls -1t "$log_dir"/yay_*.log 2>/dev/null)' \ 'mapfile -t logs < <(ls -1t "$log_dir"/yay_*.log 2>/dev/null)' \
'excess=$(( ${#logs[@]} - max_files ))' \ 'excess=$(( ${#logs[@]} - max_files ))' \
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \ 'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
' rm -f "${logs[$i]}"' \ ' rm -f "${logs[$i]}"' \
'done' \ done \
'' \ '' \
'exit $exit_code' \ 'exit $exit_code' >$_yay_wrapper
> $_yay_wrapper
chmod +x $_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 # Resolve user-dots path. Customize via: set -U __fish_user_dots_path /your/path
set -q __fish_user_dots_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 ─────────────────── # ────────────────────── user-dots convenience symlink ───────────────────
# Keep $__fish_config_dir/user-dots tracking the resolved path so it can be # 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 # 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 += [f" {k} \\" for k in quoted_keys[:-1]] + [f" {quoted_keys[-1]}"]
lines.append("") 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.append("set -g __fish_config_op_registry_values \\")
lines += [f" {v} \\" for v in values[:-1]] + [f" {values[-1]}"] lines += [f" {v} \\" for v in values[:-1]] + [f" {values[-1]}"]
lines.append("")
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"
+9 -9
View File
@@ -27,17 +27,17 @@ function __auto_source_fallback_venv --on-variable PWD
end end
# 2. If we are already in a venv, check if we've left its tree # 2. If we are already in a venv, check if we've left its tree
if set -q VIRTUAL_ENV if set -q VIRTUAL_ENV
# Check if the current PWD is still within the directory that owns the venv # Check if the current PWD is still within the directory that owns the venv
# (Assuming the venv is at the root of the project) # (Assuming the venv is at the root of the project)
set -l venv_root (string replace -r '/.venv$' '' $VIRTUAL_ENV) set -l venv_root (string replace -r '/.venv$' '' $VIRTUAL_ENV)
if not string match -q "$venv_root*" "$PWD" if not string match -q "$venv_root*" "$PWD"
type -q deactivate; and deactivate type -q deactivate; and deactivate
end
return
end end
return
end
# 3. Only source the venv if we aren't already in one # 3. Only source the venv if we aren't already in one
if test -e ".venv/bin/activate.fish" if test -e ".venv/bin/activate.fish"
source .venv/bin/activate.fish source .venv/bin/activate.fish
end end
+2 -2
View File
@@ -27,8 +27,8 @@
# __config_settings_apply __fish_config_op_greeting session DEFAULT # __config_settings_apply __fish_config_op_greeting session DEFAULT
function __config_settings_apply function __config_settings_apply
set -l varname $argv[1] set -l varname $argv[1]
set -l scope $argv[2] set -l scope $argv[2]
set -l value $argv[3] set -l value $argv[3]
# stderr is suppressed because setting a value in one scope while the # stderr is suppressed because setting a value in one scope while the
# other scope already holds the same variable makes interactive fish # 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 # __config_settings_set_value __fish_user_dots_path path '' # reset
function __config_settings_set_value function __config_settings_set_value
set -l varname $argv[1] set -l varname $argv[1]
set -l type $argv[2] set -l type $argv[2]
set -l value $argv[3] set -l value $argv[3]
# stderr suppressed: editing a universal while a global of the same name # stderr suppressed: editing a universal while a global of the same name
# shadows it makes interactive fish emit a shadow warning that would # 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 case __fish_config_op_aliases
printf '%s\t%s\t%s\n' \ printf '%s\t%s\t%s\n' \
filesystem Filesystem "ls, cat, cd, du, mkdir, rm, mv, zoxide" \ filesystem Filesystem "ls, cat, cd, du, mkdir, rm, mv, zoxide" \
search Search "rg" \ search Search rg \
network Network "ping, ssh, yt-dlp" \ network Network "ping, ssh, yt-dlp" \
monitor Monitor "top" \ monitor Monitor top \
shell-tools Shell-tools "bash, less, help" \ shell-tools Shell-tools "bash, less, help" \
dev-tools Dev-tools "claude, edit, agy" dev-tools Dev-tools "claude, edit, agy"
case __fish_config_op_autoexec 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" \ window-mgmt Window-mgmt "spwin, tab, split" \
notifications Notifications "done, WakaTime hook" \ notifications Notifications "done, WakaTime hook" \
history-logs History-logs "hist, logs" \ history-logs History-logs "hist, logs" \
pkg-upgrade Pkg-upgrade "upgrade" pkg-upgrade Pkg-upgrade upgrade
case __fish_config_op_logging case __fish_config_op_logging
printf '%s\t%s\t%s\n' \ printf '%s\t%s\t%s\n' \
terminal-capture Term-capture "Kitty watcher, smart_exit scrollback" \ 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' function __fish_user_dots_link --description 'Manage the user-dots convenience symlink'
set -l link "$__fish_config_dir/user-dots" set -l link "$__fish_config_dir/user-dots"
set -q __fish_user_dots_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"
# Explicit opt-out: remove our symlink (never a real file/dir) and stop. # Explicit opt-out: remove our symlink (never a real file/dir) and stop.
__fish_variable_check __fish_user_dots_symlink __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 test -d "$__fish_user_dots_path"; or return 0
if test -L "$link" if test -L "$link"
test (readlink "$link") != "$__fish_user_dots_path" 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" else if not test -e "$link"
ln -s "$__fish_user_dots_path" "$link" ln -s "$__fish_user_dots_path" "$link"
end end
+3 -3
View File
@@ -28,9 +28,9 @@ function __substitute_typo
set -l expanded (string replace -a -- "$old" "$new" "$last_cmd") set -l expanded (string replace -a -- "$old" "$new" "$last_cmd")
commandline -r "$expanded" commandline -r "$expanded"
# No need to move cursor, it's a whole new line # No need to move cursor, it's a whole new line
end end
else else
# If it's just a normal caret (not part of a pattern), just insert it # If it's just a normal caret (not part of a pattern), just insert it
commandline -i '^' commandline -i '^'
end end
end end
+2 -2
View File
@@ -38,8 +38,8 @@ function _agents_init_ensure_gitignore
return 1 return 1
end end
set -l root $argv[1] set -l root $argv[1]
set -l label $argv[2] set -l label $argv[2]
set -l patterns $argv[3..] set -l patterns $argv[3..]
set -l gitignore "$root/.gitignore" 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] set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2]
if test -n "$_major"; and test "$_major" -lt 4 if test -n "$_major"; and test "$_major" -lt 4
set needs_install 1 set needs_install 1
set upgrade_label "Upgrade" set upgrade_label Upgrade
end end
end end
if test $needs_install -eq 1 if test $needs_install -eq 1
set -l cargo_crate $_fdc_cargo[$i] set -l cargo_crate $_fdc_cargo[$i]
set -l pm_pkg $_fdc_pm[$i] set -l pm_pkg $_fdc_pm[$i]
set -l special $_fdc_special[$i] set -l special $_fdc_special[$i]
# Build list of available install methods # Build list of available install methods
set -l methods set -l methods
@@ -193,7 +193,9 @@ function _fish_deps_install
echo " Available methods:" echo " Available methods:"
set -l m 1 set -l m 1
for lbl in $method_labels 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 echo $lbl
set m (math $m + 1) set m (math $m + 1)
end end
@@ -202,7 +204,9 @@ function _fish_deps_install
set chosen_method $methods[$_choice] set chosen_method $methods[$_choice]
end end
else 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 end
# Execute chosen method # Execute chosen method
@@ -329,14 +333,18 @@ function _fish_deps_install
if test $status -eq 0 if test $status -eq 0
set installed_any 1 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 if test "$bin" = fish
set_color yellow set_color yellow
echo " Fish upgraded — restart your shell to use the new version." echo " Fish upgraded — restart your shell to use the new version."
set_color normal set_color normal
end end
else 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
end end
set i (math $i + 1) 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] set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2]
if test -n "$_major"; and test "$_major" -lt 4 if test -n "$_major"; and test "$_major" -lt 4
set -l _ver (fish --version 2>&1 | string replace 'fish, ' '') 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 " 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 return
end end
end end
set_color green; echo -n " ✓ "; set_color normal set_color green
echo -n " ✓ "
set_color normal
echo -n "$bin " 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 else if test "$tier" = req
set_color red; echo -n " ✗ "; set_color normal set_color red
echo -n " ✗ "
set_color normal
echo -n "$bin " 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 else if test "$tier" = rec
set_color yellow; echo -n " ⚠ "; set_color normal set_color yellow
echo -n " ⚠ "
set_color normal
echo -n "$bin " echo -n "$bin "
set_color brblack; echo "(Not installed)"; set_color normal set_color brblack
echo "(Not installed)"
set_color normal
else else
# opt / term / int: absence is expected and not alarming # 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 " echo -n "$bin "
set_color brblack; echo "(Not installed)"; set_color normal set_color brblack
echo "(Not installed)"
set_color normal
end end
end end
for tier_label in "Required Dependencies:req" "Recommended Dependencies:rec" "Optional Dependencies:opt" "Terminal Emulators:term" "Integrations:int" 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 label (string split : $tier_label)[1]
set -l tier (string split : $tier_label)[2] set -l tier (string split : $tier_label)[2]
set_color cyan; echo $label; set_color normal set_color cyan
echo $label
set_color normal
set -l i 1 set -l i 1
for bin in $_fdc_bins for bin in $_fdc_bins
if test "$_fdc_tiers[$i]" = $tier if test "$_fdc_tiers[$i]" = $tier
+11 -7
View File
@@ -33,9 +33,9 @@ function _fish_deps_update
continue continue
end end
set -l cargo_crate $_fdc_cargo[$i] set -l cargo_crate $_fdc_cargo[$i]
set -l pm_pkg $_fdc_pm[$i] set -l pm_pkg $_fdc_pm[$i]
set -l special $_fdc_special[$i] set -l special $_fdc_special[$i]
# yay: update via paru if available, else system PM # yay: update via paru if available, else system PM
if test "$special" = yay-build if test "$special" = yay-build
@@ -101,10 +101,14 @@ function _fish_deps_update
echo "Updating $bin..." echo "Updating $bin..."
set -l _arch (uname -m) set -l _arch (uname -m)
switch $_arch switch $_arch
case x86_64; set _arch amd64 case x86_64
case aarch64 arm64; set _arch arm64 set _arch amd64
case armv7l; set _arch arm case aarch64 arm64
case '*'; set _arch amd64 set _arch arm64
case armv7l
set _arch arm
case '*'
set _arch amd64
end end
set -l _zip "wakatime-cli-linux-$_arch.zip" set -l _zip "wakatime-cli-linux-$_arch.zip"
set -l _bin_src "wakatime-cli-linux-$_arch" 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 while not test -d $cursor
set -p to_create $cursor set -p to_create $cursor
set -l up (dirname $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 set cursor $up
end end
-1
View File
@@ -7,4 +7,3 @@ function _puffer_fish_expand_bang
commandline --insert '!' commandline --insert '!'
end end
end end
+1 -1
View File
@@ -4,7 +4,7 @@ function _puffer_fish_expand_star
else if string match --quiet -- '!' "$(commandline --current-token)" else if string match --quiet -- '!' "$(commandline --current-token)"
set -l prev_cmd $history[1] set -l prev_cmd $history[1]
set -l prev_args (string split ' ' $prev_cmd) 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) set -l arg_str (string join ' ' $prev_args)
# replace !* with all arguments # replace !* with all arguments
commandline --current-token '' 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 # If it ends in =, run qalc; fall back to normal execute if qalc is absent
_qalc_eval; or commandline -f execute _qalc_eval; or commandline -f execute
# case 'g *' # case 'g *'
# # EXAMPLE FUTURE EXTENSION # # EXAMPLE FUTURE EXTENSION
# _some_git_helper # _some_git_helper
case '*' case '*'
# Default: execute the command line as-is # 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 command cp -rn "$stash/." "$entry/" 2>/dev/null
rm -rf "$stash" rm -rf "$stash"
end end
printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) \ printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) >>"$entry/origin"
>>"$entry/origin"
# Only a link is dropped here, and only so the relink below has # 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 # somewhere to put the new one. Usually $live is exactly that: a
# symlink at the old entry, now dangling. But the migration is # 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 end
set -l reached 1 set -l reached 1
if git -C "$vault" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&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 \ if not $gitnet git -C "$vault" pull --rebase --autostash -q >/dev/null 2>/dev/null
>/dev/null 2>/dev/null
# Two unrelated failures land here and reporting them as # Two unrelated failures land here and reporting them as
# one sends the user hunting for a conflict that never # one sends the user hunting for a conflict that never
# existed. A rebase that genuinely started and stopped # 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 agents-vault --quiet
for i in (seq (count $argv)) for i in (seq (count $argv))
if test "$argv[$i]" = "-r" if test "$argv[$i]" = -r
set argv[$i] "-c" set argv[$i] -c
else if test "$argv[$i]" = "--resume" else if test "$argv[$i]" = --resume
set argv[$i] "--continue" set argv[$i] --continue
else if string match -q -- "--resume=*" "$argv[$i]" else if string match -q -- "--resume=*" "$argv[$i]"
set argv[$i] (string replace -- "--resume=" "--continue=" "$argv[$i]") set argv[$i] (string replace -- "--resume=" "--continue=" "$argv[$i]")
end end
+15 -6
View File
@@ -25,11 +25,20 @@
function bd-pull --description 'Pull new Gitea issues into local Beads and link them' function bd-pull --description 'Pull new Gitea issues into local Beads and link them'
__fish_help_header (status current-function) $argv; and return 0 __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 argv[1]
if not set -q GITEA_TOKEN; echo "\$GITEA_TOKEN not set"; return 1; end 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] 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 set -l IMPORT_COUNT 0
echo (set_color blue)"📡 Checking Gitea: $REPO..."(set_color normal) echo (set_color blue)"📡 Checking Gitea: $REPO..."(set_color normal)
@@ -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 # This prevents the Gitea Action from creating a duplicate
set -l new_title "[$bid] $title" set -l new_title "[$bid] $title"
curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \ curl -s -X PATCH -H "Authorization: token $GITEA_TOKEN" \
-H "Content-Type: application/json" \ -H "Content-Type: application/json" \
-d "{\"title\":\"$new_title\"}" \ -d "{\"title\":\"$new_title\"}" \
"$GITEA_URL/api/v1/repos/$REPO/issues/$number" > /dev/null "$GITEA_URL/api/v1/repos/$REPO/issues/$number" >/dev/null
set IMPORT_COUNT (math $IMPORT_COUNT + 1) set IMPORT_COUNT (math $IMPORT_COUNT + 1)
end end
+2 -2
View File
@@ -33,6 +33,6 @@ function branch --description 'Switch to or create a git branch'
git checkout $argv git checkout $argv
else else
# If it doesn't exist, create it # If it doesn't exist, create it
git checkout -b $argv git checkout -b $argv
end end
end end
+2 -2
View File
@@ -23,8 +23,8 @@ function cleanup --description 'Log orphans to ~/.removed_orphans and remove the
set -l orphans (pacman -Qtdq) set -l orphans (pacman -Qtdq)
if test -n "$orphans" if test -n "$orphans"
echo "📝 Logging orphans to ~/.removed_orphans..." echo "📝 Logging orphans to ~/.removed_orphans..."
echo "--- Removed on $(date) ---" >> ~/.removed_orphans echo "--- Removed on $(date) ---" >>~/.removed_orphans
pacman -Qi $orphans | grep -E '^(Name|Version)' >> ~/.removed_orphans pacman -Qi $orphans | grep -E '^(Name|Version)' >>~/.removed_orphans
echo "🧹 Removing orphans..." echo "🧹 Removing orphans..."
sudo pacman -Rns $orphans sudo pacman -Rns $orphans
+1 -1
View File
@@ -80,7 +80,7 @@ function config-update --description 'Pull latest fish config from upstream'
end end
# ── compare local HEAD to upstream ─────────────────────────── # ── 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) set -l remote_sha (git -C "$CONFIG_DIR" rev-parse _config_update/main 2>/dev/null)
if test "$local_sha" = "$remote_sha" if test "$local_sha" = "$remote_sha"
+9 -9
View File
@@ -25,15 +25,15 @@ function fish_prompt
set -l last_status $status set -l last_status $status
# Catppuccin Mocha hex palette # Catppuccin Mocha hex palette
set -l c_green '#a6e3a1' set -l c_green '#a6e3a1'
set -l c_red '#f38ba8' set -l c_red '#f38ba8'
set -l c_yellow '#f9e2af' set -l c_yellow '#f9e2af'
set -l c_text '#cdd6f4' set -l c_text '#cdd6f4'
set -l c_blue '#89b4fa' set -l c_blue '#89b4fa'
set -l c_teal '#94e2d5' set -l c_teal '#94e2d5'
set -l c_pink '#f5c2e7' set -l c_pink '#f5c2e7'
set -l c_dim '#6c7086' set -l c_dim '#6c7086'
set -l c_mauve '#cba6f7' set -l c_mauve '#cba6f7'
# Line/connector color tracks last exit status; brackets stay bold green # Line/connector color tracks last exit status; brackets stay bold green
set -l retc $c_green set -l retc $c_green
@@ -129,7 +129,7 @@ function fish_prompt
set_color normal set_color normal
end end
echo # newline echo # newline
# Background jobs (one per line, dim) # Background jobs (one per line, dim)
for job in (jobs -c) for job in (jobs -c)
+5 -6
View File
@@ -135,11 +135,11 @@ function logs --description 'Browse terminal log files interactively with fzf'
set -l togglescript (mktemp) set -l togglescript (mktemp)
set -l deletescript (mktemp) set -l deletescript (mktemp)
set -l helpflag "$tmpfile.help" set -l helpflag "$tmpfile.help"
printf '%s\n' $fzf_lines > $tmpfile 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 '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() # 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' \ 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 chmod +x $togglescript
# Delete confirmation script — avoids nested parens inside execute(), and ensures # Delete confirmation script — avoids nested parens inside execute(), and ensures
# tmpfile is updated before +reload fires (execute is synchronous, execute-silent is not) # 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' \ 'case "$confirm" in [nN]) exit 0 ;; esac' \
"fish -c \"rm \$FILE\"" \ "fish -c \"rm \$FILE\"" \
"grep -vF \"\$FILE\" $tmpfile > $tmpfile.new" \ "grep -vF \"\$FILE\" $tmpfile > $tmpfile.new" \
"mv $tmpfile.new $tmpfile" \ "mv $tmpfile.new $tmpfile" >$deletescript
> $deletescript
chmod +x $deletescript chmod +x $deletescript
set -l selected (command cat $tmpfile | fzf \ 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 # is displayed without ov's default slateblue background, preserving
# the original ANSI colors of the starship prompt. # the original ANSI colors of the starship prompt.
set -l ov_cfg (mktemp --suffix .yaml) 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 \ command ov --config $ov_cfg --view-mode scrollback \
--section-delimiter '\x1b\]133;A' --section-header --section-header-num 1 $file --section-delimiter '\x1b\]133;A' --section-header --section-header-num 1 $file
rm -f $ov_cfg 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 switch $pm
case paru yay case paru yay
$pm -S $to_install $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 case pacman
sudo pacman -S $to_install 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 case apt
sudo apt install -y $to_install 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 case dnf
sudo dnf install -y $to_install 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 case zypper
sudo zypper install -y $to_install 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 case brew
brew install $to_install 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 case pkg
sudo pkg install -y $to_install 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 case yum
sudo yum install -y $to_install 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
end end
@@ -217,28 +241,52 @@ function pkg --description 'Install or remove packages via the system package ma
switch $pm switch $pm
case paru yay case paru yay
$pm -Rns $to_remove $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 case pacman
sudo pacman -Rns $to_remove 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 case apt
sudo apt remove -y $to_remove 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 case dnf
sudo dnf remove -y $to_remove 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 case zypper
sudo zypper remove -y $to_remove 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 case brew
brew uninstall $to_remove 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 case pkg
sudo pkg delete -y $to_remove 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 case yum
sudo yum remove -y $to_remove 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 end
end end
+4 -4
View File
@@ -62,8 +62,8 @@ function rand_string --description 'Generate random, memorable strings from cura
return 0 return 0
end end
set -l sep "dash" set -l sep dash
set -l casing "lower" set -l casing lower
# Locate the words directory relative to this function # Locate the words directory relative to this function
set -l words_dir "" set -l words_dir ""
@@ -114,9 +114,9 @@ function rand_string --description 'Generate random, memorable strings from cura
set -l actual_sep $sep set -l actual_sep $sep
switch $sep switch $sep
case dash case dash
set actual_sep "-" set actual_sep -
case underscore case underscore
set actual_sep "_" set actual_sep _
case dot case dot
set actual_sep "." set actual_sep "."
case none empty case none empty
+1 -1
View File
@@ -17,6 +17,6 @@
# EXAMPLE # EXAMPLE
# rawfish # rawfish
function rawfish --wraps='env NO_TMUX=1 fish' --description 'alias rawfish=env NO_TMUX=1 fish' 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 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 set had_scheme 1
end end
set -l u (string replace -r '^[a-z0-9]+://' '' -- $remote) # strip scheme 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 '^[^@/]+@' '' -- $u) # strip user@
set u (string replace -r '\.git$' '' -- $u) # strip .git set u (string replace -r '\.git$' '' -- $u) # strip .git
if test $had_scheme -eq 0 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 else
set u (string replace -r ':[0-9]+/' '/' -- $u) # url: drop :port set u (string replace -r ':[0-9]+/' '/' -- $u) # url: drop :port
end end
set -l parts (string split -m1 '/' -- $u) set -l parts (string split -m1 '/' -- $u)
+43 -43
View File
@@ -24,53 +24,53 @@
# sbver # sbver
# sbver --brief # sbver --brief
function sbver --description 'Verifies Secure Boot status of EFI binaries using sbctl' 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 if not type -q sbctl
echo "Error: 'sbctl' is not installed." echo "Error: 'sbctl' is not installed."
return 1 return 1
end end
# ANSI color codes (Fish uses set_color for easier management) # ANSI color codes (Fish uses set_color for easier management)
set RED (set_color red) set RED (set_color red)
set GREEN (set_color green) set GREEN (set_color green)
set NC (set_color normal) set NC (set_color normal)
# Flags # Flags
set brief_mode false set brief_mode false
if test "$argv[1]" = "--brief" if test "$argv[1]" = --brief
set brief_mode true set brief_mode true
end end
# Counters # Counters
set pass_count 0 set pass_count 0
set fail_count 0 set fail_count 0
# Run and process sbctl output # Run and process sbctl output
# Fish doesn't use 'done < <()'; we pipe directly into the while loop # 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 sudo sbctl verify 2>&1 | grep -v -i 'invalid pe header' | while read -l line
if string match -q "*✓*" -- "$line" if string match -q "*✓*" -- "$line"
set pass_count (math $pass_count + 1) set pass_count (math $pass_count + 1)
if not $brief_mode if not $brief_mode
echo -e "$GREEN$line$NC" echo -e "$GREEN$line$NC"
end end
else if string match -q "*✗*" -- "$line" else if string match -q "*✗*" -- "$line"
set fail_count (math $fail_count + 1) set fail_count (math $fail_count + 1)
if not $brief_mode if not $brief_mode
echo -e "$RED$line$NC" echo -e "$RED$line$NC"
end end
else else
if not $brief_mode if not $brief_mode
echo "$line" echo "$line"
end end
end end
end end
# Summary # Summary
echo echo
if test $fail_count -eq 0 if test $fail_count -eq 0
echo -e "$GREEN✅ All images are signed ($pass_count verified)$NC" echo -e "$GREEN✅ All images are signed ($pass_count verified)$NC"
else else
echo -e "$RED❌ Some images are not signed ($fail_count failed, $pass_count passed)$NC" echo -e "$RED❌ Some images are not signed ($fail_count failed, $pass_count passed)$NC"
end end
end end
+1 -1
View File
@@ -23,7 +23,7 @@
# spark 1 1 2 5 14 42 # spark 1 1 2 5 14 42
# seq 64 | sort --random-sort | spark # seq 64 | sort --random-sort | spark
# echo "3 7 2 9 1" | 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 argparse --ignore-unknown --name=spark v/version h/help m/min= M/max= -- $argv || return
if set --query _flag_version[1] if set --query _flag_version[1]
+1 -1
View File
@@ -33,7 +33,7 @@
# set -U -a sponge_filters sponge_filter_secrets # set -U -a sponge_filters sponge_filter_secrets
function sponge_filter_secrets --argument-names command function sponge_filter_secrets --argument-names command
# Find all exported variables with security-sensitive names # 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)') '(?i)(?:TOKEN|PASSWORD|PASSWD|SECRET|API[_-]KEY|PRIVATE[_-]KEY|ACCESS[_-]KEY|AUTH[_-]KEY|CREDENTIAL|KOPIA_PASSWORD)')
for var in $sensitive_vars for var in $sensitive_vars
+1 -1
View File
@@ -32,7 +32,7 @@ function sudo-toggle --description 'Toggle sudo password requirement on/off'
else else
# 2. Toggle is currently ON (Security active). We want to BYPASS it. # 2. Toggle is currently ON (Security active). We want to BYPASS it.
# We write a high-priority user-specific NOPASSWD rule. # 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)" echo "🔓 Sudo security: DISABLED (Bypass active)"
end end
end end
+1 -1
View File
@@ -62,7 +62,7 @@ function yt-dlp --description 'yt-dlp with embedding + SponsorBlock defaults'
# --sponsorblock-remove all # --sponsorblock-remove all
# Skip if the user set their own remove (bare or --opt=value form), or # Skip if the user set their own remove (bare or --opt=value form), or
# disabled SponsorBlock entirely with --no-sponsorblock. # 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 string match -q -- '--sponsorblock-remove=*' $argv
and not contains -- --no-sponsorblock $argv and not contains -- --no-sponsorblock $argv
set -a extra --sponsorblock-remove all set -a extra --sponsorblock-remove all
+441 -437
View File
@@ -13,7 +13,6 @@
# - $FZF_ALT_C_COMMAND # - $FZF_ALT_C_COMMAND
# - $FZF_ALT_C_OPTS # - $FZF_ALT_C_OPTS
# Key bindings # Key bindings
# ------------ # ------------
# The oldest supported fish version is 3.1b1. To maintain compatibility, the # 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. # check, otherwise the source command will fail on fish versions older than 3.4.0.
function fzf_key_bindings function fzf_key_bindings
# Check fish version # Check fish version
if set -l -- fish_ver (string match -r '^(\d+)\.(\d+)' $version 2>/dev/null) 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 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 echo "This script requires fish version 3.1b1 or newer." >&2
return 1 return 1
else if not type -q fzf else if not type -q fzf
echo "fzf was not found in path." >&2 echo "fzf was not found in path." >&2
return 1 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)
end end
set -l -- var_count 0 # bg-transform runs the preview toggle in a background thread (non-blocking).
for i in $tokens # It was added after fzf 0.60; fall back to the synchronous transform on older builds.
if string match -qr -- '^[\w]+=' $i set -l _fzf_ver (fzf --version | string match -r '^(\d+)\.(\d+)')
set var_count (math $var_count + 1) set -l _fzf_transform_action transform
else if test -n "$_fzf_ver[3]"; and test "$_fzf_ver[3]" -ge 62
break set _fzf_transform_action bg-transform
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 end
string escape -n -- $tokens #----BEGIN INCLUDE common.fish
end # 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' function __fzf_defaults
set -l fzf_query '' test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
set -l prefix '' string join ' ' -- \
set -l dir '.' "--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) \
set -l -- fish_major (string match -r -- '^\d+' $version) $FZF_DEFAULT_OPTS $argv[2..-1]
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 end
if test "$fish_major" -ge 4 function __fzfcmd
string match -q -r -- $match_regex (commandline --current-token --tokens-expanded | string collect -N) test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2 if test -n "$FZF_TMUX_OPTS"
string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N) echo "fzf-tmux $FZF_TMUX_OPTS -- "
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '') else if test "$FZF_TMUX" = 1
else echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
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)
else else
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N) echo fzf
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '') 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 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 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 else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \ string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
(string replace -- "$dir" '' $fzf_query | string collect -N) eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
else else
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N) set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\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
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
#----END INCLUDE
string escape -n -- "$dir" "$fzf_query" "$prefix" # Store current token in $dir as root for the 'find' command
end function fzf-file-widget -d "List files and folders"
#----END INCLUDE 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 set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
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 \
"--reverse --walker=file,dir,follow,hidden --scheme=path" \ "--reverse --walker=file,dir,follow,hidden --scheme=path" \
"--multi $FZF_CTRL_T_OPTS --print0") "--multi $FZF_CTRL_T_OPTS --print0")
set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND" set -lx FZF_DEFAULT_COMMAND "$FZF_CTRL_T_COMMAND"
set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_OPTS_FILE
set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0) set -l result (eval (__fzfcmd) --walker-root=$dir --query=$fzf_query | string split0)
and commandline -rt -- (string join -- ' ' $prefix(string escape --no-quoted -- $result))' ' and commandline -rt -- (string join -- ' ' $prefix(string escape --no-quoted -- $result))' '
commandline -f repaint 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
end 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 \ '--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=\'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="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" \ "--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) '--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 # Add dynamic preview options if preview command isn't already set by user
if string match -qvr -- '--preview[= ]' "$FZF_DEFAULT_OPTS" if string match -qvr -- '--preview[= ]' "$FZF_DEFAULT_OPTS"
# Convert the highlighted timestamp using the date command if available # Convert the highlighted timestamp using the date command if available
set -l -- date_cmd '{1}' set -l -- date_cmd '{1}'
if type -q date if type -q date
if date -d @0 '+%s' 2>/dev/null | string match -q 0 if date -d @0 '+%s' 2>/dev/null | string match -q 0
# GNU date # GNU date
set -- date_cmd '(date -d @{1} \\"+%F %a %T\\")' set -- date_cmd '(date -d @{1} \\"+%F %a %T\\")'
else if date -r 0 '+%s' 2>/dev/null | string match -q 0 else if date -r 0 '+%s' 2>/dev/null | string match -q 0
# BSD date # BSD date
set -- date_cmd '(date -r {1} \\"+%F %a %T\\")' 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
end
# Prepend the options to allow user customizations set -lx FZF_DEFAULT_OPTS_FILE
if test "$_fzf_transform_action" = bg-transform
# Newer fzf: dynamic show/hide preview based on terminal width set -lx -- FZF_DEFAULT_COMMAND 'builtin history -z --show-time="%s%t"'
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"' \ # Enable syntax highlighting colors on fish v4.3.3 and newer
'--preview="string collect -- (test \\"$FZF_SELECT_COUNT\\" -gt 0; and string collect -- {+2..}) \\"\\n# \\"'$date_cmd' {2..} | fish_indent --ansi"' \ if set -l -- v (string match -r -- '^(\d+)\.(\d+)(?:\.(\d+))?' $version)
'--preview-window="right,50%,wrap-word,follow,info,hidden"' and test "$v[2]" -gt 4 -o "$v[2]" -eq 4 -a \
else \( "$v[3]" -gt 3 -o "$v[3]" -eq 3 -a \
# Older fzf: static preview; focus/resize events not reliably supported \( -n "$v[4]" -a "$v[4]" -ge 3 \) \)
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"' \ set -a -- FZF_DEFAULT_OPTS --ansi
'--preview-window="right,50%,wrap,follow,info"' set -a -- FZF_DEFAULT_COMMAND '--color=always'
end 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 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"' set -lx FZF_DEFAULT_OPTS (__fzf_defaults \
# 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 \
"--reverse --walker=dir,follow,hidden --scheme=path" \ "--reverse --walker=dir,follow,hidden --scheme=path" \
"$FZF_ALT_C_OPTS --no-multi --print0") "$FZF_ALT_C_OPTS --no-multi --print0")
set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND" set -lx FZF_DEFAULT_COMMAND "$FZF_ALT_C_COMMAND"
if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0) if set -l result (eval (__fzfcmd) --query=$fzf_query --walker-root=$dir | string split0)
cd -- $result cd -- $result
commandline -rt -- $prefix commandline -rt -- $prefix
end
commandline -f repaint
end end
commandline -f repaint if not set -q FZF_CTRL_R_COMMAND; or test -n "$FZF_CTRL_R_COMMAND"
end 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" end
if test -n "$FZF_CTRL_R_COMMAND" bind \cr fzf-history-widget
echo "warning: FZF_CTRL_R_COMMAND is set to a custom command, but custom commands are not yet supported for CTRL-R" >&2 bind -M insert \cr fzf-history-widget
end 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" if not set -q FZF_CTRL_T_COMMAND; or test -n "$FZF_CTRL_T_COMMAND"
bind \ct fzf-file-widget bind \ct fzf-file-widget
bind -M insert \ct fzf-file-widget bind -M insert \ct fzf-file-widget
end end
if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND" if not set -q FZF_ALT_C_COMMAND; or test -n "$FZF_ALT_C_COMMAND"
bind \ec fzf-cd-widget bind \ec fzf-cd-widget
bind -M insert \ec fzf-cd-widget bind -M insert \ec fzf-cd-widget
end end
end end
@@ -321,233 +320,238 @@ fzf_key_bindings
function fzf_completion_setup function fzf_completion_setup
#----BEGIN INCLUDE common.fish #----BEGIN INCLUDE common.fish
# NOTE: Do not directly edit this section, which is copied from "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 # 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. # the changes. See code comments in "common.fish" for the implementation details.
function __fzf_defaults function __fzf_defaults
test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40% test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
string join ' ' -- \ string join ' ' -- \
"--height $FZF_TMUX_HEIGHT --min-height=20+ --bind=ctrl-z:ignore" $argv[1] \ "--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) \ (test -r "$FZF_DEFAULT_OPTS_FILE"; and string join -- ' ' <$FZF_DEFAULT_OPTS_FILE) \
$FZF_DEFAULT_OPTS $argv[2..-1] $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)
end end
set -l -- var_count 0 function __fzfcmd
for i in $tokens test -n "$FZF_TMUX_HEIGHT"; or set -l FZF_TMUX_HEIGHT 40%
if string match -qr -- '^[\w]+=' $i if test -n "$FZF_TMUX_OPTS"
set var_count (math $var_count + 1) echo "fzf-tmux $FZF_TMUX_OPTS -- "
else else if test "$FZF_TMUX" = 1
break echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
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)
else else
set -- fzf_query (string replace -r -a -- '(?<=/)/|(?<!^)/+(?!\n)$' '' $fzf_query | string collect -N) echo fzf
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r '\\\n$' '') 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 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 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 else if test "$fish_major" -eq 3 -a "$fish_minor" -ge 2
string match -q -r -- '^/?(?<fzf_query>[\s\S]*?(?=\n?$)$)' \ string match -q -r -- $match_regex (commandline --current-token --tokenize | string collect -N)
(string replace -- "$dir" '' $fzf_query | string collect -N) eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^\\\(?=~)|\\\(?=\$\w)' '')
else else
set -- fzf_query (string replace -- "$dir" '' $fzf_query | string collect -N) set -l -- cl_token (commandline --current-token --tokenize | string collect -N)
eval set -- fzf_query (string escape -n -- $fzf_query | string replace -r -a '^/?|\\\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
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
#----END INCLUDE
string escape -n -- "$dir" "$fzf_query" "$prefix" # Use complete builtin for specific commands
end function __fzf_complete_native
#----END INCLUDE 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 # Calculate tabstop based on longest completion item (sample first 500 for performance)
function __fzf_complete_native set -l -- tabstop 20
set -l -- token (commandline -t) set -l -- sample_size (math "min(500, "(count $completions)")")
set -l -- completions (eval complete -C \"$argv[1]\") for c in $completions[1..$sample_size]
test -n "$completions"; or begin commandline -f repaint; return; end 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 result
set -l -- tabstop 20 set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults \
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 \
"--reverse --delimiter=\\t --nth=1 --tabstop=$tabstop --color=fg:dim,nth:regular" \ "--reverse --delimiter=\\t --nth=1 --tabstop=$tabstop --color=fg:dim,nth:regular" \
$FZF_COMPLETION_OPTS $argv[2..-1] --accept-nth=1 --read0 --print0) $FZF_COMPLETION_OPTS $argv[2..-1] --accept-nth=1 --read0 --print0)
set -- result (string join0 -- $completions | eval (__fzfcmd) | string split0) set -- result (string join0 -- $completions | eval (__fzfcmd) | string split0)
and begin and begin
set -l -- tail ' ' set -l -- tail ' '
# Append / to bare ~username results (fish omits it unlike other shells) # Append / to bare ~username results (fish omits it unlike other shells)
set -- result (string replace -r -- '^(~\w+)\s?$' '$1/' $result) set -- result (string replace -r -- '^(~\w+)\s?$' '$1/' $result)
# Don't add trailing space if single result is a directory # Don't add trailing space if single result is a directory
test (count $result) -eq 1 test (count $result) -eq 1
and string match -q -- '*/' "$result"; and set -- tail '' 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" string match -q -- '~*' "$token"
and set result (string replace -r -- '^\\\\~' '~' $result) and set result (string replace -r -- '^\\\\~' '~' $result)
string match -q -- '$*' "$token" string match -q -- '$*' "$token"
and set result (string replace -r -- '^\\\\\$' '\$' $result) and set result (string replace -r -- '^\\\\\$' '\$' $result)
commandline -rt -- (string join ' ' -- $result)$tail commandline -rt -- (string join ' ' -- $result)$tail
end
commandline -f repaint
end end
commandline -f repaint
end
function _fzf_complete function _fzf_complete
set -l -- args (string escape -- $argv | string join ' ' | string split -- ' -- ') set -l -- args (string escape -- $argv | string join ' ' | string split -- ' -- ')
set -l -- post_func (status function)_(string split -- ' ' $args[2])[1]_post 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 (__fzf_defaults --reverse $FZF_COMPLETION_OPTS $args[1])
set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_OPTS_FILE
set -lx FZF_DEFAULT_COMMAND set -lx FZF_DEFAULT_COMMAND
set -l -- fzf_query (commandline -t | string escape) set -l -- fzf_query (commandline -t | string escape)
set -l result set -l result
eval (__fzfcmd) --query=$fzf_query | while read -l r; set -a -- result $r; end eval (__fzfcmd) --query=$fzf_query | while read -l r
and if functions -q $post_func set -a -- result $r
commandline -rt -- (string collect -- $result | eval $post_func $args[2] | string join ' ')' ' end
else and if functions -q $post_func
commandline -rt -- (string join -- ' ' (string escape -- $result))' ' 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 end
commandline -f repaint
end
# Kill completion (process selection) # Kill completion (process selection)
function _fzf_complete_kill function _fzf_complete_kill
set -l -- fzf_query (commandline -t | string escape) set -l -- fzf_query (commandline -t | string escape)
set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS \ set -lx -- FZF_DEFAULT_OPTS (__fzf_defaults --reverse $FZF_COMPLETION_OPTS \
--accept-nth=2 -m --header-lines=1 --no-preview --wrap) --accept-nth=2 -m --header-lines=1 --no-preview --wrap)
set -lx FZF_DEFAULT_OPTS_FILE set -lx FZF_DEFAULT_OPTS_FILE
if type -q ps if type -q ps
set -l -- ps_cmd 'begin command ps -eo user,pid,ppid,start,time,command 2>/dev/null;' \ 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 -eo user,pid,ppid,time,args 2>/dev/null;' \
'or command ps --everyone --full --windows 2>/dev/null; end' 'or command ps --everyone --full --windows 2>/dev/null; end'
set -l -- result (eval $ps_cmd \| (__fzfcmd) --query=$fzf_query) set -l -- result (eval $ps_cmd \| (__fzfcmd) --query=$fzf_query)
and commandline -rt -- (string join ' ' -- $result)" " and commandline -rt -- (string join ' ' -- $result)" "
else else
__fzf_complete_native "kill " --multi --query=$fzf_query __fzf_complete_native "kill " --multi --query=$fzf_query
end
commandline -f repaint
end end
commandline -f repaint
end
# Main completion function # Main completion function
function fzf-completion function fzf-completion
set -l -- tokens (__fzf_cmd_tokens) set -l -- tokens (__fzf_cmd_tokens)
set -l -- current_token (commandline -t) set -l -- current_token (commandline -t)
set -l -- cmd_name $tokens[1] set -l -- cmd_name $tokens[1]
# Route to appropriate completion function # Route to appropriate completion function
if test -n "$tokens"; and functions -q _fzf_complete_$cmd_name if test -n "$tokens"; and functions -q _fzf_complete_$cmd_name
_fzf_complete_$cmd_name $tokens _fzf_complete_$cmd_name $tokens
else else
set -l -- fzf_opt --query=$current_token --multi set -l -- fzf_opt --query=$current_token --multi
__fzf_complete_native "$tokens $current_token" $fzf_opt __fzf_complete_native "$tokens $current_token" $fzf_opt
end
end end
end
# Bind Shift-Tab to fzf-completion (Tab retains native Fish behavior) # Bind Shift-Tab to fzf-completion (Tab retains native Fish behavior)
if test (string match -r -- '^\d+' $version) -ge 4 if test (string match -r -- '^\d+' $version) -ge 4
bind shift-tab fzf-completion bind shift-tab fzf-completion
bind -M insert shift-tab fzf-completion bind -M insert shift-tab fzf-completion
else else
bind -k btab fzf-completion bind -k btab fzf-completion
bind -M insert -k btab fzf-completion bind -M insert -k btab fzf-completion
end end
end end
# Run setup # Run setup
+9 -6
View File
@@ -24,9 +24,12 @@ set -l mode bytes
set -l baseline main set -l baseline main
for i in (seq (count $argv)) for i in (seq (count $argv))
switch $argv[$i] switch $argv[$i]
case --structural; set mode structural case --structural
case --startup; set mode startup set mode structural
case --baseline; set baseline $argv[(math $i + 1)] case --startup
set mode startup
case --baseline
set baseline $argv[(math $i + 1)]
end end
end end
@@ -36,8 +39,8 @@ function __pb_cleanup --on-event fish_exit --inherit-variable tmp
end end
# ── Build the two sandboxes ──────────────────────────────────────────── # ── Build the two sandboxes ────────────────────────────────────────────
set -l A $tmp/base/fish # pristine baseline ref set -l A $tmp/base/fish # pristine baseline ref
set -l B $tmp/work/fish # current working tree set -l B $tmp/work/fish # current working tree
mkdir -p $A $B mkdir -p $A $B
git -C $repo archive $baseline | tar -x -C $A git -C $repo archive $baseline | tar -x -C $A
or begin or begin
@@ -71,7 +74,7 @@ set -l cases \
"p --help" "pkg --help" "play-media --help" "qc --help" \ "p --help" "pkg --help" "play-media --help" "qc --help" \
"rand_string --help" "replay --help" "repo-open --help" "scrub --help" \ "rand_string --help" "replay --help" "repo-open --help" "scrub --help" \
"smart_exit --help" "spark --help" "y --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__" "agents-init --no-such-flag" "pkg __no_such_subcommand__"
function __pb_run --argument-names cfg stub cmd out 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 # SPDX-License-Identifier: AGPL-3.0-or-later
# #
# CI test runner for this fish configuration. # 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 # 2. Discovers tests/test-*.fish and reads the mode each suite declares
# in its own header (`# MODE: isolated` or `# MODE: in-session`). # in its own header (`# MODE: isolated` or `# MODE: in-session`).
# 3. Runs each isolated suite as its own --no-config fish process with # 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 repo_root (realpath $script_dir/..)
set -l overall_failed 0 set -l overall_failed 0
# ---- Phase 1: syntax lint ------------------------------------------------ # ---- Phase 1: syntax & indent lint ---------------------------------------
echo "== Syntax lint ==" echo "== Syntax & indent lint =="
set -l lint_files $repo_root/config.fish 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) set -a lint_files (find $repo_root/$dir -name '*.fish' | sort)
end end
set -l lint_failed 0 set -l syntax_failed 0
set -l indent_failed 0
for f in $lint_files for f in $lint_files
set -l out (fish -n $f 2>&1) set -l out (fish -n $f 2>&1)
if test $status -ne 0 if test $status -ne 0
echo " FAIL "(string replace $repo_root/ '' $f) echo " FAIL (syntax) "(string replace $repo_root/ '' $f)
printf '%s\n' $out 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
end end
set -l lint_total (count $lint_files) set -l lint_total (count $lint_files)
echo (math $lint_total - $lint_failed)"/$lint_total files passed lint" echo (math $lint_total - $syntax_failed)"/$lint_total files passed syntax check"
if test $lint_failed -ne 0 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 set overall_failed 1
end end
@@ -160,8 +167,7 @@ if test (count $session_suites) -gt 0
__fish_config_op_autoexec=off \ __fish_config_op_autoexec=off \
FISH_CONFIG_TEST_ROOT=$repo_root \ FISH_CONFIG_TEST_ROOT=$repo_root \
FISH_CONFIG_TEST_COUNTS=$counts \ FISH_CONFIG_TEST_COUNTS=$counts \
fish -i -c "source $repo_root/tests/lib.fish; $srcs report" \ fish -i -c "source $repo_root/tests/lib.fish; $srcs report" 2>$err_file
2>$err_file
set -l session_status $status set -l session_status $status
set -l stderr_out (command cat $err_file) 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' \ '#!/bin/sh' \
'for a in "$@"; do' \ 'for a in "$@"; do' \
" case \"\$a\" in *$match*) exit 1 ;; esac" \ " case \"\$a\" in *$match*) exit 1 ;; esac" \
'done' \ done \
"exec $real \"\$@\"" >$d/$name "exec $real \"\$@\"" >$d/$name
chmod +x $d/$name chmod +x $d/$name
printf '%s\n' $d 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) check "falls back to first remote" $want (_agents_repo_slug $r3)
# Slug sanitization test: special chars in fallback (local-) branch. # 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!" mkdir -p "$dirt/projects/My Project!"
git -C "$dirt/projects/My Project!" init -q git -C "$dirt/projects/My Project!" init -q
git -C "$dirt/projects/My Project!" config user.email t@t 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 ""
echo "== _agents_repo_ensure_symlink ==" 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 mkdir -p $w/target $w/live
# Fresh link onto an empty live parent. # 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) 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. # 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 mkdir -p $a/vault $a/live/memory
echo vault-version >$a/vault/shared.md echo vault-version >$a/vault/shared.md
echo vault-only >$a/vault/vaultonly.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) 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. # 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 mkdir -p $p/one $p/two
ln -s $p/one $p/link ln -s $p/one $p/link
_agents_repo_ensure_symlink $p/link $p/two >/dev/null _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 # 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 # the local commit down with it. The divergence is still built here so
# that guarantee is tested against the case that used to fail. # 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 $origin init -q --bare
git -C $s remote add origin $origin git -C $s remote add origin $origin
git -C $s push -q -u origin HEAD:refs/heads/main 2>/dev/null 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 clone -q $origin $clone
git -C $clone config user.email t@t git -C $clone config user.email t@t
git -C $clone config user.name t git -C $clone config user.name t
@@ -297,7 +303,8 @@ set -l h (new_repo)
echo first >$h/a.md echo first >$h/a.md
_agents_repo_sync $h "chore: init" >/dev/null _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 printf '#!/bin/sh\nexit 1\n' >$hooks/pre-commit
chmod +x $hooks/pre-commit chmod +x $hooks/pre-commit
git -C $h config core.hooksPath $hooks 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 ""
echo "== agents-vault (scaffold + link) ==" echo "== agents-vault (scaffold + link) =="
set -l vroot (mktemp -d); set -ga TMPDIRS $vroot set -l vroot (mktemp -d)
set -l croot (mktemp -d); set -ga TMPDIRS $croot 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_dir $vroot/agent-vault
set -g __fish_agent_vault_claude_root $croot set -g __fish_agent_vault_claude_root $croot
@@ -359,8 +368,10 @@ set -e __fish_agent_vault_claude_root
echo "" echo ""
echo "== agents-vault (emergent restore) ==" echo "== agents-vault (emergent restore) =="
set -l vroot2 (mktemp -d); set -ga TMPDIRS $vroot2 set -l vroot2 (mktemp -d)
set -l croot2 (mktemp -d); set -ga TMPDIRS $croot2 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_dir $vroot2/agent-vault
set -g __fish_agent_vault_claude_root $croot2 set -g __fish_agent_vault_claude_root $croot2
@@ -393,8 +404,10 @@ set -e __fish_agent_vault_claude_root
echo "" echo ""
echo "== agents-vault (link failure surfaces as exit 1) ==" echo "== agents-vault (link failure surfaces as exit 1) =="
set -l vroot3 (mktemp -d); set -ga TMPDIRS $vroot3 set -l vroot3 (mktemp -d)
set -l croot3 (mktemp -d); set -ga TMPDIRS $croot3 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_dir $vroot3/agent-vault
set -g __fish_agent_vault_claude_root $croot3 set -g __fish_agent_vault_claude_root $croot3
@@ -420,8 +433,10 @@ set -e __fish_agent_vault_claude_root
echo "" echo ""
echo "== agents-vault (slug migration) ==" echo "== agents-vault (slug migration) =="
set -l vroot2 (mktemp -d); set -ga TMPDIRS $vroot2 set -l vroot2 (mktemp -d)
set -l croot2 (mktemp -d); set -ga TMPDIRS $croot2 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_dir $vroot2/agent-vault
set -g __fish_agent_vault_claude_root $croot2 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 mp (new_repo)
set -l mmangled (string replace -a '/' '-' -- $mp | string replace -a '.' '-') set -l mmangled (string replace -a '/' '-' -- $mp | string replace -a '.' '-')
mkdir -p $croot2/$mmangled/memory mkdir -p $croot2/$mmangled/memory
echo "precious" >$croot2/$mmangled/memory/keep.md echo precious >$croot2/$mmangled/memory/keep.md
pushd $mp >/dev/null pushd $mp >/dev/null
agents-vault --silent agents-vault --silent
@@ -495,8 +510,7 @@ set -l enew_slug git.rootiest.dev-rootiest-emptycase
# hand-built shape is covered separately just below. # hand-built shape is covered separately just below.
mkdir -p $vroot2/agent-vault/projects/$enew_slug mkdir -p $vroot2/agent-vault/projects/$enew_slug
printf 'remote: %s\npath: %s\nhost: %s\n' \ printf 'remote: %s\npath: %s\nhost: %s\n' \
https://git.rootiest.dev/rootiest/emptycase.git /gone/elsewhere othermachine \ https://git.rootiest.dev/rootiest/emptycase.git /gone/elsewhere othermachine >$vroot2/agent-vault/projects/$enew_slug/origin
>$vroot2/agent-vault/projects/$enew_slug/origin
pushd $emp >/dev/null pushd $emp >/dev/null
set -l erc (agents-vault --silent 2>/dev/null; echo $status) 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 # 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 # 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. # 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!" set -l dp "$dirty_root/My Project!"
mkdir -p "$dp" mkdir -p "$dp"
git -C "$dp" init -q 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 '.' '-') set -l dmangled (string replace -a '/' '-' -- $dp | string replace -a '.' '-')
mkdir -p $croot2/$dmangled/memory 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 pushd $dp >/dev/null
agents-vault --silent 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 # 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 # printed "rm: cannot remove ...: Is a directory", which is the only
# thing the user sees and reads as a failure. # 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" set -l rp "$real_root/proj"
mkdir -p "$rp" mkdir -p "$rp"
git -C "$rp" init -q 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 '.' '-') set -l rmangled (string replace -a '/' '-' -- $rp | string replace -a '.' '-')
mkdir -p $croot2/$rmangled/memory mkdir -p $croot2/$rmangled/memory
echo "banked" >$croot2/$rmangled/memory/old.md echo banked >$croot2/$rmangled/memory/old.md
pushd $rp >/dev/null pushd $rp >/dev/null
agents-vault --silent agents-vault --silent
@@ -643,10 +659,11 @@ popd >/dev/null
# never seen, then change the slug so the migration runs. # never seen, then change the slug so the migration runs.
rm -f $croot2/$rmangled/memory rm -f $croot2/$rmangled/memory
mkdir -p $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 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 pushd $rp >/dev/null
set -l real_rc (agents-vault --silent 2>$rerr; echo $status) set -l real_rc (agents-vault --silent 2>$rerr; echo $status)
popd >/dev/null popd >/dev/null
@@ -673,8 +690,10 @@ echo ""
echo "== agents-vault (a real git clone) ==" echo "== agents-vault (a real git clone) =="
# Machine A: populate a vault and let agents-vault commit it. # 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_vroot (mktemp -d)
set -l cl_croot (mktemp -d); set -ga TMPDIRS $cl_croot 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_dir $cl_vroot/agent-vault
set -g __fish_agent_vault_claude_root $cl_croot set -g __fish_agent_vault_claude_root $cl_croot
@@ -698,7 +717,8 @@ agents-vault --silent
popd >/dev/null popd >/dev/null
# The clone, exactly as the README tells a user to make it. # 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 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.email t@t
git -C $cl_new/agent-vault config user.name 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) 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. # 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_dir $cl_new/agent-vault
set -g __fish_agent_vault_claude_root $cl_croot2 set -g __fish_agent_vault_claude_root $cl_croot2
@@ -762,10 +783,14 @@ set -e __fish_agent_vault_claude_root
echo "" echo ""
echo "== agents-vault (global state) ==" echo "== agents-vault (global state) =="
set -l vroot5 (mktemp -d); set -ga TMPDIRS $vroot5 set -l vroot5 (mktemp -d)
set -l croot5 (mktemp -d); set -ga TMPDIRS $croot5 set -ga TMPDIRS $vroot5
set -l chome5 (mktemp -d); set -ga TMPDIRS $chome5 set -l croot5 (mktemp -d)
set -l agy5 (mktemp -d); set -ga TMPDIRS $agy5 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_dir $vroot5/agent-vault
set -g __fish_agent_vault_claude_root $croot5 set -g __fish_agent_vault_claude_root $croot5
set -g __fish_agent_vault_claude_home $chome5 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 # 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 # home: a directory the store has no business reaching into, planted with
# exactly the shapes that qualify. # 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 mkdir -p $outside5/nested
echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/leaked.json echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/leaked.json
echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/target.md echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/target.md
@@ -905,10 +931,14 @@ set -e __fish_agent_vault_claude_root
echo "" echo ""
echo "== agents-vault (global emergent restore) ==" echo "== agents-vault (global emergent restore) =="
set -l vroot6 (mktemp -d); set -ga TMPDIRS $vroot6 set -l vroot6 (mktemp -d)
set -l croot6 (mktemp -d); set -ga TMPDIRS $croot6 set -ga TMPDIRS $vroot6
set -l chome6 (mktemp -d); set -ga TMPDIRS $chome6 set -l croot6 (mktemp -d)
set -l agy6 (mktemp -d); set -ga TMPDIRS $agy6 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_dir $vroot6/agent-vault
set -g __fish_agent_vault_claude_root $croot6 set -g __fish_agent_vault_claude_root $croot6
set -g __fish_agent_vault_claude_home $chome6 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 # A home with neither side populated must not have a memory/ invented for
# it: ~/.claude/memory does not exist by default. # it: ~/.claude/memory does not exist by default.
set -l chome7 (mktemp -d); set -ga TMPDIRS $chome7 set -l chome7 (mktemp -d)
set -l vroot7 (mktemp -d); set -ga TMPDIRS $vroot7 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_dir $vroot7/agent-vault
set -g __fish_agent_vault_claude_home $chome7 set -g __fish_agent_vault_claude_home $chome7
set -l gp7 (new_repo https://git.rootiest.dev/rootiest/globals-absent.git) 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 ""
echo "== agents-vault (global link failure is non-fatal) ==" echo "== agents-vault (global link failure is non-fatal) =="
set -l vroot8 (mktemp -d); set -ga TMPDIRS $vroot8 set -l vroot8 (mktemp -d)
set -l croot8 (mktemp -d); set -ga TMPDIRS $croot8 set -ga TMPDIRS $vroot8
set -l chome8 (mktemp -d); set -ga TMPDIRS $chome8 set -l croot8 (mktemp -d)
set -l agy8 (mktemp -d); set -ga TMPDIRS $agy8 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_dir $vroot8/agent-vault
set -g __fish_agent_vault_claude_root $croot8 set -g __fish_agent_vault_claude_root $croot8
set -g __fish_agent_vault_claude_home $chome8 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 mkdir -p $croot8/$fmangled/memory
echo project-memory >$croot8/$fmangled/memory/p.md 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 pushd $fp >/dev/null
set -l frc (agents-vault --silent 2>$ferr; echo $status) set -l frc (agents-vault --silent 2>$ferr; echo $status)
popd >/dev/null 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 # 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. # re-enter the block and warn again, not treat the vault as correct.
pushd $fp >/dev/null 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 agents-vault --silent 2>$ferr2
popd >/dev/null 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) 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 ""
echo "== agents-vault (status, remote, adopt) ==" echo "== agents-vault (status, remote, adopt) =="
set -l vroot9 (mktemp -d); set -ga TMPDIRS $vroot9 set -l vroot9 (mktemp -d)
set -l croot9 (mktemp -d); set -ga TMPDIRS $croot9 set -ga TMPDIRS $vroot9
set -l chome9 (mktemp -d); set -ga TMPDIRS $chome9 set -l croot9 (mktemp -d)
set -l agy9 (mktemp -d); set -ga TMPDIRS $agy9 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_dir $vroot9/agent-vault
set -g __fish_agent_vault_claude_root $croot9 set -g __fish_agent_vault_claude_root $croot9
set -g __fish_agent_vault_claude_home $chome9 set -g __fish_agent_vault_claude_home $chome9
set -g __fish_agent_vault_agy_root $agy9 set -g __fish_agent_vault_agy_root $agy9
# A report asked for before the vault exists must say so, not scaffold one. # 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) set -l s0rc (agents-vault --status >$s0out; echo $status)
check "status without a vault exits 0" 0 "$s0rc" 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) 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 # 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 # git command that did not run is the same silent-false-success shape that
# a hook-rejected commit produced earlier in this project. # 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 rshim (failing_shim git set-url)
set -l rpath $PATH set -l rpath $PATH
set PATH $rshim $PATH set PATH $rshim $PATH
@@ -1106,10 +1150,11 @@ agents-vault --silent
popd >/dev/null popd >/dev/null
set -l projects_before (command ls -A $vroot9/agent-vault/projects | sort | string join ',') 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 pushd $bp >/dev/null
for bad in $bad_slugs 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) set -l brc (agents-vault --adopt=$bad --silent 2>$berr; echo $status)
check "adopt refuses '$bad'" 1 "$brc" 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) 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_porcelain (git -C $vroot9/agent-vault status --porcelain | string join ',')
set -l pre_link (path resolve $croot9/$tmang/memory) 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 tshim (failing_shim rm $tmang)
set -l tpath $PATH set -l tpath $PATH
set PATH $tshim $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 # 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. # git knows about out from under the index, which is the whole hazard.
mkdir -p $vroot9/agent-vault/projects/stash-target/claude/memory mkdir -p $vroot9/agent-vault/projects/stash-target/claude/memory
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere \ printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere >$vroot9/agent-vault/projects/stash-target/origin
>$vroot9/agent-vault/projects/stash-target/origin
pushd $sa >/dev/null pushd $sa >/dev/null
agents-vault --silent agents-vault --silent
popd >/dev/null popd >/dev/null
@@ -1245,8 +1290,7 @@ agents-vault --silent
popd >/dev/null popd >/dev/null
mkdir -p $vroot9/agent-vault/projects/stashfail-target/claude/memory mkdir -p $vroot9/agent-vault/projects/stashfail-target/claude/memory
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere-else \ printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere-else >$vroot9/agent-vault/projects/stashfail-target/origin
>$vroot9/agent-vault/projects/stashfail-target/origin
pushd $sf >/dev/null pushd $sf >/dev/null
agents-vault --silent agents-vault --silent
popd >/dev/null 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) set -l sf_link (path resolve $croot9/$sfmang/memory)
check "stash adopt rollback: vault clean before the adopt" "" "$sf_porcelain" 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 sfshim (failing_shim rm $sfmang)
set -l sfpath $PATH set -l sfpath $PATH
set PATH $sfshim $PATH set PATH $sfshim $PATH
@@ -1359,10 +1404,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo "" echo ""
echo "== agents-vault (restore) ==" echo "== agents-vault (restore) =="
set -l vroot10 (mktemp -d); set -ga TMPDIRS $vroot10 set -l vroot10 (mktemp -d)
set -l croot10 (mktemp -d); set -ga TMPDIRS $croot10 set -ga TMPDIRS $vroot10
set -l chome10 (mktemp -d); set -ga TMPDIRS $chome10 set -l croot10 (mktemp -d)
set -l agy10 (mktemp -d); set -ga TMPDIRS $agy10 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_dir $vroot10/agent-vault
set -g __fish_agent_vault_claude_root $croot10 set -g __fish_agent_vault_claude_root $croot10
set -g __fish_agent_vault_claude_home $chome10 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. # succeeds and says which entry needs --adopt.
mkdir -p $vroot10/agent-vault/projects/ghost-entry/claude/memory mkdir -p $vroot10/agent-vault/projects/ghost-entry/claude/memory
echo x >$vroot10/agent-vault/projects/ghost-entry/claude/memory/x.md echo x >$vroot10/agent-vault/projects/ghost-entry/claude/memory/x.md
printf 'remote: (none)\npath: %s\nhost: t\n' $vroot10/gone-forever \ printf 'remote: (none)\npath: %s\nhost: t\n' $vroot10/gone-forever >$vroot10/agent-vault/projects/ghost-entry/origin
>$vroot10/agent-vault/projects/ghost-entry/origin set -l r2out (mktemp)
set -l r2out (mktemp); set -ga TMPDIRS $r2out set -ga TMPDIRS $r2out
set -l r2rc (agents-vault --restore >$r2out; echo $status) set -l r2rc (agents-vault --restore >$r2out; echo $status)
check "restore: exits 0 with an unplaceable entry" 0 "$r2rc" 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) 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 '.' '-') set -l dotmang (string replace -a '/' '-' -- $dotp | string replace -a '.' '-')
mkdir -p $vroot10/agent-vault/projects/.dot-entry/claude/memory mkdir -p $vroot10/agent-vault/projects/.dot-entry/claude/memory
echo dot-precious >$vroot10/agent-vault/projects/.dot-entry/claude/memory/keep.md echo dot-precious >$vroot10/agent-vault/projects/.dot-entry/claude/memory/keep.md
printf 'remote: (none)\npath: %s\nhost: t\n' $dotp \ printf 'remote: (none)\npath: %s\nhost: t\n' $dotp >$vroot10/agent-vault/projects/.dot-entry/origin
>$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) 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) set -l dotreport (agents-vault --status)
@@ -1425,7 +1473,8 @@ rm -f $croot10/$rmang/memory
set -l r3shim (failing_shim ln $rmang) set -l r3shim (failing_shim ln $rmang)
set -l r3path $PATH set -l r3path $PATH
set PATH $r3shim $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 -l r3rc (agents-vault --restore >/dev/null 2>$r3err; echo $status)
set PATH $r3path set PATH $r3path
check "restore: a failed relink returns non-zero" 1 "$r3rc" 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 ""
echo "== agents-vault (push) ==" echo "== agents-vault (push) =="
set -l vroot11 (mktemp -d); set -ga TMPDIRS $vroot11 set -l vroot11 (mktemp -d)
set -l croot11 (mktemp -d); set -ga TMPDIRS $croot11 set -ga TMPDIRS $vroot11
set -l chome11 (mktemp -d); set -ga TMPDIRS $chome11 set -l croot11 (mktemp -d)
set -l agy11 (mktemp -d); set -ga TMPDIRS $agy11 set -ga TMPDIRS $croot11
set -l bare (mktemp -d); set -ga TMPDIRS $bare 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 git init -q --bare $bare
set -g __fish_agent_vault_dir $vroot11/agent-vault set -g __fish_agent_vault_dir $vroot11/agent-vault
set -g __fish_agent_vault_claude_root $croot11 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 # --push with no remote must fail loudly. The commit still happened, so
# silently returning 0 would read as "backed up off this machine". # 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 pushd $pp >/dev/null
set -l prc0 (agents-vault --push --silent 2>$perr; echo $status) set -l prc0 (agents-vault --push --silent 2>$perr; echo $status)
popd >/dev/null 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 set -l deadremote $vroot11/not-a-repo.git
agents-vault --remote=$deadremote --silent agents-vault --remote=$deadremote --silent
echo pushed-never >$croot11/$pmang/memory/p4.md 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 pushd $pp >/dev/null
set -l fprc (agents-vault --push --silent 2>$fperr; echo $status) set -l fprc (agents-vault --push --silent 2>$fperr; echo $status)
popd >/dev/null 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. # saying "Synced" must not come with a zero exit when the push failed.
echo pushed-never-2 >$croot11/$pmang/memory/p5.md echo pushed-never-2 >$croot11/$pmang/memory/p5.md
set -g __fish_agent_vault_autopush 1 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 pushd $pp >/dev/null
set -l fp2rc (agents-vault --quiet 2>$fp2err >/dev/null; echo $status) set -l fp2rc (agents-vault --quiet 2>$fp2err >/dev/null; echo $status)
popd >/dev/null popd >/dev/null
@@ -1561,10 +1618,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo "" echo ""
echo "== agents-vault (the user's ssh command wins) ==" echo "== agents-vault (the user's ssh command wins) =="
set -l vroot14 (mktemp -d); set -ga TMPDIRS $vroot14 set -l vroot14 (mktemp -d)
set -l croot14 (mktemp -d); set -ga TMPDIRS $croot14 set -ga TMPDIRS $vroot14
set -l chome14 (mktemp -d); set -ga TMPDIRS $chome14 set -l croot14 (mktemp -d)
set -l agy14 (mktemp -d); set -ga TMPDIRS $agy14 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_dir $vroot14/agent-vault
set -g __fish_agent_vault_claude_root $croot14 set -g __fish_agent_vault_claude_root $croot14
set -g __fish_agent_vault_claude_home $chome14 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 # plain `ssh` (what the injected default resolves to) and one named
# explicitly by the user. Both refuse the connection, so nothing leaves # explicitly by the user. Both refuse the connection, so nothing leaves
# the machine and no test waits on a network. # 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 pathssh_log $sbin/path-ssh.log
set -l usessh $sbin/user-ssh set -l usessh $sbin/user-ssh
set -l usessh_log $sbin/user-ssh.log set -l usessh_log $sbin/user-ssh.log
@@ -1655,11 +1717,16 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo "" echo ""
echo "== agents-vault (autopush without timeout) ==" echo "== agents-vault (autopush without timeout) =="
set -l vroot15 (mktemp -d); set -ga TMPDIRS $vroot15 set -l vroot15 (mktemp -d)
set -l croot15 (mktemp -d); set -ga TMPDIRS $croot15 set -ga TMPDIRS $vroot15
set -l chome15 (mktemp -d); set -ga TMPDIRS $chome15 set -l croot15 (mktemp -d)
set -l agy15 (mktemp -d); set -ga TMPDIRS $agy15 set -ga TMPDIRS $croot15
set -l bare15 (mktemp -d); set -ga TMPDIRS $bare15 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 git init -q --bare $bare15
set -g __fish_agent_vault_dir $vroot15/agent-vault set -g __fish_agent_vault_dir $vroot15/agent-vault
set -g __fish_agent_vault_claude_root $croot15 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. # 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 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 nopath
set -l shimn 0 set -l shimn 0
for d in $PATH for d in $PATH
@@ -1696,7 +1764,8 @@ end
set -l realpath15 $PATH set -l realpath15 $PATH
echo t2 >$croot15/$tmang15/memory/t2.md 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 __fish_agent_vault_autopush 1
set -g PATH $nopath set -g PATH $nopath
check "the shimmed PATH really has no timeout" false (type -q timeout; and echo true; or echo false) 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 ""
echo "== agents-vault (a failed vault commit is fatal) ==" echo "== agents-vault (a failed vault commit is fatal) =="
set -l vroot13 (mktemp -d); set -ga TMPDIRS $vroot13 set -l vroot13 (mktemp -d)
set -l croot13 (mktemp -d); set -ga TMPDIRS $croot13 set -ga TMPDIRS $vroot13
set -l chome13 (mktemp -d); set -ga TMPDIRS $chome13 set -l croot13 (mktemp -d)
set -l agy13 (mktemp -d); set -ga TMPDIRS $agy13 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_dir $vroot13/agent-vault
set -g __fish_agent_vault_claude_root $croot13 set -g __fish_agent_vault_claude_root $croot13
set -g __fish_agent_vault_claude_home $chome13 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 chmod +x $vroot13/agent-vault/.agents-tools/hooks/pre-commit
echo hook-more >$croot13/$hmang13/memory/keep2.md echo hook-more >$croot13/$hmang13/memory/keep2.md
set -l hhead13 (git -C $vroot13/agent-vault rev-list --count HEAD) 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 pushd $hp13 >/dev/null
set -l hrc13 (agents-vault --silent 2>$herr13; echo $status) set -l hrc13 (agents-vault --silent 2>$herr13; echo $status)
popd >/dev/null 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 # 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 # divergence has to be reckoned with, and where the two ways it can fail
# have to be told apart. # have to be told apart.
set -l vroot14 (mktemp -d); set -ga TMPDIRS $vroot14 set -l vroot14 (mktemp -d)
set -l croot14 (mktemp -d); set -ga TMPDIRS $croot14 set -ga TMPDIRS $vroot14
set -l chome14 (mktemp -d); set -ga TMPDIRS $chome14 set -l croot14 (mktemp -d)
set -l agy14 (mktemp -d); set -ga TMPDIRS $agy14 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_dir $vroot14/agent-vault
set -g __fish_agent_vault_claude_root $croot14 set -g __fish_agent_vault_claude_root $croot14
set -g __fish_agent_vault_claude_home $chome14 set -g __fish_agent_vault_claude_home $chome14
set -g __fish_agent_vault_agy_root $agy14 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 git init -q --bare $bare14
set -l cp14 (new_repo https://git.rootiest.dev/rootiest/conflict.git) 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 git -C $vroot14/agent-vault push -q -u origin HEAD:refs/heads/main
# Another machine records a conflicting change to the same line... # 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 clone -q $bare14 $cclone14
git -C $cclone14 config user.email t@t git -C $cclone14 config user.email t@t
git -C $cclone14 config user.name 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 # ... 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 # line. The ordinary run has to commit it: it never fetches, so the
# divergence is invisible to it and irrelevant. # 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 echo ours >$croot14/$cmang14/memory/keep.md
set -l chead14 (git -C $vroot14/agent-vault rev-list --count HEAD) set -l chead14 (git -C $vroot14/agent-vault rev-list --count HEAD)
pushd $cp14 >/dev/null pushd $cp14 >/dev/null
@@ -1885,10 +1966,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
echo "" echo ""
echo "== agents-vault (dangling global memory link) ==" echo "== agents-vault (dangling global memory link) =="
set -l vroot12 (mktemp -d); set -ga TMPDIRS $vroot12 set -l vroot12 (mktemp -d)
set -l croot12 (mktemp -d); set -ga TMPDIRS $croot12 set -ga TMPDIRS $vroot12
set -l chome12 (mktemp -d); set -ga TMPDIRS $chome12 set -l croot12 (mktemp -d)
set -l agy12 (mktemp -d); set -ga TMPDIRS $agy12 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_dir $vroot12/agent-vault
set -g __fish_agent_vault_claude_root $croot12 set -g __fish_agent_vault_claude_root $croot12
set -g __fish_agent_vault_claude_home $chome12 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) 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 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 pushd $dp >/dev/null
set -l drc (agents-vault --silent 2>$derr; echo $status) set -l drc (agents-vault --silent 2>$derr; echo $status)
popd >/dev/null 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 # 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 # 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. # 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 init -q --bare $ibare
git -C $ip/AGENTS remote add origin $ibare git -C $ip/AGENTS remote add origin $ibare
git -C $ip/AGENTS push -q -u origin HEAD 2>/dev/null 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 chmod +x $ip/AGENTS/.agents-tools/hooks/pre-commit
echo blocked >$ip/AGENTS/devlogs/blocked.md echo blocked >$ip/AGENTS/devlogs/blocked.md
set -l ibhead (git -C $ip/AGENTS rev-list --count HEAD) 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 pushd $ip >/dev/null
set -l ibrc (agents-init --silent 2>$ierr; echo $status) set -l ibrc (agents-init --silent 2>$ierr; echo $status)
popd >/dev/null 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. # 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_keys "syn_on:" "syn_off:" "syn_and:" "syn_bare:" "syn_multi:"
set -g __fish_config_op_registry_values \ set -g __fish_config_op_registry_values \
"always/on" \ always/on \
"always/off" \ always/off \
"aliases/filesystem integrations/notifications" \ "aliases/filesystem integrations/notifications" \
"aliases" \ aliases \
"always/off always/on" "always/off always/on"
set -e __fish_config_op_aliases __fish_config_op_integrations __fish_config_opinionated 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 # The path production code actually takes, as opposed to calling the cascade
# directly. Same rule: unset means off and the master cannot enable it. # 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_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 set -e __fish_config_op_logging __fish_config_op_logging_terminal_capture
__fish_config_op_enabled syn_log __fish_config_op_enabled syn_log
+3 -3
View File
@@ -45,7 +45,7 @@ function test_help_renderer
'function fixturefn' \ 'function fixturefn' \
' __fish_help_header (status current-function) $argv; and return 0' \ ' __fish_help_header (status current-function) $argv; and return 0' \
' echo RAN-BODY' \ ' echo RAN-BODY' \
'end' >$tmp/fixturefn.fish end >$tmp/fixturefn.fish
set -l out (_help_probe $tmp 'fixturefn --help') set -l out (_help_probe $tmp 'fixturefn --help')
set -l code $status set -l code $status
@@ -106,14 +106,14 @@ function test_help_renderer_degrades_safely
'function headerless' \ 'function headerless' \
' __fish_help_header (status current-function) $argv; and return 0' \ ' __fish_help_header (status current-function) $argv; and return 0' \
" touch $tmp/BODY-RAN" \ " touch $tmp/BODY-RAN" \
'end' >$tmp/headerless.fish end >$tmp/headerless.fish
# A comment run carrying no `# LABEL` line at all. # A comment run carrying no `# LABEL` line at all.
printf '%s\n' \ printf '%s\n' \
'# just an ordinary comment, no labels here' \ '# just an ordinary comment, no labels here' \
'function malformed' \ 'function malformed' \
' __fish_help_header (status current-function) $argv; and return 0' \ ' __fish_help_header (status current-function) $argv; and return 0' \
" touch $tmp/BODY-RAN" \ " touch $tmp/BODY-RAN" \
'end' >$tmp/malformed.fish end >$tmp/malformed.fish
set -l failed 0 set -l failed 0
for fn in headerless malformed 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()