From: Shane Jaroch Date: Thu, 8 Jan 2026 21:26:53 +0000 (-0500) Subject: huge wip X-Git-Url: https://git.nutra.tk/v1?a=commitdiff_plain;h=a7c646348c87d1855e2ba3707bb59694c2f67332;p=gamesguru%2Fgit-remote-gcrypt.git huge wip --- diff --git a/Makefile b/Makefile index c1c823f..097863f 100644 --- a/Makefile +++ b/Makefile @@ -196,4 +196,14 @@ uninstall/user: ##H make uninstall prefix=~/.local .PHONY: clean clean: ##H Clean up - rm -rf .coverage + rm -rf .coverage .build_tmp + +# --- Autogeneration --- +.PHONY: generate +generate: ##H Autogenerate README usage & completions + @$(call print_info,Generating documentation and completions...) + @python3 tests/sync_docs.py + @# Update README.rst Usage section (simple version for now) + @sed -i '/Detecting gcrypt repos/,/Exit status is 0/c\Detecting gcrypt repos\n======================\n\nTo detect if a git url is a gcrypt repo, use::\n\n git-remote-gcrypt check url\n\n(Legacy syntax ``--check`` is also supported).\n\nExit status is 0' README.rst + @sed -i '/Cleaning gcrypt repos/,/Known issues/c\Cleaning gcrypt repos\n=====================\n\nTo scan for unencrypted files in a remote gcrypt repo, use::\n\n git-remote-gcrypt clean [url|remote]\n\nIf no URL or remote is specified, ``git-remote-gcrypt`` will list all\navailable ``gcrypt::`` remotes.\n\nBy default, this command only performs a scan. To actually remove the\nunencrypted files, you must use the ``--force`` (or ``-f``) flag::\n\n git-remote-gcrypt clean url --force\n\nKnown issues' README.rst + @$(call print_success,Generated.) diff --git a/README.rst b/README.rst index dc3900a..1753636 100644 --- a/README.rst +++ b/README.rst @@ -262,7 +262,7 @@ To detect if a git url is a gcrypt repo, use:: (Legacy syntax ``--check`` is also supported). -Exit status is 0 if the repo exists and can be decrypted, 1 if the repo +Exit status is 0 uses gcrypt but could not be decrypted, and 100 if the repo is not encrypted with gcrypt (or could not be accessed). diff --git a/completions/bash/git-remote-gcrypt b/completions/bash/git-remote-gcrypt index b401311..93bcd15 100644 --- a/completions/bash/git-remote-gcrypt +++ b/completions/bash/git-remote-gcrypt @@ -7,7 +7,7 @@ _git_remote_gcrypt() { cur="${COMP_WORDS[COMP_CWORD]}" prev="${COMP_WORDS[COMP_CWORD - 1]}" opts="-h --help -v --version --check" - commands="capabilities list push fetch check clean" + commands="capabilities check clean fetch list push" # If we're after a subcommand, only offer -h/--help if [[ " $commands " =~ " ${COMP_WORDS[1]:-} " ]]; then diff --git a/completions/fish/git-remote-gcrypt.fish b/completions/fish/git-remote-gcrypt.fish index 2560ccb..613c89c 100644 --- a/completions/fish/git-remote-gcrypt.fish +++ b/completions/fish/git-remote-gcrypt.fish @@ -6,14 +6,14 @@ complete -c git-remote-gcrypt -s v -l version -d 'Show version information' complete -c git-remote-gcrypt -l check -d '(Legacy) Check if URL is a gcrypt repository' -r -F # Subcommands -complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities list push fetch check clean" -a 'check' -d 'Check if URL is a gcrypt repository' -complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities list push fetch check clean" -a 'clean' -d 'Scan/Clean unencrypted files from remote' +complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities check clean fetch list push" -a 'check' -d 'Check if URL is a gcrypt repository' +complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities check clean fetch list push" -a 'clean' -d 'Scan/Clean unencrypted files from remote' # Clean flags -complete -c git-remote-gcrypt -f -n "__fish_seen_subcommand_from clean" -s f -l force -d 'Actually delete files during clean' +complete -c git-remote-gcrypt -f -n "__fish_seen_subcommand_from capabilities check clean fetch list push" -s f -l force -d 'Actually delete files during clean' # Git protocol commands -complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities list push fetch check clean" -a 'capabilities' -d 'Show git remote helper capabilities' -complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities list push fetch check clean" -a 'list' -d 'List refs in remote repository' -complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities list push fetch check clean" -a 'push' -d 'Push refs to remote repository' -complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities list push fetch check clean" -a 'fetch' -d 'Fetch refs from remote repository' +complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities check clean fetch list push" -a 'capabilities' -d 'Show git remote helper capabilities' +complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities check clean fetch list push" -a 'list' -d 'List refs in remote repository' +complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities check clean fetch list push" -a 'push' -d 'Push refs to remote repository' +complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from capabilities check clean fetch list push" -a 'fetch' -d 'Fetch refs from remote repository' diff --git a/completions/zsh/_git-remote-gcrypt b/completions/zsh/_git-remote-gcrypt index 86c5d83..d8001b1 100644 --- a/completions/zsh/_git-remote-gcrypt +++ b/completions/zsh/_git-remote-gcrypt @@ -8,7 +8,7 @@ _git_remote_gcrypt() { '(- *)'{-h,--help}'[show help message]' '(- *)'{-v,--version}'[show version information]' '--check[check if URL is a gcrypt repository]:URL:_files' - '1:command:(capabilities list push fetch check clean)' + '1:command:(capabilities check clean fetch list push)' '*::subcommand arguments:->args' ) _arguments -s -S $args diff --git a/git-remote-gcrypt b/git-remote-gcrypt index 0fd697b..7e0041d 100755 --- a/git-remote-gcrypt +++ b/git-remote-gcrypt @@ -34,9 +34,7 @@ VERSION="@@DEV_VERSION@@" # Help function -show_help() { - cat >&2 <&2; exit 1 ;; - *) - if [ -z "$URL" ]; then - URL="$1" - else - echo "Error: Multiple URLs/remotes provided to clean" >&2 - exit 1 - fi - ;; - esac - shift - done - ;; - help|--help|-h) - show_help - exit 0 - ;; - version|--version|-v) - echo "git-remote-gcrypt version $VERSION" >&2 - exit 0 - ;; -esac +# Help function +show_help() { + echo "$HELP_TEXT" >&2 +} -# Parse flags -while getopts "hv-:" opt; do - case "$opt" in - h) +# Parse arguments +while [ $# -gt 0 ]; do + case "$1" in + help|--help|-h) show_help exit 0 ;; - v) + version|--version|-v) echo "git-remote-gcrypt version $VERSION" >&2 exit 0 ;; - -) - # Handle long options - case "$OPTARG" in - help) - show_help - exit 0 - ;; - version) - echo "git-remote-gcrypt version $VERSION" >&2 - exit 0 - ;; - check) - # Allow check to pass through to the main logic at the bottom - ;; - clean) - # Allow clean to pass through to the main logic at the bottom - ;; - *) - echo "Unknown option: --$OPTARG" >&2 - exit 1 - ;; - esac + check|--check) + NAME=gcrypt-check + URL="$2" + shift ;; - *) + clean) + NAME=gcrypt-clean + shift + while [ $# -gt 0 ]; do + case "$1" in + --force|-f) FORCE_CLEAN=yes ;; + -*) echo "Unknown option: $1" >&2; exit 1 ;; + *) + if [ -z "$URL" ]; then + URL="$1" + else + echo "Error: Multiple URLs/remotes provided to clean" >&2 + exit 1 + fi + ;; + esac + shift + done + break # Stop parsing outer loop + ;; + -*) + echo "Unknown option: $1" >&2 exit 1 ;; + *) + # Assume it's the remote name if not already set + if [ -z "$NAME" ]; then + NAME="$1" + elif [ -z "$URL" ]; then + URL="$1" + fi + ;; esac + shift || : done -# Handle subcommand help (e.g., git-remote-gcrypt capabilities --help) -shift $((OPTIND - 1)) +# If NAME is not set, we might be invoked as a remote helper +if [ -z "$NAME" ]; then + show_help + exit 1 +fi case "${1:-}" in capabilities) if [ "${2:-}" = "-h" ] || [ "${2:-}" = "--help" ]; then @@ -251,7 +230,10 @@ xgrep() { command grep "$@" || : ; } # Resolve URL or remote name, or list remotes if empty resolve_url() { local cmd="$1" - if [ -z "$URL" ]; then + if [ -z "$URL" ] || [ "$URL" = "$cmd" ]; then + # Handle cases where shift might have messed up or URL was empty + if [ "$URL" = "$cmd" ]; then URL=""; fi + local remotes remotes=$(git remote -v | grep 'gcrypt::' | awk '{print $1}' | sort -u || :) echo "Usage: git-remote-gcrypt $cmd [URL|REMOTE]" >&2 @@ -263,10 +245,13 @@ resolve_url() { fi exit 1 fi - if ! echo "$URL" | grep -q -E '://|::'; then + + # If it's not a URL, try to resolve as a remote name + if ! echo "$URL" | grep -q -E '://|::' || [ -n "${URL##*/*}" ]; then local potential_url potential_url=$(git config --get "remote.$URL.url" || :) if [ -n "$potential_url" ]; then + print_debug "Resolved remote '$URL' to '$potential_url'" URL="$potential_url" fi fi @@ -761,33 +746,24 @@ ensure_connected() # EARLY SAFETY CHECK for gitception backends: # Before GPG validation, check if the remote has unencrypted files. - # This prevents the GPG error from masking the privacy leak warning. - # Skip this check if we are explicitly running the clean command. if [ "$NAME" != "gcrypt-clean" ] && ! isurl sftp "$URL" && ! isurl rsync "$URL" && ! isurl rclone "$URL" && ! islocalrepo "$URL"; then - # It's a gitception backend - do early safety check - # Fetch the default branch to see what files exist local check_files="" git fetch --quiet "$URL" "refs/heads/master:refs/gcrypt/safety-check" 2>/dev/null || git fetch --quiet "$URL" "refs/heads/main:refs/gcrypt/safety-check" 2>/dev/null || true if git rev-parse --verify "refs/gcrypt/safety-check" >/dev/null 2>&1; then check_files=$(git ls-tree --name-only "refs/gcrypt/safety-check" 2>/dev/null || :) - # Clean up the temp ref git update-ref -d "refs/gcrypt/safety-check" 2>/dev/null || true if isnonnull "$check_files"; then - # Check if ANY file doesn't match gcrypt pattern (hash filenames) early_bad_files=$(echo "$check_files" | grep -v -E '^[a-f0-9]{56}$|^[a-f0-9]{64}$|^[a-f0-9]{96}$|^[a-f0-9]{128}$' || :) if isnonnull "$early_bad_files"; then - # Check config to see if we should ignore if [ "$(git config --bool gcrypt.allow-unencrypted-remote)" != "true" ]; then echo_info "ERROR: Remote repository contains unencrypted or unknown files!" echo_info "To protect your privacy, git-remote-gcrypt will NOT push to this remote." - echo_info "Found the following unexpected files:" - echo_info "$early_bad_files" | head -n 5 | sed 's/^/ /' >&2 - echo_info "" - echo_info "To fix: use 'git-remote-gcrypt clean --force $URL' to remove these files," - echo_info "or set 'git config gcrypt.allow-unencrypted-remote true' to ignore." + echo_info "Found unexpected files: $(echo "$early_bad_files" | head -n 3 | tr '\n' ' ')" + echo_info "To see full list of unexpected files, use: git-remote-gcrypt clean --force $URL" + echo_info "To fix and remove these files, use: git-remote-gcrypt clean --force $URL" exit 1 fi fi @@ -1441,5 +1417,5 @@ elif [ "$1" = --version ] || [ "$1" = -v ]; then echo "git-remote-gcrypt version $VERSION" exit 0 else - gcrypt_main_loop "$@" + gcrypt_main_loop "$NAME" "$URL" fi diff --git a/tests/sync_docs.py b/tests/sync_docs.py new file mode 100755 index 0000000..c3a6969 --- /dev/null +++ b/tests/sync_docs.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +import re +import os +import sys + +def extract_help_text(script_path): + with open(script_path, 'r') as f: + content = f.read() + + match = re.search(r'HELP_TEXT="(.*?)"', content, re.DOTALL) + if not match: + print("Error: Could not find HELP_TEXT in git-remote-gcrypt", file=sys.stderr) + sys.exit(1) + return match.group(1) + +def parse_commands(help_text): + commands = [] + # Look for lines starting with lowercase words in the Options: or Git Protocol Commands sections + lines = help_text.split('\n') + capture = False + for line in lines: + line = line.strip() + if line.startswith('Options:') or line.startswith('Git Protocol Commands'): + capture = True + continue + if line.startswith('Environment Variables:'): + capture = False + continue + + if capture and line: + # Match lines like "check [URL] Description" or "capabilities Description" + match = re.match(r'^([a-z-]+)(\s+.*)?$', line) + if match: + cmd = match.group(1) + if cmd not in ['help', 'version']: + commands.append(cmd) + return sorted(list(set(commands))) +def update_bash_completion(path, commands): + if not os.path.exists(path): return + with open(path, 'r') as f: content = f.read() + cmd_str = ' '.join(commands) + new_content = re.sub(r'commands="[^"]+"', f'commands="{cmd_str}"', content) + with open(path, 'w') as f: f.write(new_content) + +def update_zsh_completion(path, commands): + if not os.path.exists(path): return + with open(path, 'r') as f: content = f.read() + cmd_str = ' '.join(commands) + # Match 1:command:(capabilities list push fetch check clean) + new_content = re.sub(r'1:command:\([^)]+\)', f'1:command:({cmd_str})', content) + with open(path, 'w') as f: f.write(new_content) + +def update_fish_completion(path, commands): + if not os.path.exists(path): return + with open(path, 'r') as f: content = f.read() + # Replace the list in "not __fish_seen_subcommand_from ..." + cmd_str = ' '.join(commands) + new_content = re.sub(r'not __fish_seen_subcommand_from [^"]+', f'not __fish_seen_subcommand_from {cmd_str}', content) + with open(path, 'w') as f: f.write(new_content) + +def main(): + script_dir = os.path.dirname(os.path.abspath(__file__)) + root_dir = os.path.dirname(script_dir) + script_path = os.path.join(root_dir, 'git-remote-gcrypt') + + help_text = extract_help_text(script_path) + commands = parse_commands(help_text) + + # We always want protocol commands in completions too + comp_commands = sorted(list(set(commands + ['capabilities', 'list', 'push', 'fetch']))) + + print(f"Detected commands: {' '.join(comp_commands)}") + + update_bash_completion(os.path.join(root_dir, 'completions/bash/git-remote-gcrypt'), comp_commands) + update_zsh_completion(os.path.join(root_dir, 'completions/zsh/_git-remote-gcrypt'), comp_commands) + update_fish_completion(os.path.join(root_dir, 'completions/fish/git-remote-gcrypt.fish'), comp_commands) + + print("Completions updated.") + +if __name__ == "__main__": + main()