test: expand test coverage across core functions, enforce strict fish_indent linting, and add network isolation harnesses #143
+2
-3
@@ -108,7 +108,6 @@ function __bd_requires_order_preservation
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
# This function does two things:
|
||||
# - Obtain the completions and store them in the global __bd_comp_results
|
||||
# - Return false if file completion should be performed
|
||||
@@ -215,7 +214,7 @@ end
|
||||
# so we can properly delete any completions provided by another script.
|
||||
# Only do this if the program can be found, or else fish may print some errors; besides,
|
||||
# the existing completions will only be loaded if the program can be found.
|
||||
if type -q "bd"
|
||||
if type -q bd
|
||||
# The space after the program name is essential to trigger completion for the program
|
||||
# and not completion of the program name itself.
|
||||
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
|
||||
@@ -226,7 +225,7 @@ end
|
||||
complete -c bd -e
|
||||
|
||||
# this will get called after the two calls below and clear the $__bd_perform_completion_once_result global
|
||||
complete -c bd -n '__bd_clear_perform_completion_once_result'
|
||||
complete -c bd -n __bd_clear_perform_completion_once_result
|
||||
# The call to __bd_prepare_completions will setup __bd_comp_results
|
||||
# which provides the program's completion choices.
|
||||
# If this doesn't require order preservation, we don't use the -k flag
|
||||
|
||||
+22
-22
@@ -1,14 +1,14 @@
|
||||
# fish completion for copilot
|
||||
# Generated by `copilot completion fish`. Do not edit by hand.
|
||||
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'login' -d 'Authenticate with Copilot'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'help' -d 'Display help information'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'init' -d 'Initialize Copilot instructions'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'update' -d 'Download the latest version'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'version' -d 'Display version information'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'plugin' -d 'Manage plugins'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'mcp' -d 'Manage MCP servers'
|
||||
complete -c copilot -n '__fish_use_subcommand' -f -a 'completion' -d 'Generate a shell completion script'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a login -d 'Authenticate with Copilot'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a help -d 'Display help information'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a init -d 'Initialize Copilot instructions'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a update -d 'Download the latest version'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a version -d 'Display version information'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a plugin -d 'Manage plugins'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a mcp -d 'Manage MCP servers'
|
||||
complete -c copilot -n __fish_use_subcommand -f -a completion -d 'Generate a shell completion script'
|
||||
complete -c copilot -l version -s v -f -d 'show version information'
|
||||
complete -c copilot -l interactive -s i -r -d 'Start interactive mode and automatically execute this prompt'
|
||||
complete -c copilot -l prompt -s p -r -d 'Execute a prompt in non-interactive mode (exits after completion)'
|
||||
@@ -75,22 +75,22 @@ complete -c copilot -l acp -f -d 'Start as Agent Client Protocol server'
|
||||
complete -c copilot -l remote -f -d 'Enable remote control of your session from GitHub web and mobile'
|
||||
complete -c copilot -l no-remote -f -d 'Disable remote control of your session from GitHub web and mobile'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from login' -l host -r -d 'GitHub host URL (default: https://github.com)'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'install' -d 'Install a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'uninstall' -d 'Uninstall a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'update' -d 'Update a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'list' -d 'List installed plugins'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a 'marketplace' -d 'Manage plugin marketplaces'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a install -d 'Install a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a uninstall -d 'Uninstall a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a update -d 'Update a plugin'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a list -d 'List installed plugins'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin' -f -a marketplace -d 'Manage plugin marketplaces'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from update' -l all -f -d 'Update all installed plugins'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'add' -d 'Add a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'remove' -d 'Remove a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'list' -d 'List registered marketplaces'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'browse' -d 'Browse plugins in a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a 'update' -d 'Update marketplace plugin catalogs'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a add -d 'Add a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a remove -d 'Remove a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a list -d 'List registered marketplaces'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a browse -d 'Browse plugins in a marketplace'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace' -f -a update -d 'Update marketplace plugin catalogs'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from plugin; and __fish_seen_subcommand_from marketplace; and __fish_seen_subcommand_from remove' -l force -s f -f -d 'Force removal even if plugins are installed'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'list' -d 'List configured MCP servers'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'get' -d 'Show server details'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'add' -d 'Add an MCP server'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a 'remove' -d 'Remove an MCP server'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a list -d 'List configured MCP servers'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a get -d 'Show server details'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a add -d 'Add an MCP server'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp' -f -a remove -d 'Remove an MCP server'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from list' -l json -f -d 'Output as JSON'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from get' -l json -f -d 'Output as JSON'
|
||||
complete -c copilot -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from get' -l show-secrets -f -d 'Show full environment variable and header values (masked by default)'
|
||||
|
||||
+44
-44
@@ -24,15 +24,15 @@ function __fish_deadbranch_using_subcommand
|
||||
contains -- $cmd[1] $argv
|
||||
end
|
||||
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "list" -d 'List stale branches'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "clean" -d 'Delete stale branches (merged only by default, use --force for unmerged)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "config" -d 'Manage configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "backup" -d 'Manage backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "stats" -d 'Show repository branch statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "completions" -d 'Generate shell completion scripts'
|
||||
complete -c deadbranch -n "__fish_deadbranch_needs_command" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a list -d 'List stale branches'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a clean -d 'Delete stale branches (merged only by default, use --force for unmerged)'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a config -d 'Manage configuration'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a backup -d 'Manage backups'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a stats -d 'Show repository branch statistics'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a completions -d 'Generate shell completion scripts'
|
||||
complete -c deadbranch -n __fish_deadbranch_needs_command -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -s d -l days -d 'Only show branches older than N days (default: from config or 30)' -r
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -l local -d 'Only show local branches'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand list" -l remote -d 'Only show remote branches'
|
||||
@@ -51,11 +51,11 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand clean" -s h -l hel
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand clean" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "set" -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "show" -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "edit" -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "reset" -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a set -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a show -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a edit -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a reset -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and not __fish_seen_subcommand_from set show edit reset help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from set" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from set" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from show" -s h -l help -d 'Print help'
|
||||
@@ -64,18 +64,18 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from edit" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from reset" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from reset" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "set" -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "show" -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "edit" -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "reset" -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a set -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a show -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a edit -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a reset -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand config; and __fish_seen_subcommand_from help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "list" -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "restore" -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "stats" -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "clean" -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a list -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a restore -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a stats -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a clean -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and not __fish_seen_subcommand_from list restore stats clean help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -l repo -d 'Show backups for a specific repository by name' -r
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -l current -d 'Only show backups for current repository'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from list" -s h -l help -d 'Print help'
|
||||
@@ -94,28 +94,28 @@ complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s y -l yes -d 'Skip confirmation prompt'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from clean" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "list" -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "restore" -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "stats" -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "clean" -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a list -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a restore -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a stats -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a clean -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand backup; and __fish_seen_subcommand_from help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s d -l days -d 'Treat branches older than N days as stale (default: from config or 30)' -r
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand stats" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand completions" -s h -l help -d 'Print help'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand completions" -s V -l version -d 'Print version'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "list" -d 'List stale branches'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "clean" -d 'Delete stale branches (merged only by default, use --force for unmerged)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "config" -d 'Manage configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "backup" -d 'Manage backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "stats" -d 'Show repository branch statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "completions" -d 'Generate shell completion scripts'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a "help" -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "set" -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "show" -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "edit" -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a "reset" -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "list" -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "restore" -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "stats" -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a "clean" -d 'Remove old backups, keeping the most recent ones'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a list -d 'List stale branches'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a clean -d 'Delete stale branches (merged only by default, use --force for unmerged)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a config -d 'Manage configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a backup -d 'Manage backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a stats -d 'Show repository branch statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a completions -d 'Generate shell completion scripts'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and not __fish_seen_subcommand_from list clean config backup stats completions help" -f -a help -d 'Print this message or the help of the given subcommand(s)'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a set -d 'Set a configuration value'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a show -d 'Show current configuration'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a edit -d 'Open config file in $EDITOR'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from config" -f -a reset -d 'Reset configuration to defaults'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a list -d 'List available backups'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a restore -d 'Restore a branch from backup'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a stats -d 'Show backup storage statistics'
|
||||
complete -c deadbranch -n "__fish_deadbranch_using_subcommand help; and __fish_seen_subcommand_from backup" -f -a clean -d 'Remove old backups, keeping the most recent ones'
|
||||
|
||||
+2
-3
@@ -108,7 +108,6 @@ function __ov_requires_order_preservation
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
# This function does two things:
|
||||
# - Obtain the completions and store them in the global __ov_comp_results
|
||||
# - Return false if file completion should be performed
|
||||
@@ -215,7 +214,7 @@ end
|
||||
# so we can properly delete any completions provided by another script.
|
||||
# Only do this if the program can be found, or else fish may print some errors; besides,
|
||||
# the existing completions will only be loaded if the program can be found.
|
||||
if type -q "ov"
|
||||
if type -q ov
|
||||
# The space after the program name is essential to trigger completion for the program
|
||||
# and not completion of the program name itself.
|
||||
# Also, we use '> /dev/null 2>&1' since '&>' is not supported in older versions of fish.
|
||||
@@ -226,7 +225,7 @@ end
|
||||
complete -c ov -e
|
||||
|
||||
# this will get called after the two calls below and clear the $__ov_perform_completion_once_result global
|
||||
complete -c ov -n '__ov_clear_perform_completion_once_result'
|
||||
complete -c ov -n __ov_clear_perform_completion_once_result
|
||||
# The call to __ov_prepare_completions will setup __ov_comp_results
|
||||
# which provides the program's completion choices.
|
||||
# If this doesn't require order preservation, we don't use the -k flag
|
||||
|
||||
@@ -125,7 +125,6 @@ function __tailscale_requires_order_preservation
|
||||
return 1
|
||||
end
|
||||
|
||||
|
||||
# This function does two things:
|
||||
# - Obtain the completions and store them in the global __tailscale_comp_results
|
||||
# - Return false if file completion should be performed
|
||||
@@ -242,7 +241,7 @@ end
|
||||
complete -c tailscale -e
|
||||
|
||||
# this will get called after the two calls below and clear the $__tailscale_perform_completion_once_result global
|
||||
complete -c tailscale -n '__tailscale_clear_perform_completion_once_result'
|
||||
complete -c tailscale -n __tailscale_clear_perform_completion_once_result
|
||||
# The call to __tailscale_prepare_completions will setup __tailscale_comp_results
|
||||
# which provides the program's completion choices.
|
||||
# If this doesn't require order preservation, we don't use the -k flag
|
||||
|
||||
@@ -80,69 +80,68 @@ set -g __fish_config_op_registry_keys \
|
||||
"zoxide:"
|
||||
|
||||
set -g __fish_config_op_registry_values \
|
||||
"autoexec/venv" \
|
||||
"logging/terminal-capture" \
|
||||
"autoexec/sync" \
|
||||
"logging/multiplexer-capture" \
|
||||
"integrations/terminal-abbrs" \
|
||||
"overrides/key-bindings" \
|
||||
"aliases/dev-tools" \
|
||||
"autoexec/sync" \
|
||||
"overrides/key-bindings" \
|
||||
"aliases/shell-tools" \
|
||||
"overrides/key-bindings" \
|
||||
"aliases/filesystem" \
|
||||
"aliases/dev-tools" \
|
||||
"aliases/filesystem" \
|
||||
"overrides/key-bindings" \
|
||||
"overrides/environment" \
|
||||
"overrides/environment" \
|
||||
"overrides/key-bindings" \
|
||||
"greeting/greeting-message" \
|
||||
"overrides/environment" \
|
||||
"overrides/environment" \
|
||||
"overrides/privacy" \
|
||||
"overrides/key-bindings" \
|
||||
"integrations/notifications" \
|
||||
"aliases/filesystem" \
|
||||
"aliases/dev-tools" \
|
||||
"autoexec/plugin-management" \
|
||||
"greeting/first-run" \
|
||||
"overrides/prompt" \
|
||||
"aliases/shell-tools" \
|
||||
"integrations/history-logs" \
|
||||
"overrides/key-bindings" \
|
||||
"logging/terminal-capture" \
|
||||
"logging/terminal-capture" \
|
||||
"aliases/shell-tools" \
|
||||
"integrations/history-logs" \
|
||||
"aliases/filesystem" \
|
||||
"aliases/filesystem" \
|
||||
"aliases/filesystem" \
|
||||
"autoexec/pkg-wrappers" \
|
||||
"logging/pkg-logs" \
|
||||
"aliases/network" \
|
||||
"overrides/key-bindings" \
|
||||
"aliases/search" \
|
||||
"aliases/filesystem" \
|
||||
"overrides/key-bindings" \
|
||||
"logging/terminal-capture" \
|
||||
"integrations/window-mgmt" \
|
||||
"integrations/window-mgmt" \
|
||||
"aliases/network" \
|
||||
"overrides/prompt" \
|
||||
"integrations/window-mgmt" \
|
||||
"overrides/prompt" \
|
||||
"logging/multiplexer-capture" \
|
||||
"aliases/monitor" \
|
||||
"aliases/filesystem" \
|
||||
"overrides/key-bindings" \
|
||||
"overrides/environment" \
|
||||
"integrations/pkg-upgrade" \
|
||||
"autoexec/telemetry" \
|
||||
"integrations/notifications" \
|
||||
"autoexec/pkg-wrappers" \
|
||||
"logging/pkg-logs" \
|
||||
"aliases/network" \
|
||||
"aliases/filesystem"
|
||||
|
||||
autoexec/venv \
|
||||
logging/terminal-capture \
|
||||
autoexec/sync \
|
||||
logging/multiplexer-capture \
|
||||
integrations/terminal-abbrs \
|
||||
overrides/key-bindings \
|
||||
aliases/dev-tools \
|
||||
autoexec/sync \
|
||||
overrides/key-bindings \
|
||||
aliases/shell-tools \
|
||||
overrides/key-bindings \
|
||||
aliases/filesystem \
|
||||
aliases/dev-tools \
|
||||
aliases/filesystem \
|
||||
overrides/key-bindings \
|
||||
overrides/environment \
|
||||
overrides/environment \
|
||||
overrides/key-bindings \
|
||||
greeting/greeting-message \
|
||||
overrides/environment \
|
||||
overrides/environment \
|
||||
overrides/privacy \
|
||||
overrides/key-bindings \
|
||||
integrations/notifications \
|
||||
aliases/filesystem \
|
||||
aliases/dev-tools \
|
||||
autoexec/plugin-management \
|
||||
greeting/first-run \
|
||||
overrides/prompt \
|
||||
aliases/shell-tools \
|
||||
integrations/history-logs \
|
||||
overrides/key-bindings \
|
||||
logging/terminal-capture \
|
||||
logging/terminal-capture \
|
||||
aliases/shell-tools \
|
||||
integrations/history-logs \
|
||||
aliases/filesystem \
|
||||
aliases/filesystem \
|
||||
aliases/filesystem \
|
||||
autoexec/pkg-wrappers \
|
||||
logging/pkg-logs \
|
||||
aliases/network \
|
||||
overrides/key-bindings \
|
||||
aliases/search \
|
||||
aliases/filesystem \
|
||||
overrides/key-bindings \
|
||||
logging/terminal-capture \
|
||||
integrations/window-mgmt \
|
||||
integrations/window-mgmt \
|
||||
aliases/network \
|
||||
overrides/prompt \
|
||||
integrations/window-mgmt \
|
||||
overrides/prompt \
|
||||
logging/multiplexer-capture \
|
||||
aliases/monitor \
|
||||
aliases/filesystem \
|
||||
overrides/key-bindings \
|
||||
overrides/environment \
|
||||
integrations/pkg-upgrade \
|
||||
autoexec/telemetry \
|
||||
integrations/notifications \
|
||||
autoexec/pkg-wrappers \
|
||||
logging/pkg-logs \
|
||||
aliases/network \
|
||||
aliases/filesystem
|
||||
|
||||
@@ -17,7 +17,9 @@ function expand_bang_all --description 'Execute expand_bang_all'
|
||||
__fish_config_op_enabled (status basename); or return 1
|
||||
|
||||
set -l token $argv[1]
|
||||
if test -z "$token"; set token (commandline -t); end
|
||||
if test -z "$token"
|
||||
set token (commandline -t)
|
||||
end
|
||||
|
||||
set -l tokens (string split -n " " -- $history[1])
|
||||
if test (count $tokens) -gt 1
|
||||
@@ -46,7 +48,9 @@ function expand_bang_minus_n --description 'Execute expand_bang_minus_n'
|
||||
__fish_config_op_enabled (status basename); or return 1
|
||||
|
||||
set -l token $argv[1]
|
||||
if test -z "$token"; set token (commandline -t); end
|
||||
if test -z "$token"
|
||||
set token (commandline -t)
|
||||
end
|
||||
|
||||
# Extract the number from the regex match
|
||||
if string match -qr '!-(\d+)' -- "$token"
|
||||
|
||||
@@ -60,7 +60,7 @@ printf '%s\n' \
|
||||
"cmd_str=\"$_paru_real\"" \
|
||||
'for arg in "$@"; do' \
|
||||
' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \
|
||||
'done' \
|
||||
done \
|
||||
'script -q -e -c "$cmd_str" "$log_file"' \
|
||||
'exit_code=$?' \
|
||||
'' \
|
||||
@@ -70,19 +70,18 @@ printf '%s\n' \
|
||||
'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \
|
||||
'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \
|
||||
' python3 "$cleaner" < "$log_file" > "${log_file}.tmp" 2>/dev/null && mv "${log_file}.tmp" "$log_file" || rm -f "${log_file}.tmp"' \
|
||||
'else' \
|
||||
else \
|
||||
' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \
|
||||
'fi' \
|
||||
fi \
|
||||
'' \
|
||||
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
|
||||
'mapfile -t logs < <(ls -1t "$log_dir"/paru_*.log 2>/dev/null)' \
|
||||
'excess=$(( ${#logs[@]} - max_files ))' \
|
||||
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
|
||||
' rm -f "${logs[$i]}"' \
|
||||
'done' \
|
||||
done \
|
||||
'' \
|
||||
'exit $exit_code' \
|
||||
> $_paru_wrapper
|
||||
'exit $exit_code' >$_paru_wrapper
|
||||
|
||||
chmod +x $_paru_wrapper
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ function __register_wakatime_fish_before_exec -e fish_postexec
|
||||
|
||||
set exec_command_str (string split -f1 ' ' "$argv")
|
||||
|
||||
if test "$exec_command_str" = 'exit'
|
||||
if test "$exec_command_str" = exit
|
||||
return 0
|
||||
end
|
||||
|
||||
@@ -49,8 +49,9 @@ function __register_wakatime_fish_before_exec -e fish_postexec
|
||||
if git rev-parse --is-inside-work-tree &>/dev/null
|
||||
set project (basename (git rev-parse --show-toplevel))
|
||||
else
|
||||
set project "Terminal"
|
||||
set project Terminal
|
||||
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
|
||||
|
||||
@@ -60,7 +60,7 @@ printf '%s\n' \
|
||||
"cmd_str=\"$_yay_real\"" \
|
||||
'for arg in "$@"; do' \
|
||||
' cmd_str+=" $(printf '"'"'%q'"'"' "$arg")"' \
|
||||
'done' \
|
||||
done \
|
||||
'script -q -e -c "$cmd_str" "$log_file"' \
|
||||
'exit_code=$?' \
|
||||
'' \
|
||||
@@ -70,19 +70,18 @@ printf '%s\n' \
|
||||
'cleaner="${XDG_CONFIG_HOME:-$HOME/.config}/fish/scripts/clean_progress_log.py"' \
|
||||
'if command -v python3 >/dev/null 2>&1 && [[ -f "$cleaner" ]]; then' \
|
||||
' python3 "$cleaner" < "$log_file" > "${log_file}.tmp" 2>/dev/null && mv "${log_file}.tmp" "$log_file" || rm -f "${log_file}.tmp"' \
|
||||
'else' \
|
||||
else \
|
||||
' sed -i "/^Script \(started\|done\) on /d" "$log_file" 2>/dev/null || true' \
|
||||
'fi' \
|
||||
fi \
|
||||
'' \
|
||||
'max_files="${SCROLLBACK_HISTORY_MAX_FILES:-100}"' \
|
||||
'mapfile -t logs < <(ls -1t "$log_dir"/yay_*.log 2>/dev/null)' \
|
||||
'excess=$(( ${#logs[@]} - max_files ))' \
|
||||
'for (( i = ${#logs[@]} - 1; i >= ${#logs[@]} - excess && i >= 0; i-- )); do' \
|
||||
' rm -f "${logs[$i]}"' \
|
||||
'done' \
|
||||
done \
|
||||
'' \
|
||||
'exit $exit_code' \
|
||||
> $_yay_wrapper
|
||||
'exit $exit_code' >$_yay_wrapper
|
||||
|
||||
chmod +x $_yay_wrapper
|
||||
|
||||
|
||||
@@ -99,10 +99,16 @@ def render(registry: dict[str, list[str]]) -> str:
|
||||
lines += [f" {k} \\" for k in quoted_keys[:-1]] + [f" {quoted_keys[-1]}"]
|
||||
lines.append("")
|
||||
|
||||
values = ['"' + " ".join(registry[k]) + '"' for k in keys]
|
||||
values = [
|
||||
(
|
||||
'"' + " ".join(registry[k]) + '"'
|
||||
if any(c in " ".join(registry[k]) for c in ' \t*?[]"\'\\$')
|
||||
else " ".join(registry[k])
|
||||
)
|
||||
for k in keys
|
||||
]
|
||||
lines.append("set -g __fish_config_op_registry_values \\")
|
||||
lines += [f" {v} \\" for v in values[:-1]] + [f" {values[-1]}"]
|
||||
lines.append("")
|
||||
return "\n".join(lines) + "\n"
|
||||
|
||||
|
||||
|
||||
@@ -28,9 +28,9 @@ function __config_settings_subcats --description 'List the sub-categories for on
|
||||
case __fish_config_op_aliases
|
||||
printf '%s\t%s\t%s\n' \
|
||||
filesystem Filesystem "ls, cat, cd, du, mkdir, rm, mv, zoxide" \
|
||||
search Search "rg" \
|
||||
search Search rg \
|
||||
network Network "ping, ssh, yt-dlp" \
|
||||
monitor Monitor "top" \
|
||||
monitor Monitor top \
|
||||
shell-tools Shell-tools "bash, less, help" \
|
||||
dev-tools Dev-tools "claude, edit, agy"
|
||||
case __fish_config_op_autoexec
|
||||
@@ -52,7 +52,7 @@ function __config_settings_subcats --description 'List the sub-categories for on
|
||||
window-mgmt Window-mgmt "spwin, tab, split" \
|
||||
notifications Notifications "done, WakaTime hook" \
|
||||
history-logs History-logs "hist, logs" \
|
||||
pkg-upgrade Pkg-upgrade "upgrade"
|
||||
pkg-upgrade Pkg-upgrade upgrade
|
||||
case __fish_config_op_logging
|
||||
printf '%s\t%s\t%s\n' \
|
||||
terminal-capture Term-capture "Kitty watcher, smart_exit scrollback" \
|
||||
|
||||
@@ -75,7 +75,7 @@ function _fish_deps_install
|
||||
set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2]
|
||||
if test -n "$_major"; and test "$_major" -lt 4
|
||||
set needs_install 1
|
||||
set upgrade_label "Upgrade"
|
||||
set upgrade_label Upgrade
|
||||
end
|
||||
end
|
||||
|
||||
@@ -193,7 +193,9 @@ function _fish_deps_install
|
||||
echo " Available methods:"
|
||||
set -l m 1
|
||||
for lbl in $method_labels
|
||||
set_color brblack; echo -n " $m) "; set_color normal
|
||||
set_color brblack
|
||||
echo -n " $m) "
|
||||
set_color normal
|
||||
echo $lbl
|
||||
set m (math $m + 1)
|
||||
end
|
||||
@@ -202,7 +204,9 @@ function _fish_deps_install
|
||||
set chosen_method $methods[$_choice]
|
||||
end
|
||||
else
|
||||
set_color brblack; echo " "(string lower $upgrade_label)"ing via $method_labels[1]"; set_color normal
|
||||
set_color brblack
|
||||
echo " "(string lower $upgrade_label)"ing via $method_labels[1]"
|
||||
set_color normal
|
||||
end
|
||||
|
||||
# Execute chosen method
|
||||
@@ -329,14 +333,18 @@ function _fish_deps_install
|
||||
|
||||
if test $status -eq 0
|
||||
set installed_any 1
|
||||
set_color green; echo " $bin "(string lower $upgrade_label)"ed."; set_color normal
|
||||
set_color green
|
||||
echo " $bin "(string lower $upgrade_label)"ed."
|
||||
set_color normal
|
||||
if test "$bin" = fish
|
||||
set_color yellow
|
||||
echo " Fish upgraded — restart your shell to use the new version."
|
||||
set_color normal
|
||||
end
|
||||
else
|
||||
set_color red; echo " $bin "(string lower $upgrade_label)" failed."; set_color normal
|
||||
set_color red
|
||||
echo " $bin "(string lower $upgrade_label)" failed."
|
||||
set_color normal
|
||||
end
|
||||
end
|
||||
set i (math $i + 1)
|
||||
|
||||
@@ -23,35 +23,57 @@ function _fish_deps_status
|
||||
set -l _major (fish --version 2>&1 | string match -r 'version (\d+)')[2]
|
||||
if test -n "$_major"; and test "$_major" -lt 4
|
||||
set -l _ver (fish --version 2>&1 | string replace 'fish, ' '')
|
||||
set_color yellow; echo -n " ⚠ "; set_color normal
|
||||
set_color yellow
|
||||
echo -n " ⚠ "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "($_ver — upgrade to 4.0+ required)"; set_color normal
|
||||
set_color brblack
|
||||
echo "($_ver — upgrade to 4.0+ required)"
|
||||
set_color normal
|
||||
return
|
||||
end
|
||||
end
|
||||
set_color green; echo -n " ✓ "; set_color normal
|
||||
set_color green
|
||||
echo -n " ✓ "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "(Found at "(__fish_real_command $bin)")"; set_color normal
|
||||
set_color brblack
|
||||
echo "(Found at "(__fish_real_command $bin)")"
|
||||
set_color normal
|
||||
else if test "$tier" = req
|
||||
set_color red; echo -n " ✗ "; set_color normal
|
||||
set_color red
|
||||
echo -n " ✗ "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "(Not installed)"; set_color normal
|
||||
set_color brblack
|
||||
echo "(Not installed)"
|
||||
set_color normal
|
||||
else if test "$tier" = rec
|
||||
set_color yellow; echo -n " ⚠ "; set_color normal
|
||||
set_color yellow
|
||||
echo -n " ⚠ "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "(Not installed)"; set_color normal
|
||||
set_color brblack
|
||||
echo "(Not installed)"
|
||||
set_color normal
|
||||
else
|
||||
# opt / term / int: absence is expected and not alarming
|
||||
set_color brblack; echo -n " – "; set_color normal
|
||||
set_color brblack
|
||||
echo -n " – "
|
||||
set_color normal
|
||||
echo -n "$bin "
|
||||
set_color brblack; echo "(Not installed)"; set_color normal
|
||||
set_color brblack
|
||||
echo "(Not installed)"
|
||||
set_color normal
|
||||
end
|
||||
end
|
||||
|
||||
for tier_label in "Required Dependencies:req" "Recommended Dependencies:rec" "Optional Dependencies:opt" "Terminal Emulators:term" "Integrations:int"
|
||||
set -l label (string split : $tier_label)[1]
|
||||
set -l tier (string split : $tier_label)[2]
|
||||
set_color cyan; echo $label; set_color normal
|
||||
set_color cyan
|
||||
echo $label
|
||||
set_color normal
|
||||
set -l i 1
|
||||
for bin in $_fdc_bins
|
||||
if test "$_fdc_tiers[$i]" = $tier
|
||||
|
||||
@@ -101,10 +101,14 @@ function _fish_deps_update
|
||||
echo "Updating $bin..."
|
||||
set -l _arch (uname -m)
|
||||
switch $_arch
|
||||
case x86_64; set _arch amd64
|
||||
case aarch64 arm64; set _arch arm64
|
||||
case armv7l; set _arch arm
|
||||
case '*'; set _arch amd64
|
||||
case x86_64
|
||||
set _arch amd64
|
||||
case aarch64 arm64
|
||||
set _arch arm64
|
||||
case armv7l
|
||||
set _arch arm
|
||||
case '*'
|
||||
set _arch amd64
|
||||
end
|
||||
set -l _zip "wakatime-cli-linux-$_arch.zip"
|
||||
set -l _bin_src "wakatime-cli-linux-$_arch"
|
||||
|
||||
@@ -7,4 +7,3 @@ function _puffer_fish_expand_bang
|
||||
commandline --insert '!'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -893,8 +893,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
command cp -rn "$stash/." "$entry/" 2>/dev/null
|
||||
rm -rf "$stash"
|
||||
end
|
||||
printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) \
|
||||
>>"$entry/origin"
|
||||
printf 'renamed: %s → %s (%s)\n' "$prev_slug" "$slug" (date -I) >>"$entry/origin"
|
||||
# Only a link is dropped here, and only so the relink below has
|
||||
# somewhere to put the new one. Usually $live is exactly that: a
|
||||
# symlink at the old entry, now dangling. But the migration is
|
||||
@@ -1078,8 +1077,7 @@ function agents-vault --description 'track curated agent memory in a host-scoped
|
||||
end
|
||||
set -l reached 1
|
||||
if git -C "$vault" rev-parse --abbrev-ref --symbolic-full-name '@{u}' >/dev/null 2>&1
|
||||
if not $gitnet git -C "$vault" pull --rebase --autostash -q \
|
||||
>/dev/null 2>/dev/null
|
||||
if not $gitnet git -C "$vault" pull --rebase --autostash -q >/dev/null 2>/dev/null
|
||||
# Two unrelated failures land here and reporting them as
|
||||
# one sends the user hunting for a conflict that never
|
||||
# existed. A rebase that genuinely started and stopped
|
||||
|
||||
+4
-4
@@ -52,10 +52,10 @@ function agy --wraps=agy --description 'agy wrapper: auto-initializes AGENTS/ su
|
||||
agents-vault --quiet
|
||||
|
||||
for i in (seq (count $argv))
|
||||
if test "$argv[$i]" = "-r"
|
||||
set argv[$i] "-c"
|
||||
else if test "$argv[$i]" = "--resume"
|
||||
set argv[$i] "--continue"
|
||||
if test "$argv[$i]" = -r
|
||||
set argv[$i] -c
|
||||
else if test "$argv[$i]" = --resume
|
||||
set argv[$i] --continue
|
||||
else if string match -q -- "--resume=*" "$argv[$i]"
|
||||
set argv[$i] (string replace -- "--resume=" "--continue=" "$argv[$i]")
|
||||
end
|
||||
|
||||
+12
-3
@@ -25,11 +25,20 @@
|
||||
function bd-pull --description 'Pull new Gitea issues into local Beads and link them'
|
||||
__fish_help_header (status current-function) $argv; and return 0
|
||||
|
||||
if not set -q argv[1]; echo "Need repo owner/name"; return 1; end
|
||||
if not set -q GITEA_TOKEN; echo "\$GITEA_TOKEN not set"; return 1; end
|
||||
if not set -q argv[1]
|
||||
echo "Need repo owner/name"
|
||||
return 1
|
||||
end
|
||||
if not set -q GITEA_TOKEN
|
||||
echo "\$GITEA_TOKEN not set"
|
||||
return 1
|
||||
end
|
||||
|
||||
set -l REPO $argv[1]
|
||||
if not set -q GITEA_URL; echo "\$GITEA_URL not set"; return 1; end
|
||||
if not set -q GITEA_URL
|
||||
echo "\$GITEA_URL not set"
|
||||
return 1
|
||||
end
|
||||
set -l IMPORT_COUNT 0
|
||||
|
||||
echo (set_color blue)"📡 Checking Gitea: $REPO..."(set_color normal)
|
||||
|
||||
+1
-2
@@ -151,8 +151,7 @@ function logs --description 'Browse terminal log files interactively with fzf'
|
||||
'case "$confirm" in [nN]) exit 0 ;; esac' \
|
||||
"fish -c \"rm \$FILE\"" \
|
||||
"grep -vF \"\$FILE\" $tmpfile > $tmpfile.new" \
|
||||
"mv $tmpfile.new $tmpfile" \
|
||||
> $deletescript
|
||||
"mv $tmpfile.new $tmpfile" >$deletescript
|
||||
chmod +x $deletescript
|
||||
|
||||
set -l selected (command cat $tmpfile | fzf \
|
||||
|
||||
+64
-16
@@ -187,28 +187,52 @@ function pkg --description 'Install or remove packages via the system package ma
|
||||
switch $pm
|
||||
case paru yay
|
||||
$pm -S $to_install
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case pacman
|
||||
sudo pacman -S $to_install
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case apt
|
||||
sudo apt install -y $to_install
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case dnf
|
||||
sudo dnf install -y $to_install
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case zypper
|
||||
sudo zypper install -y $to_install
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case brew
|
||||
brew install $to_install
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case pkg
|
||||
sudo pkg install -y $to_install
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case yum
|
||||
sudo yum install -y $to_install
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -217,28 +241,52 @@ function pkg --description 'Install or remove packages via the system package ma
|
||||
switch $pm
|
||||
case paru yay
|
||||
$pm -Rns $to_remove
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case pacman
|
||||
sudo pacman -Rns $to_remove
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case apt
|
||||
sudo apt remove -y $to_remove
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case dnf
|
||||
sudo dnf remove -y $to_remove
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case zypper
|
||||
sudo zypper remove -y $to_remove
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case brew
|
||||
brew uninstall $to_remove
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case pkg
|
||||
sudo pkg delete -y $to_remove
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
case yum
|
||||
sudo yum remove -y $to_remove
|
||||
or begin; functions -e __pkg_is_installed; return $status; end
|
||||
or begin
|
||||
functions -e __pkg_is_installed
|
||||
return $status
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -62,8 +62,8 @@ function rand_string --description 'Generate random, memorable strings from cura
|
||||
return 0
|
||||
end
|
||||
|
||||
set -l sep "dash"
|
||||
set -l casing "lower"
|
||||
set -l sep dash
|
||||
set -l casing lower
|
||||
|
||||
# Locate the words directory relative to this function
|
||||
set -l words_dir ""
|
||||
@@ -114,9 +114,9 @@ function rand_string --description 'Generate random, memorable strings from cura
|
||||
set -l actual_sep $sep
|
||||
switch $sep
|
||||
case dash
|
||||
set actual_sep "-"
|
||||
set actual_sep -
|
||||
case underscore
|
||||
set actual_sep "_"
|
||||
set actual_sep _
|
||||
case dot
|
||||
set actual_sep "."
|
||||
case none empty
|
||||
|
||||
@@ -38,7 +38,7 @@ function sbver --description 'Verifies Secure Boot status of EFI binaries using
|
||||
|
||||
# Flags
|
||||
set brief_mode false
|
||||
if test "$argv[1]" = "--brief"
|
||||
if test "$argv[1]" = --brief
|
||||
set brief_mode true
|
||||
end
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
# spark 1 1 2 5 14 42
|
||||
# seq 64 | sort --random-sort | spark
|
||||
# echo "3 7 2 9 1" | spark
|
||||
function spark --description 'Sparklines'
|
||||
function spark --description Sparklines
|
||||
argparse --ignore-unknown --name=spark v/version h/help m/min= M/max= -- $argv || return
|
||||
|
||||
if set --query _flag_version[1]
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
# set -U -a sponge_filters sponge_filter_secrets
|
||||
function sponge_filter_secrets --argument-names command
|
||||
# Find all exported variables with security-sensitive names
|
||||
set -l sensitive_vars (set --names --export | string match --regex -- \
|
||||
set -l sensitive_vars (set --names --export | string match --entire --regex -- \
|
||||
'(?i)(?:TOKEN|PASSWORD|PASSWD|SECRET|API[_-]KEY|PRIVATE[_-]KEY|ACCESS[_-]KEY|AUTH[_-]KEY|CREDENTIAL|KOPIA_PASSWORD)')
|
||||
|
||||
for var in $sensitive_vars
|
||||
|
||||
@@ -62,7 +62,7 @@ function yt-dlp --description 'yt-dlp with embedding + SponsorBlock defaults'
|
||||
# --sponsorblock-remove all
|
||||
# Skip if the user set their own remove (bare or --opt=value form), or
|
||||
# disabled SponsorBlock entirely with --no-sponsorblock.
|
||||
if not string match -q -- '--sponsorblock-remove' $argv
|
||||
if not string match -q -- --sponsorblock-remove $argv
|
||||
and not string match -q -- '--sponsorblock-remove=*' $argv
|
||||
and not contains -- --no-sponsorblock $argv
|
||||
set -a extra --sponsorblock-remove all
|
||||
|
||||
+16
-12
@@ -13,7 +13,6 @@
|
||||
# - $FZF_ALT_C_COMMAND
|
||||
# - $FZF_ALT_C_OPTS
|
||||
|
||||
|
||||
# Key bindings
|
||||
# ------------
|
||||
# The oldest supported fish version is 3.1b1. To maintain compatibility, the
|
||||
@@ -56,10 +55,10 @@ function fzf_key_bindings
|
||||
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"
|
||||
else if test "$FZF_TMUX" = 1
|
||||
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
|
||||
else
|
||||
echo "fzf"
|
||||
echo fzf
|
||||
end
|
||||
end
|
||||
|
||||
@@ -85,10 +84,10 @@ function fzf_key_bindings
|
||||
switch "$tokens[1]"
|
||||
case builtin command
|
||||
set -e -- tokens[1]
|
||||
test "$tokens[1]" = "--"; and 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]
|
||||
test "$tokens[1]" = --; and set -e -- tokens[1]
|
||||
while string match -qr -- '^[\w]+=' "$tokens[1]"
|
||||
set -e -- tokens[1]
|
||||
end
|
||||
@@ -245,7 +244,7 @@ function fzf_key_bindings
|
||||
\( "$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_OPTS --ansi
|
||||
set -a -- FZF_DEFAULT_COMMAND '--color=always'
|
||||
end
|
||||
|
||||
@@ -338,10 +337,10 @@ function fzf_completion_setup
|
||||
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"
|
||||
else if test "$FZF_TMUX" = 1
|
||||
echo "fzf-tmux -d$FZF_TMUX_HEIGHT -- "
|
||||
else
|
||||
echo "fzf"
|
||||
echo fzf
|
||||
end
|
||||
end
|
||||
|
||||
@@ -367,10 +366,10 @@ function fzf_completion_setup
|
||||
switch "$tokens[1]"
|
||||
case builtin command
|
||||
set -e -- tokens[1]
|
||||
test "$tokens[1]" = "--"; and 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]
|
||||
test "$tokens[1]" = --; and set -e -- tokens[1]
|
||||
while string match -qr -- '^[\w]+=' "$tokens[1]"
|
||||
set -e -- tokens[1]
|
||||
end
|
||||
@@ -451,7 +450,10 @@ function fzf_completion_setup
|
||||
function __fzf_complete_native
|
||||
set -l -- token (commandline -t)
|
||||
set -l -- completions (eval complete -C \"$argv[1]\")
|
||||
test -n "$completions"; or begin commandline -f repaint; return; end
|
||||
test -n "$completions"; or begin
|
||||
commandline -f repaint
|
||||
return
|
||||
end
|
||||
|
||||
# Calculate tabstop based on longest completion item (sample first 500 for performance)
|
||||
set -l -- tabstop 20
|
||||
@@ -498,7 +500,9 @@ function fzf_completion_setup
|
||||
set -lx FZF_DEFAULT_COMMAND
|
||||
set -l -- fzf_query (commandline -t | string escape)
|
||||
set -l result
|
||||
eval (__fzfcmd) --query=$fzf_query | while read -l r; set -a -- result $r; end
|
||||
eval (__fzfcmd) --query=$fzf_query | while read -l r
|
||||
set -a -- result $r
|
||||
end
|
||||
and if functions -q $post_func
|
||||
commandline -rt -- (string collect -- $result | eval $post_func $args[2] | string join ' ')' '
|
||||
else
|
||||
|
||||
@@ -24,9 +24,12 @@ set -l mode bytes
|
||||
set -l baseline main
|
||||
for i in (seq (count $argv))
|
||||
switch $argv[$i]
|
||||
case --structural; set mode structural
|
||||
case --startup; set mode startup
|
||||
case --baseline; set baseline $argv[(math $i + 1)]
|
||||
case --structural
|
||||
set mode structural
|
||||
case --startup
|
||||
set mode startup
|
||||
case --baseline
|
||||
set baseline $argv[(math $i + 1)]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -71,7 +74,7 @@ set -l cases \
|
||||
"p --help" "pkg --help" "play-media --help" "qc --help" \
|
||||
"rand_string --help" "replay --help" "repo-open --help" "scrub --help" \
|
||||
"smart_exit --help" "spark --help" "y --help" \
|
||||
"mkcd" "auto-pull remove __no_such_repo__" \
|
||||
mkcd "auto-pull remove __no_such_repo__" \
|
||||
"agents-init --no-such-flag" "pkg __no_such_subcommand__"
|
||||
|
||||
function __pb_run --argument-names cfg stub cmd out
|
||||
|
||||
+17
-11
@@ -3,7 +3,7 @@
|
||||
# SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
#
|
||||
# CI test runner for this fish configuration.
|
||||
# 1. Syntax-lints every tracked .fish file (fish -n).
|
||||
# 1. Syntax-lints and indent-checks every tracked .fish file (fish -n, fish_indent --check).
|
||||
# 2. Discovers tests/test-*.fish and reads the mode each suite declares
|
||||
# in its own header (`# MODE: isolated` or `# MODE: in-session`).
|
||||
# 3. Runs each isolated suite as its own --no-config fish process with
|
||||
@@ -24,25 +24,32 @@ set -l script_dir (realpath (dirname (status filename)))
|
||||
set -l repo_root (realpath $script_dir/..)
|
||||
set -l overall_failed 0
|
||||
|
||||
# ---- Phase 1: syntax lint ------------------------------------------------
|
||||
echo "== Syntax lint =="
|
||||
# ---- Phase 1: syntax & indent lint ---------------------------------------
|
||||
echo "== Syntax & indent lint =="
|
||||
set -l lint_files $repo_root/config.fish
|
||||
for dir in functions conf.d completions integrations
|
||||
for dir in functions conf.d completions integrations tests
|
||||
set -a lint_files (find $repo_root/$dir -name '*.fish' | sort)
|
||||
end
|
||||
|
||||
set -l lint_failed 0
|
||||
set -l syntax_failed 0
|
||||
set -l indent_failed 0
|
||||
for f in $lint_files
|
||||
set -l out (fish -n $f 2>&1)
|
||||
if test $status -ne 0
|
||||
echo " FAIL "(string replace $repo_root/ '' $f)
|
||||
echo " FAIL (syntax) "(string replace $repo_root/ '' $f)
|
||||
printf '%s\n' $out
|
||||
set lint_failed (math $lint_failed + 1)
|
||||
set syntax_failed (math $syntax_failed + 1)
|
||||
end
|
||||
|
||||
if not fish_indent --check $f >/dev/null 2>&1
|
||||
echo " FAIL (indent) "(string replace $repo_root/ '' $f)
|
||||
set indent_failed (math $indent_failed + 1)
|
||||
end
|
||||
end
|
||||
set -l lint_total (count $lint_files)
|
||||
echo (math $lint_total - $lint_failed)"/$lint_total files passed lint"
|
||||
if test $lint_failed -ne 0
|
||||
echo (math $lint_total - $syntax_failed)"/$lint_total files passed syntax check"
|
||||
echo (math $lint_total - $indent_failed)"/$lint_total files passed indent check"
|
||||
if test $syntax_failed -ne 0 -o $indent_failed -ne 0
|
||||
set overall_failed 1
|
||||
end
|
||||
|
||||
@@ -160,8 +167,7 @@ if test (count $session_suites) -gt 0
|
||||
__fish_config_op_autoexec=off \
|
||||
FISH_CONFIG_TEST_ROOT=$repo_root \
|
||||
FISH_CONFIG_TEST_COUNTS=$counts \
|
||||
fish -i -c "source $repo_root/tests/lib.fish; $srcs report" \
|
||||
2>$err_file
|
||||
fish -i -c "source $repo_root/tests/lib.fish; $srcs report" 2>$err_file
|
||||
set -l session_status $status
|
||||
|
||||
set -l stderr_out (command cat $err_file)
|
||||
|
||||
+197
-109
@@ -70,7 +70,7 @@ function failing_shim --argument-names name match
|
||||
'#!/bin/sh' \
|
||||
'for a in "$@"; do' \
|
||||
" case \"\$a\" in *$match*) exit 1 ;; esac" \
|
||||
'done' \
|
||||
done \
|
||||
"exec $real \"\$@\"" >$d/$name
|
||||
chmod +x $d/$name
|
||||
printf '%s\n' $d
|
||||
@@ -166,7 +166,8 @@ git -C $r3 remote add upstream https://git.rootiest.dev/rootiest/fish-config.git
|
||||
check "falls back to first remote" $want (_agents_repo_slug $r3)
|
||||
|
||||
# Slug sanitization test: special chars in fallback (local-) branch.
|
||||
set -l dirt (mktemp -d); set -ga TMPDIRS $dirt
|
||||
set -l dirt (mktemp -d)
|
||||
set -ga TMPDIRS $dirt
|
||||
mkdir -p "$dirt/projects/My Project!"
|
||||
git -C "$dirt/projects/My Project!" init -q
|
||||
git -C "$dirt/projects/My Project!" config user.email t@t
|
||||
@@ -179,7 +180,8 @@ check "sanitizes special chars in local slug" false "$has_bad_chars"
|
||||
echo ""
|
||||
echo "== _agents_repo_ensure_symlink =="
|
||||
|
||||
set -l w (mktemp -d); set -ga TMPDIRS $w
|
||||
set -l w (mktemp -d)
|
||||
set -ga TMPDIRS $w
|
||||
mkdir -p $w/target $w/live
|
||||
|
||||
# Fresh link onto an empty live parent.
|
||||
@@ -200,7 +202,8 @@ check "refuses missing target" 1 (_agents_repo_ensure_symlink $w/live/f3 $w/nope
|
||||
check "no dangling link left" false (test -L $w/live/f3; and echo true; or echo false)
|
||||
|
||||
# Non-destructive adoption: content on both sides, nothing overwritten.
|
||||
set -l a (mktemp -d); set -ga TMPDIRS $a
|
||||
set -l a (mktemp -d)
|
||||
set -ga TMPDIRS $a
|
||||
mkdir -p $a/vault $a/live/memory
|
||||
echo vault-version >$a/vault/shared.md
|
||||
echo vault-only >$a/vault/vaultonly.md
|
||||
@@ -213,7 +216,8 @@ check "adoption keeps vault-only file" vault-only (cat $a/vault/vaultonly.md)
|
||||
check "adoption replaced dir with link" true (test -L $a/live/memory; and echo true; or echo false)
|
||||
|
||||
# Repins a link that points somewhere else.
|
||||
set -l p (mktemp -d); set -ga TMPDIRS $p
|
||||
set -l p (mktemp -d)
|
||||
set -ga TMPDIRS $p
|
||||
mkdir -p $p/one $p/two
|
||||
ln -s $p/one $p/link
|
||||
_agents_repo_ensure_symlink $p/link $p/two >/dev/null
|
||||
@@ -239,12 +243,14 @@ check "idempotent, silent" "" "$out"
|
||||
# guarantee, and the old pull-first shape broke it: a failed fetch took
|
||||
# the local commit down with it. The divergence is still built here so
|
||||
# that guarantee is tested against the case that used to fail.
|
||||
set -l origin (mktemp -d); set -ga TMPDIRS $origin
|
||||
set -l origin (mktemp -d)
|
||||
set -ga TMPDIRS $origin
|
||||
git -C $origin init -q --bare
|
||||
git -C $s remote add origin $origin
|
||||
git -C $s push -q -u origin HEAD:refs/heads/main 2>/dev/null
|
||||
|
||||
set -l clone (mktemp -d); set -ga TMPDIRS $clone
|
||||
set -l clone (mktemp -d)
|
||||
set -ga TMPDIRS $clone
|
||||
git clone -q $origin $clone
|
||||
git -C $clone config user.email t@t
|
||||
git -C $clone config user.name t
|
||||
@@ -297,7 +303,8 @@ set -l h (new_repo)
|
||||
echo first >$h/a.md
|
||||
_agents_repo_sync $h "chore: init" >/dev/null
|
||||
|
||||
set -l hooks (mktemp -d); set -ga TMPDIRS $hooks
|
||||
set -l hooks (mktemp -d)
|
||||
set -ga TMPDIRS $hooks
|
||||
printf '#!/bin/sh\nexit 1\n' >$hooks/pre-commit
|
||||
chmod +x $hooks/pre-commit
|
||||
git -C $h config core.hooksPath $hooks
|
||||
@@ -312,8 +319,10 @@ check "commit hook rejection commits nothing" $before_hook_count (git -C $h rev-
|
||||
echo ""
|
||||
echo "== agents-vault (scaffold + link) =="
|
||||
|
||||
set -l vroot (mktemp -d); set -ga TMPDIRS $vroot
|
||||
set -l croot (mktemp -d); set -ga TMPDIRS $croot
|
||||
set -l vroot (mktemp -d)
|
||||
set -ga TMPDIRS $vroot
|
||||
set -l croot (mktemp -d)
|
||||
set -ga TMPDIRS $croot
|
||||
set -g __fish_agent_vault_dir $vroot/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot
|
||||
|
||||
@@ -359,8 +368,10 @@ set -e __fish_agent_vault_claude_root
|
||||
echo ""
|
||||
echo "== agents-vault (emergent restore) =="
|
||||
|
||||
set -l vroot2 (mktemp -d); set -ga TMPDIRS $vroot2
|
||||
set -l croot2 (mktemp -d); set -ga TMPDIRS $croot2
|
||||
set -l vroot2 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot2
|
||||
set -l croot2 (mktemp -d)
|
||||
set -ga TMPDIRS $croot2
|
||||
set -g __fish_agent_vault_dir $vroot2/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot2
|
||||
|
||||
@@ -393,8 +404,10 @@ set -e __fish_agent_vault_claude_root
|
||||
echo ""
|
||||
echo "== agents-vault (link failure surfaces as exit 1) =="
|
||||
|
||||
set -l vroot3 (mktemp -d); set -ga TMPDIRS $vroot3
|
||||
set -l croot3 (mktemp -d); set -ga TMPDIRS $croot3
|
||||
set -l vroot3 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot3
|
||||
set -l croot3 (mktemp -d)
|
||||
set -ga TMPDIRS $croot3
|
||||
set -g __fish_agent_vault_dir $vroot3/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot3
|
||||
|
||||
@@ -420,8 +433,10 @@ set -e __fish_agent_vault_claude_root
|
||||
echo ""
|
||||
echo "== agents-vault (slug migration) =="
|
||||
|
||||
set -l vroot2 (mktemp -d); set -ga TMPDIRS $vroot2
|
||||
set -l croot2 (mktemp -d); set -ga TMPDIRS $croot2
|
||||
set -l vroot2 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot2
|
||||
set -l croot2 (mktemp -d)
|
||||
set -ga TMPDIRS $croot2
|
||||
set -g __fish_agent_vault_dir $vroot2/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot2
|
||||
|
||||
@@ -429,7 +444,7 @@ set -g __fish_agent_vault_claude_root $croot2
|
||||
set -l mp (new_repo)
|
||||
set -l mmangled (string replace -a '/' '-' -- $mp | string replace -a '.' '-')
|
||||
mkdir -p $croot2/$mmangled/memory
|
||||
echo "precious" >$croot2/$mmangled/memory/keep.md
|
||||
echo precious >$croot2/$mmangled/memory/keep.md
|
||||
|
||||
pushd $mp >/dev/null
|
||||
agents-vault --silent
|
||||
@@ -495,8 +510,7 @@ set -l enew_slug git.rootiest.dev-rootiest-emptycase
|
||||
# hand-built shape is covered separately just below.
|
||||
mkdir -p $vroot2/agent-vault/projects/$enew_slug
|
||||
printf 'remote: %s\npath: %s\nhost: %s\n' \
|
||||
https://git.rootiest.dev/rootiest/emptycase.git /gone/elsewhere othermachine \
|
||||
>$vroot2/agent-vault/projects/$enew_slug/origin
|
||||
https://git.rootiest.dev/rootiest/emptycase.git /gone/elsewhere othermachine >$vroot2/agent-vault/projects/$enew_slug/origin
|
||||
|
||||
pushd $emp >/dev/null
|
||||
set -l erc (agents-vault --silent 2>/dev/null; echo $status)
|
||||
@@ -579,7 +593,8 @@ check "removal: old remote entry removed" false (test -d $vroot2/agent-vault/pro
|
||||
# still find and adopt it. Before the fix, the fallback only lowercased
|
||||
# the basename instead of sanitizing it like _agents_repo_slug does, so it
|
||||
# could never match the real entry directory for a name like this.
|
||||
set -l dirty_root (mktemp -d); set -ga TMPDIRS $dirty_root
|
||||
set -l dirty_root (mktemp -d)
|
||||
set -ga TMPDIRS $dirty_root
|
||||
set -l dp "$dirty_root/My Project!"
|
||||
mkdir -p "$dp"
|
||||
git -C "$dp" init -q
|
||||
@@ -590,7 +605,7 @@ git -C "$dp" config core.hooksPath /dev/null
|
||||
|
||||
set -l dmangled (string replace -a '/' '-' -- $dp | string replace -a '.' '-')
|
||||
mkdir -p $croot2/$dmangled/memory
|
||||
echo "dirty-precious" >$croot2/$dmangled/memory/keep.md
|
||||
echo dirty-precious >$croot2/$dmangled/memory/keep.md
|
||||
|
||||
pushd $dp >/dev/null
|
||||
agents-vault --silent
|
||||
@@ -621,7 +636,8 @@ check "fallback old entry removed" false (test -d $vroot2/agent-vault/projects/$
|
||||
# is not: a --silent run that did the right thing and returned 0 still
|
||||
# printed "rm: cannot remove ...: Is a directory", which is the only
|
||||
# thing the user sees and reads as a failure.
|
||||
set -l real_root (mktemp -d); set -ga TMPDIRS $real_root
|
||||
set -l real_root (mktemp -d)
|
||||
set -ga TMPDIRS $real_root
|
||||
set -l rp "$real_root/proj"
|
||||
mkdir -p "$rp"
|
||||
git -C "$rp" init -q
|
||||
@@ -632,7 +648,7 @@ git -C "$rp" config core.hooksPath /dev/null
|
||||
|
||||
set -l rmangled (string replace -a '/' '-' -- $rp | string replace -a '.' '-')
|
||||
mkdir -p $croot2/$rmangled/memory
|
||||
echo "banked" >$croot2/$rmangled/memory/old.md
|
||||
echo banked >$croot2/$rmangled/memory/old.md
|
||||
|
||||
pushd $rp >/dev/null
|
||||
agents-vault --silent
|
||||
@@ -643,10 +659,11 @@ popd >/dev/null
|
||||
# never seen, then change the slug so the migration runs.
|
||||
rm -f $croot2/$rmangled/memory
|
||||
mkdir -p $croot2/$rmangled/memory
|
||||
echo "written-live" >$croot2/$rmangled/memory/fresh.md
|
||||
echo written-live >$croot2/$rmangled/memory/fresh.md
|
||||
git -C $rp remote add origin https://git.rootiest.dev/rootiest/realdir.git
|
||||
|
||||
set -l rerr (mktemp); set -ga TMPDIRS $rerr
|
||||
set -l rerr (mktemp)
|
||||
set -ga TMPDIRS $rerr
|
||||
pushd $rp >/dev/null
|
||||
set -l real_rc (agents-vault --silent 2>$rerr; echo $status)
|
||||
popd >/dev/null
|
||||
@@ -673,8 +690,10 @@ echo ""
|
||||
echo "== agents-vault (a real git clone) =="
|
||||
|
||||
# Machine A: populate a vault and let agents-vault commit it.
|
||||
set -l cl_vroot (mktemp -d); set -ga TMPDIRS $cl_vroot
|
||||
set -l cl_croot (mktemp -d); set -ga TMPDIRS $cl_croot
|
||||
set -l cl_vroot (mktemp -d)
|
||||
set -ga TMPDIRS $cl_vroot
|
||||
set -l cl_croot (mktemp -d)
|
||||
set -ga TMPDIRS $cl_croot
|
||||
set -g __fish_agent_vault_dir $cl_vroot/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $cl_croot
|
||||
|
||||
@@ -698,7 +717,8 @@ agents-vault --silent
|
||||
popd >/dev/null
|
||||
|
||||
# The clone, exactly as the README tells a user to make it.
|
||||
set -l cl_new (mktemp -d); set -ga TMPDIRS $cl_new
|
||||
set -l cl_new (mktemp -d)
|
||||
set -ga TMPDIRS $cl_new
|
||||
git clone -q $cl_vroot/agent-vault $cl_new/agent-vault
|
||||
git -C $cl_new/agent-vault config user.email t@t
|
||||
git -C $cl_new/agent-vault config user.name t
|
||||
@@ -709,7 +729,8 @@ check "clone: the empty entry has no claude/ subtree" false (test -d $cl_new/age
|
||||
check "clone: the empty entry is its origin file alone" true (test -f $cl_new/agent-vault/projects/$cl_empty_slug/origin; and echo true; or echo false)
|
||||
|
||||
# Machine B, case 1: an ordinary run against the clone restores memory.
|
||||
set -l cl_croot2 (mktemp -d); set -ga TMPDIRS $cl_croot2
|
||||
set -l cl_croot2 (mktemp -d)
|
||||
set -ga TMPDIRS $cl_croot2
|
||||
set -g __fish_agent_vault_dir $cl_new/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $cl_croot2
|
||||
|
||||
@@ -762,10 +783,14 @@ set -e __fish_agent_vault_claude_root
|
||||
echo ""
|
||||
echo "== agents-vault (global state) =="
|
||||
|
||||
set -l vroot5 (mktemp -d); set -ga TMPDIRS $vroot5
|
||||
set -l croot5 (mktemp -d); set -ga TMPDIRS $croot5
|
||||
set -l chome5 (mktemp -d); set -ga TMPDIRS $chome5
|
||||
set -l agy5 (mktemp -d); set -ga TMPDIRS $agy5
|
||||
set -l vroot5 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot5
|
||||
set -l croot5 (mktemp -d)
|
||||
set -ga TMPDIRS $croot5
|
||||
set -l chome5 (mktemp -d)
|
||||
set -ga TMPDIRS $chome5
|
||||
set -l agy5 (mktemp -d)
|
||||
set -ga TMPDIRS $agy5
|
||||
set -g __fish_agent_vault_dir $vroot5/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot5
|
||||
set -g __fish_agent_vault_claude_home $chome5
|
||||
@@ -802,7 +827,8 @@ printf 'transcript\n' >$agy5/knowledge/session.jsonl
|
||||
# the store boundary has to hold on its own. $outside5 stands in for that
|
||||
# home: a directory the store has no business reaching into, planted with
|
||||
# exactly the shapes that qualify.
|
||||
set -l outside5 (mktemp -d); set -ga TMPDIRS $outside5
|
||||
set -l outside5 (mktemp -d)
|
||||
set -ga TMPDIRS $outside5
|
||||
mkdir -p $outside5/nested
|
||||
echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/leaked.json
|
||||
echo SECRET-OUTSIDE-KNOWLEDGE >$outside5/target.md
|
||||
@@ -905,10 +931,14 @@ set -e __fish_agent_vault_claude_root
|
||||
echo ""
|
||||
echo "== agents-vault (global emergent restore) =="
|
||||
|
||||
set -l vroot6 (mktemp -d); set -ga TMPDIRS $vroot6
|
||||
set -l croot6 (mktemp -d); set -ga TMPDIRS $croot6
|
||||
set -l chome6 (mktemp -d); set -ga TMPDIRS $chome6
|
||||
set -l agy6 (mktemp -d); set -ga TMPDIRS $agy6
|
||||
set -l vroot6 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot6
|
||||
set -l croot6 (mktemp -d)
|
||||
set -ga TMPDIRS $croot6
|
||||
set -l chome6 (mktemp -d)
|
||||
set -ga TMPDIRS $chome6
|
||||
set -l agy6 (mktemp -d)
|
||||
set -ga TMPDIRS $agy6
|
||||
set -g __fish_agent_vault_dir $vroot6/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot6
|
||||
set -g __fish_agent_vault_claude_home $chome6
|
||||
@@ -927,8 +957,10 @@ check "global restore: vault content readable through the link" restored-global
|
||||
|
||||
# A home with neither side populated must not have a memory/ invented for
|
||||
# it: ~/.claude/memory does not exist by default.
|
||||
set -l chome7 (mktemp -d); set -ga TMPDIRS $chome7
|
||||
set -l vroot7 (mktemp -d); set -ga TMPDIRS $vroot7
|
||||
set -l chome7 (mktemp -d)
|
||||
set -ga TMPDIRS $chome7
|
||||
set -l vroot7 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot7
|
||||
set -g __fish_agent_vault_dir $vroot7/agent-vault
|
||||
set -g __fish_agent_vault_claude_home $chome7
|
||||
set -l gp7 (new_repo https://git.rootiest.dev/rootiest/globals-absent.git)
|
||||
@@ -951,10 +983,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
echo ""
|
||||
echo "== agents-vault (global link failure is non-fatal) =="
|
||||
|
||||
set -l vroot8 (mktemp -d); set -ga TMPDIRS $vroot8
|
||||
set -l croot8 (mktemp -d); set -ga TMPDIRS $croot8
|
||||
set -l chome8 (mktemp -d); set -ga TMPDIRS $chome8
|
||||
set -l agy8 (mktemp -d); set -ga TMPDIRS $agy8
|
||||
set -l vroot8 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot8
|
||||
set -l croot8 (mktemp -d)
|
||||
set -ga TMPDIRS $croot8
|
||||
set -l chome8 (mktemp -d)
|
||||
set -ga TMPDIRS $chome8
|
||||
set -l agy8 (mktemp -d)
|
||||
set -ga TMPDIRS $agy8
|
||||
set -g __fish_agent_vault_dir $vroot8/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot8
|
||||
set -g __fish_agent_vault_claude_home $chome8
|
||||
@@ -972,7 +1008,8 @@ set -l fmangled (string replace -a '/' '-' -- $fp | string replace -a '.' '-')
|
||||
mkdir -p $croot8/$fmangled/memory
|
||||
echo project-memory >$croot8/$fmangled/memory/p.md
|
||||
|
||||
set -l ferr (mktemp); set -ga TMPDIRS $ferr
|
||||
set -l ferr (mktemp)
|
||||
set -ga TMPDIRS $ferr
|
||||
pushd $fp >/dev/null
|
||||
set -l frc (agents-vault --silent 2>$ferr; echo $status)
|
||||
popd >/dev/null
|
||||
@@ -991,7 +1028,8 @@ check "global link failure: per-project memory is committed" true (git -C $vroot
|
||||
# A failed global link must not be recorded as done: the next run has to
|
||||
# re-enter the block and warn again, not treat the vault as correct.
|
||||
pushd $fp >/dev/null
|
||||
set -l ferr2 (mktemp); set -ga TMPDIRS $ferr2
|
||||
set -l ferr2 (mktemp)
|
||||
set -ga TMPDIRS $ferr2
|
||||
agents-vault --silent 2>$ferr2
|
||||
popd >/dev/null
|
||||
check "global link failure: retried on the next run" true (string match -q "*$chome8/memory*" -- (cat $ferr2); and echo true; or echo false)
|
||||
@@ -1008,17 +1046,22 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
echo ""
|
||||
echo "== agents-vault (status, remote, adopt) =="
|
||||
|
||||
set -l vroot9 (mktemp -d); set -ga TMPDIRS $vroot9
|
||||
set -l croot9 (mktemp -d); set -ga TMPDIRS $croot9
|
||||
set -l chome9 (mktemp -d); set -ga TMPDIRS $chome9
|
||||
set -l agy9 (mktemp -d); set -ga TMPDIRS $agy9
|
||||
set -l vroot9 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot9
|
||||
set -l croot9 (mktemp -d)
|
||||
set -ga TMPDIRS $croot9
|
||||
set -l chome9 (mktemp -d)
|
||||
set -ga TMPDIRS $chome9
|
||||
set -l agy9 (mktemp -d)
|
||||
set -ga TMPDIRS $agy9
|
||||
set -g __fish_agent_vault_dir $vroot9/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot9
|
||||
set -g __fish_agent_vault_claude_home $chome9
|
||||
set -g __fish_agent_vault_agy_root $agy9
|
||||
|
||||
# A report asked for before the vault exists must say so, not scaffold one.
|
||||
set -l s0out (mktemp); set -ga TMPDIRS $s0out
|
||||
set -l s0out (mktemp)
|
||||
set -ga TMPDIRS $s0out
|
||||
set -l s0rc (agents-vault --status >$s0out; echo $status)
|
||||
check "status without a vault exits 0" 0 "$s0rc"
|
||||
check "status without a vault says so" true (string match -q '*no vault*' -- (cat $s0out); and echo true; or echo false)
|
||||
@@ -1068,7 +1111,8 @@ check "status reports the remote" true (string match -q '*rootiest/agent-vault.g
|
||||
# A *failed* remote update must return non-zero. Reporting success after a
|
||||
# git command that did not run is the same silent-false-success shape that
|
||||
# a hook-rejected commit produced earlier in this project.
|
||||
set -l rerr (mktemp); set -ga TMPDIRS $rerr
|
||||
set -l rerr (mktemp)
|
||||
set -ga TMPDIRS $rerr
|
||||
set -l rshim (failing_shim git set-url)
|
||||
set -l rpath $PATH
|
||||
set PATH $rshim $PATH
|
||||
@@ -1106,10 +1150,11 @@ agents-vault --silent
|
||||
popd >/dev/null
|
||||
set -l projects_before (command ls -A $vroot9/agent-vault/projects | sort | string join ',')
|
||||
|
||||
set -l bad_slugs ../escape has/slash . .. 'UPPER' 'sp ace' ''
|
||||
set -l bad_slugs ../escape has/slash . .. UPPER 'sp ace' ''
|
||||
pushd $bp >/dev/null
|
||||
for bad in $bad_slugs
|
||||
set -l berr (mktemp); set -ga TMPDIRS $berr
|
||||
set -l berr (mktemp)
|
||||
set -ga TMPDIRS $berr
|
||||
set -l brc (agents-vault --adopt=$bad --silent 2>$berr; echo $status)
|
||||
check "adopt refuses '$bad'" 1 "$brc"
|
||||
check "adopt refuses '$bad' out loud" true (string match -q '*invalid*' -- (cat $berr); and echo true; or echo false)
|
||||
@@ -1150,7 +1195,8 @@ set -l pre_head (git -C $vroot9/agent-vault rev-list --count HEAD)
|
||||
set -l pre_porcelain (git -C $vroot9/agent-vault status --porcelain | string join ',')
|
||||
set -l pre_link (path resolve $croot9/$tmang/memory)
|
||||
|
||||
set -l terr (mktemp); set -ga TMPDIRS $terr
|
||||
set -l terr (mktemp)
|
||||
set -ga TMPDIRS $terr
|
||||
set -l tshim (failing_shim rm $tmang)
|
||||
set -l tpath $PATH
|
||||
set PATH $tshim $PATH
|
||||
@@ -1203,8 +1249,7 @@ popd >/dev/null
|
||||
# The target must be *tracked* for this to bite: stashing it takes files
|
||||
# git knows about out from under the index, which is the whole hazard.
|
||||
mkdir -p $vroot9/agent-vault/projects/stash-target/claude/memory
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere \
|
||||
>$vroot9/agent-vault/projects/stash-target/origin
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere >$vroot9/agent-vault/projects/stash-target/origin
|
||||
pushd $sa >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
@@ -1245,8 +1290,7 @@ agents-vault --silent
|
||||
popd >/dev/null
|
||||
|
||||
mkdir -p $vroot9/agent-vault/projects/stashfail-target/claude/memory
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere-else \
|
||||
>$vroot9/agent-vault/projects/stashfail-target/origin
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' /nowhere-else >$vroot9/agent-vault/projects/stashfail-target/origin
|
||||
pushd $sf >/dev/null
|
||||
agents-vault --silent
|
||||
popd >/dev/null
|
||||
@@ -1257,7 +1301,8 @@ set -l sf_porcelain (git -C $vroot9/agent-vault status --porcelain | string join
|
||||
set -l sf_link (path resolve $croot9/$sfmang/memory)
|
||||
check "stash adopt rollback: vault clean before the adopt" "" "$sf_porcelain"
|
||||
|
||||
set -l sferr (mktemp); set -ga TMPDIRS $sferr
|
||||
set -l sferr (mktemp)
|
||||
set -ga TMPDIRS $sferr
|
||||
set -l sfshim (failing_shim rm $sfmang)
|
||||
set -l sfpath $PATH
|
||||
set PATH $sfshim $PATH
|
||||
@@ -1359,10 +1404,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
echo ""
|
||||
echo "== agents-vault (restore) =="
|
||||
|
||||
set -l vroot10 (mktemp -d); set -ga TMPDIRS $vroot10
|
||||
set -l croot10 (mktemp -d); set -ga TMPDIRS $croot10
|
||||
set -l chome10 (mktemp -d); set -ga TMPDIRS $chome10
|
||||
set -l agy10 (mktemp -d); set -ga TMPDIRS $agy10
|
||||
set -l vroot10 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot10
|
||||
set -l croot10 (mktemp -d)
|
||||
set -ga TMPDIRS $croot10
|
||||
set -l chome10 (mktemp -d)
|
||||
set -ga TMPDIRS $chome10
|
||||
set -l agy10 (mktemp -d)
|
||||
set -ga TMPDIRS $agy10
|
||||
set -g __fish_agent_vault_dir $vroot10/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot10
|
||||
set -g __fish_agent_vault_claude_home $chome10
|
||||
@@ -1389,9 +1438,9 @@ check "restore: names what it restored" true (string match -q '*restoreme*' -- "
|
||||
# succeeds and says which entry needs --adopt.
|
||||
mkdir -p $vroot10/agent-vault/projects/ghost-entry/claude/memory
|
||||
echo x >$vroot10/agent-vault/projects/ghost-entry/claude/memory/x.md
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' $vroot10/gone-forever \
|
||||
>$vroot10/agent-vault/projects/ghost-entry/origin
|
||||
set -l r2out (mktemp); set -ga TMPDIRS $r2out
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' $vroot10/gone-forever >$vroot10/agent-vault/projects/ghost-entry/origin
|
||||
set -l r2out (mktemp)
|
||||
set -ga TMPDIRS $r2out
|
||||
set -l r2rc (agents-vault --restore >$r2out; echo $status)
|
||||
check "restore: exits 0 with an unplaceable entry" 0 "$r2rc"
|
||||
check "restore: reports the unplaceable entry" true (string match -q '*ghost-entry*' -- (cat $r2out); and echo true; or echo false)
|
||||
@@ -1406,8 +1455,7 @@ set -l dotp (new_repo https://git.rootiest.dev/rootiest/dotted.git)
|
||||
set -l dotmang (string replace -a '/' '-' -- $dotp | string replace -a '.' '-')
|
||||
mkdir -p $vroot10/agent-vault/projects/.dot-entry/claude/memory
|
||||
echo dot-precious >$vroot10/agent-vault/projects/.dot-entry/claude/memory/keep.md
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' $dotp \
|
||||
>$vroot10/agent-vault/projects/.dot-entry/origin
|
||||
printf 'remote: (none)\npath: %s\nhost: t\n' $dotp >$vroot10/agent-vault/projects/.dot-entry/origin
|
||||
check "dot-led entry: a glob really does skip it" false (string match -q '*.dot-entry*' -- (echo $vroot10/agent-vault/projects/*); and echo true; or echo false)
|
||||
|
||||
set -l dotreport (agents-vault --status)
|
||||
@@ -1425,7 +1473,8 @@ rm -f $croot10/$rmang/memory
|
||||
set -l r3shim (failing_shim ln $rmang)
|
||||
set -l r3path $PATH
|
||||
set PATH $r3shim $PATH
|
||||
set -l r3err (mktemp); set -ga TMPDIRS $r3err
|
||||
set -l r3err (mktemp)
|
||||
set -ga TMPDIRS $r3err
|
||||
set -l r3rc (agents-vault --restore >/dev/null 2>$r3err; echo $status)
|
||||
set PATH $r3path
|
||||
check "restore: a failed relink returns non-zero" 1 "$r3rc"
|
||||
@@ -1440,11 +1489,16 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
echo ""
|
||||
echo "== agents-vault (push) =="
|
||||
|
||||
set -l vroot11 (mktemp -d); set -ga TMPDIRS $vroot11
|
||||
set -l croot11 (mktemp -d); set -ga TMPDIRS $croot11
|
||||
set -l chome11 (mktemp -d); set -ga TMPDIRS $chome11
|
||||
set -l agy11 (mktemp -d); set -ga TMPDIRS $agy11
|
||||
set -l bare (mktemp -d); set -ga TMPDIRS $bare
|
||||
set -l vroot11 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot11
|
||||
set -l croot11 (mktemp -d)
|
||||
set -ga TMPDIRS $croot11
|
||||
set -l chome11 (mktemp -d)
|
||||
set -ga TMPDIRS $chome11
|
||||
set -l agy11 (mktemp -d)
|
||||
set -ga TMPDIRS $agy11
|
||||
set -l bare (mktemp -d)
|
||||
set -ga TMPDIRS $bare
|
||||
git init -q --bare $bare
|
||||
set -g __fish_agent_vault_dir $vroot11/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot11
|
||||
@@ -1459,7 +1513,8 @@ echo pushed >$croot11/$pmang/memory/p.md
|
||||
|
||||
# --push with no remote must fail loudly. The commit still happened, so
|
||||
# silently returning 0 would read as "backed up off this machine".
|
||||
set -l perr (mktemp); set -ga TMPDIRS $perr
|
||||
set -l perr (mktemp)
|
||||
set -ga TMPDIRS $perr
|
||||
pushd $pp >/dev/null
|
||||
set -l prc0 (agents-vault --push --silent 2>$perr; echo $status)
|
||||
popd >/dev/null
|
||||
@@ -1498,7 +1553,8 @@ check "autopush pushes when enabled" pushed-auto (git -C $bare show $vbranch:pro
|
||||
set -l deadremote $vroot11/not-a-repo.git
|
||||
agents-vault --remote=$deadremote --silent
|
||||
echo pushed-never >$croot11/$pmang/memory/p4.md
|
||||
set -l fperr (mktemp); set -ga TMPDIRS $fperr
|
||||
set -l fperr (mktemp)
|
||||
set -ga TMPDIRS $fperr
|
||||
pushd $pp >/dev/null
|
||||
set -l fprc (agents-vault --push --silent 2>$fperr; echo $status)
|
||||
popd >/dev/null
|
||||
@@ -1510,7 +1566,8 @@ check "failing push still committed locally" true (git -C $vroot11/agent-vault l
|
||||
# saying "Synced" must not come with a zero exit when the push failed.
|
||||
echo pushed-never-2 >$croot11/$pmang/memory/p5.md
|
||||
set -g __fish_agent_vault_autopush 1
|
||||
set -l fp2err (mktemp); set -ga TMPDIRS $fp2err
|
||||
set -l fp2err (mktemp)
|
||||
set -ga TMPDIRS $fp2err
|
||||
pushd $pp >/dev/null
|
||||
set -l fp2rc (agents-vault --quiet 2>$fp2err >/dev/null; echo $status)
|
||||
popd >/dev/null
|
||||
@@ -1561,10 +1618,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
echo ""
|
||||
echo "== agents-vault (the user's ssh command wins) =="
|
||||
|
||||
set -l vroot14 (mktemp -d); set -ga TMPDIRS $vroot14
|
||||
set -l croot14 (mktemp -d); set -ga TMPDIRS $croot14
|
||||
set -l chome14 (mktemp -d); set -ga TMPDIRS $chome14
|
||||
set -l agy14 (mktemp -d); set -ga TMPDIRS $agy14
|
||||
set -l vroot14 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot14
|
||||
set -l croot14 (mktemp -d)
|
||||
set -ga TMPDIRS $croot14
|
||||
set -l chome14 (mktemp -d)
|
||||
set -ga TMPDIRS $chome14
|
||||
set -l agy14 (mktemp -d)
|
||||
set -ga TMPDIRS $agy14
|
||||
set -g __fish_agent_vault_dir $vroot14/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot14
|
||||
set -g __fish_agent_vault_claude_home $chome14
|
||||
@@ -1574,7 +1635,8 @@ set -g __fish_agent_vault_agy_root $agy14
|
||||
# plain `ssh` (what the injected default resolves to) and one named
|
||||
# explicitly by the user. Both refuse the connection, so nothing leaves
|
||||
# the machine and no test waits on a network.
|
||||
set -l sbin (mktemp -d); set -ga TMPDIRS $sbin
|
||||
set -l sbin (mktemp -d)
|
||||
set -ga TMPDIRS $sbin
|
||||
set -l pathssh_log $sbin/path-ssh.log
|
||||
set -l usessh $sbin/user-ssh
|
||||
set -l usessh_log $sbin/user-ssh.log
|
||||
@@ -1655,11 +1717,16 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
echo ""
|
||||
echo "== agents-vault (autopush without timeout) =="
|
||||
|
||||
set -l vroot15 (mktemp -d); set -ga TMPDIRS $vroot15
|
||||
set -l croot15 (mktemp -d); set -ga TMPDIRS $croot15
|
||||
set -l chome15 (mktemp -d); set -ga TMPDIRS $chome15
|
||||
set -l agy15 (mktemp -d); set -ga TMPDIRS $agy15
|
||||
set -l bare15 (mktemp -d); set -ga TMPDIRS $bare15
|
||||
set -l vroot15 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot15
|
||||
set -l croot15 (mktemp -d)
|
||||
set -ga TMPDIRS $croot15
|
||||
set -l chome15 (mktemp -d)
|
||||
set -ga TMPDIRS $chome15
|
||||
set -l agy15 (mktemp -d)
|
||||
set -ga TMPDIRS $agy15
|
||||
set -l bare15 (mktemp -d)
|
||||
set -ga TMPDIRS $bare15
|
||||
git init -q --bare $bare15
|
||||
set -g __fish_agent_vault_dir $vroot15/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot15
|
||||
@@ -1678,7 +1745,8 @@ popd >/dev/null
|
||||
# rev-parse would report the literal string HEAD with a fatal on stderr.
|
||||
set -l vb15 (git -C $vroot15/agent-vault symbolic-ref --short HEAD)
|
||||
|
||||
set -l shimroot (mktemp -d); set -ga TMPDIRS $shimroot
|
||||
set -l shimroot (mktemp -d)
|
||||
set -ga TMPDIRS $shimroot
|
||||
set -l nopath
|
||||
set -l shimn 0
|
||||
for d in $PATH
|
||||
@@ -1696,7 +1764,8 @@ end
|
||||
set -l realpath15 $PATH
|
||||
|
||||
echo t2 >$croot15/$tmang15/memory/t2.md
|
||||
set -l terr (mktemp); set -ga TMPDIRS $terr
|
||||
set -l terr (mktemp)
|
||||
set -ga TMPDIRS $terr
|
||||
set -g __fish_agent_vault_autopush 1
|
||||
set -g PATH $nopath
|
||||
check "the shimmed PATH really has no timeout" false (type -q timeout; and echo true; or echo false)
|
||||
@@ -1737,10 +1806,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
echo ""
|
||||
echo "== agents-vault (a failed vault commit is fatal) =="
|
||||
|
||||
set -l vroot13 (mktemp -d); set -ga TMPDIRS $vroot13
|
||||
set -l croot13 (mktemp -d); set -ga TMPDIRS $croot13
|
||||
set -l chome13 (mktemp -d); set -ga TMPDIRS $chome13
|
||||
set -l agy13 (mktemp -d); set -ga TMPDIRS $agy13
|
||||
set -l vroot13 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot13
|
||||
set -l croot13 (mktemp -d)
|
||||
set -ga TMPDIRS $croot13
|
||||
set -l chome13 (mktemp -d)
|
||||
set -ga TMPDIRS $chome13
|
||||
set -l agy13 (mktemp -d)
|
||||
set -ga TMPDIRS $agy13
|
||||
set -g __fish_agent_vault_dir $vroot13/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot13
|
||||
set -g __fish_agent_vault_claude_home $chome13
|
||||
@@ -1763,7 +1836,8 @@ printf '#!/bin/sh\nexit 1\n' >$vroot13/agent-vault/.agents-tools/hooks/pre-commi
|
||||
chmod +x $vroot13/agent-vault/.agents-tools/hooks/pre-commit
|
||||
echo hook-more >$croot13/$hmang13/memory/keep2.md
|
||||
set -l hhead13 (git -C $vroot13/agent-vault rev-list --count HEAD)
|
||||
set -l herr13 (mktemp); set -ga TMPDIRS $herr13
|
||||
set -l herr13 (mktemp)
|
||||
set -ga TMPDIRS $herr13
|
||||
pushd $hp13 >/dev/null
|
||||
set -l hrc13 (agents-vault --silent 2>$herr13; echo $status)
|
||||
popd >/dev/null
|
||||
@@ -1782,16 +1856,21 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
# moves ahead (or goes out of reach) is not a backup; --push is where the
|
||||
# divergence has to be reckoned with, and where the two ways it can fail
|
||||
# have to be told apart.
|
||||
set -l vroot14 (mktemp -d); set -ga TMPDIRS $vroot14
|
||||
set -l croot14 (mktemp -d); set -ga TMPDIRS $croot14
|
||||
set -l chome14 (mktemp -d); set -ga TMPDIRS $chome14
|
||||
set -l agy14 (mktemp -d); set -ga TMPDIRS $agy14
|
||||
set -l vroot14 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot14
|
||||
set -l croot14 (mktemp -d)
|
||||
set -ga TMPDIRS $croot14
|
||||
set -l chome14 (mktemp -d)
|
||||
set -ga TMPDIRS $chome14
|
||||
set -l agy14 (mktemp -d)
|
||||
set -ga TMPDIRS $agy14
|
||||
set -g __fish_agent_vault_dir $vroot14/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot14
|
||||
set -g __fish_agent_vault_claude_home $chome14
|
||||
set -g __fish_agent_vault_agy_root $agy14
|
||||
|
||||
set -l bare14 (mktemp -d); set -ga TMPDIRS $bare14
|
||||
set -l bare14 (mktemp -d)
|
||||
set -ga TMPDIRS $bare14
|
||||
git init -q --bare $bare14
|
||||
|
||||
set -l cp14 (new_repo https://git.rootiest.dev/rootiest/conflict.git)
|
||||
@@ -1806,7 +1885,8 @@ agents-vault --remote=$bare14 --silent
|
||||
git -C $vroot14/agent-vault push -q -u origin HEAD:refs/heads/main
|
||||
|
||||
# Another machine records a conflicting change to the same line...
|
||||
set -l cclone14 (mktemp -d); set -ga TMPDIRS $cclone14
|
||||
set -l cclone14 (mktemp -d)
|
||||
set -ga TMPDIRS $cclone14
|
||||
git clone -q $bare14 $cclone14
|
||||
git -C $cclone14 config user.email t@t
|
||||
git -C $cclone14 config user.name t
|
||||
@@ -1819,7 +1899,8 @@ git -C $cclone14 push -q origin HEAD:main
|
||||
# ... while this one writes conflicting memory of its own on the same
|
||||
# line. The ordinary run has to commit it: it never fetches, so the
|
||||
# divergence is invisible to it and irrelevant.
|
||||
set -l cerr14 (mktemp); set -ga TMPDIRS $cerr14
|
||||
set -l cerr14 (mktemp)
|
||||
set -ga TMPDIRS $cerr14
|
||||
echo ours >$croot14/$cmang14/memory/keep.md
|
||||
set -l chead14 (git -C $vroot14/agent-vault rev-list --count HEAD)
|
||||
pushd $cp14 >/dev/null
|
||||
@@ -1885,10 +1966,14 @@ set -g __fish_agent_vault_agy_root $HERMETIC_HOME/agy
|
||||
echo ""
|
||||
echo "== agents-vault (dangling global memory link) =="
|
||||
|
||||
set -l vroot12 (mktemp -d); set -ga TMPDIRS $vroot12
|
||||
set -l croot12 (mktemp -d); set -ga TMPDIRS $croot12
|
||||
set -l chome12 (mktemp -d); set -ga TMPDIRS $chome12
|
||||
set -l agy12 (mktemp -d); set -ga TMPDIRS $agy12
|
||||
set -l vroot12 (mktemp -d)
|
||||
set -ga TMPDIRS $vroot12
|
||||
set -l croot12 (mktemp -d)
|
||||
set -ga TMPDIRS $croot12
|
||||
set -l chome12 (mktemp -d)
|
||||
set -ga TMPDIRS $chome12
|
||||
set -l agy12 (mktemp -d)
|
||||
set -ga TMPDIRS $agy12
|
||||
set -g __fish_agent_vault_dir $vroot12/agent-vault
|
||||
set -g __fish_agent_vault_claude_root $croot12
|
||||
set -g __fish_agent_vault_claude_home $chome12
|
||||
@@ -1900,7 +1985,8 @@ check "dangling: -L is the only signal" true (test -L $chome12/memory; and echo
|
||||
check "dangling: the vault side is empty" false (test -e $vroot12/agent-vault/global/claude/memory; and echo true; or echo false)
|
||||
|
||||
set -l dp (new_repo https://git.rootiest.dev/rootiest/dangling.git)
|
||||
set -l derr (mktemp); set -ga TMPDIRS $derr
|
||||
set -l derr (mktemp)
|
||||
set -ga TMPDIRS $derr
|
||||
pushd $dp >/dev/null
|
||||
set -l drc (agents-vault --silent 2>$derr; echo $status)
|
||||
popd >/dev/null
|
||||
@@ -1934,7 +2020,8 @@ check "agents-init: committed the AGENTS repo" true (test (git -C $ip/AGENTS rev
|
||||
# Offline. The pull that used to run here blocked the launch until the
|
||||
# remote timed out and then took the commit down with it, so an agent's
|
||||
# edits went unrecorded on every launch away from the network.
|
||||
set -l ibare (mktemp -d); set -ga TMPDIRS $ibare
|
||||
set -l ibare (mktemp -d)
|
||||
set -ga TMPDIRS $ibare
|
||||
git init -q --bare $ibare
|
||||
git -C $ip/AGENTS remote add origin $ibare
|
||||
git -C $ip/AGENTS push -q -u origin HEAD 2>/dev/null
|
||||
@@ -1957,7 +2044,8 @@ printf '#!/bin/sh\nexit 1\n' >$ip/AGENTS/.agents-tools/hooks/pre-commit
|
||||
chmod +x $ip/AGENTS/.agents-tools/hooks/pre-commit
|
||||
echo blocked >$ip/AGENTS/devlogs/blocked.md
|
||||
set -l ibhead (git -C $ip/AGENTS rev-list --count HEAD)
|
||||
set -l ierr (mktemp); set -ga TMPDIRS $ierr
|
||||
set -l ierr (mktemp)
|
||||
set -ga TMPDIRS $ierr
|
||||
pushd $ip >/dev/null
|
||||
set -l ibrc (agents-init --silent 2>$ierr; echo $status)
|
||||
popd >/dev/null
|
||||
|
||||
@@ -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
|
||||
@@ -245,10 +245,10 @@ section "op_enabled: always/* and AND, via a synthetic registry"
|
||||
# this process reads them, so overriding them is free.
|
||||
set -g __fish_config_op_registry_keys "syn_on:" "syn_off:" "syn_and:" "syn_bare:" "syn_multi:"
|
||||
set -g __fish_config_op_registry_values \
|
||||
"always/on" \
|
||||
"always/off" \
|
||||
always/on \
|
||||
always/off \
|
||||
"aliases/filesystem integrations/notifications" \
|
||||
"aliases" \
|
||||
aliases \
|
||||
"always/off always/on"
|
||||
|
||||
set -e __fish_config_op_aliases __fish_config_op_integrations __fish_config_opinionated
|
||||
@@ -312,7 +312,7 @@ section "op_enabled: C5 through the guard"
|
||||
# The path production code actually takes, as opposed to calling the cascade
|
||||
# directly. Same rule: unset means off and the master cannot enable it.
|
||||
set -g __fish_config_op_registry_keys "syn_log:"
|
||||
set -g __fish_config_op_registry_values "logging/terminal-capture"
|
||||
set -g __fish_config_op_registry_values logging/terminal-capture
|
||||
set -e __fish_config_op_logging __fish_config_op_logging_terminal_capture
|
||||
|
||||
__fish_config_op_enabled syn_log
|
||||
|
||||
@@ -45,7 +45,7 @@ function test_help_renderer
|
||||
'function fixturefn' \
|
||||
' __fish_help_header (status current-function) $argv; and return 0' \
|
||||
' echo RAN-BODY' \
|
||||
'end' >$tmp/fixturefn.fish
|
||||
end >$tmp/fixturefn.fish
|
||||
|
||||
set -l out (_help_probe $tmp 'fixturefn --help')
|
||||
set -l code $status
|
||||
@@ -106,14 +106,14 @@ function test_help_renderer_degrades_safely
|
||||
'function headerless' \
|
||||
' __fish_help_header (status current-function) $argv; and return 0' \
|
||||
" touch $tmp/BODY-RAN" \
|
||||
'end' >$tmp/headerless.fish
|
||||
end >$tmp/headerless.fish
|
||||
# A comment run carrying no `# LABEL` line at all.
|
||||
printf '%s\n' \
|
||||
'# just an ordinary comment, no labels here' \
|
||||
'function malformed' \
|
||||
' __fish_help_header (status current-function) $argv; and return 0' \
|
||||
" touch $tmp/BODY-RAN" \
|
||||
'end' >$tmp/malformed.fish
|
||||
end >$tmp/malformed.fish
|
||||
|
||||
set -l failed 0
|
||||
for fn in headerless malformed
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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()
|
||||
Reference in New Issue
Block a user