add command to list/stat remote; update completions
authorShane Jaroch <chown_tee@proton.me>
Sat, 17 Jan 2026 01:00:51 +0000 (20:00 -0500)
committerShane Jaroch <chown_tee@proton.me>
Sat, 17 Jan 2026 01:00:51 +0000 (20:00 -0500)
README.rst
completions/bash/git-remote-gcrypt
completions/fish/git-remote-gcrypt.fish
completions/gen_docs.sh
completions/templates/bash.in
completions/templates/fish.in
completions/templates/zsh.in
completions/zsh/_git-remote-gcrypt
git-remote-gcrypt
tests/test-completions.sh [new file with mode: 0644]
utils/gen_docs.sh

index ca4fed99fe1fbc614f7e8e5bc3f3011a3335a521..134d4a466ec995a1d39e9ed788313deed9bca47b 100644 (file)
@@ -62,7 +62,7 @@ Command Reference
       clean [URL|REMOTE] Scan/Clean unencrypted files from remote
         clean -f, --force    Actually delete files (default is scan only)
         clean -i, --init     Scan even if no manifest found (DANGEROUS with --force)
-    
+      stat [URL|REMOTE] Show diagnostics (file counts, tracked vs untracked)
     Git Protocol Commands (for debugging):
       capabilities     List remote helper capabilities
       list             List refs in remote repository
index 3479aa508b62fcb893a097df0f736ab4c46d7301..a9fe8f1803685083d1c0f1ba128184aa61cd605f 100644 (file)
@@ -7,7 +7,7 @@ _git_remote_gcrypt() {
        cur="${COMP_WORDS[COMP_CWORD]}"
        prev="${COMP_WORDS[COMP_CWORD - 1]}"
        opts="-h --help -v --version"
-       commands="capabilities check clean fetch list push"
+       commands="check clean stat"
 
        # 1. First argument: complete commands and global options
        if [[ $COMP_CWORD -eq 1 ]]; then
@@ -22,10 +22,10 @@ _git_remote_gcrypt() {
        case "${COMP_WORDS[1]}" in
                clean)
                        local remotes=$( git remote -v 2>/dev/null | grep 'gcrypt::' | awk '{print $1}' | sort -u || : )
-                       COMPREPLY=( $( compgen -W " $remotes" -- "$cur" ) )
+                       COMPREPLY=( $( compgen -W "-f --force -i --init $remotes" -- "$cur" ) )
                        return 0
                        ;;
-               check)
+               check|stat)
                        local remotes=$( git remote 2>/dev/null || : )
                        COMPREPLY=( $( compgen -W "$remotes" -- "$cur" ) )
                        return 0
index c5441aab5c1fb9cb905971db3b1fb62d7cb14236..583101a1ba3fe7e91a1d86673ce4c917ef68489c 100644 (file)
@@ -5,16 +5,14 @@ complete -c git-remote-gcrypt -s h -l help -d 'Show help message'
 complete -c git-remote-gcrypt -s v -l version -d 'Show version information'
 
 # Subcommands
-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'
+complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from check clean stat" -a 'check' -d 'Check if URL is a gcrypt repository'
+complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from check clean stat" -a 'clean' -d 'Scan/Clean unencrypted files from remote'
+complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from check clean stat" -a 'stat' -d 'Show diagnostics'
 complete -c git-remote-gcrypt -n "__fish_seen_subcommand_from clean" -a "(git remote -v 2>/dev/null | grep 'gcrypt::' | awk '{print \$1}' | sort -u)" -d 'Gcrypt Remote'
 complete -c git-remote-gcrypt -n "__fish_seen_subcommand_from check" -a "(git remote 2>/dev/null)" -d 'Git Remote'
+complete -c git-remote-gcrypt -n "__fish_seen_subcommand_from stat" -a "(git remote 2>/dev/null)" -d 'Git Remote'
 
 # Clean flags
+complete -c git-remote-gcrypt -f -n "__fish_seen_subcommand_from clean" -s f -l force -d 'Flag';
+complete -c git-remote-gcrypt -f -n "__fish_seen_subcommand_from clean" -s i -l init -d 'Flag';
 
-
-# Git protocol commands
-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'
index 85691ff1e49b3e361739eb88f06f4a6971c194e7..93b2a921e01bbafc939a891d34db25c32fbb8824 100755 (executable)
@@ -33,12 +33,12 @@ COMMANDS_HELP=$(echo "$RAW_HELP" | sed -n '/^Options:/,$p' | sed 's/^/    /')
 
 # 2. Parse Commands and Flags for Completions
 # Extract command names (first word after 2 spaces)
-COMMANDS_LIST=$(echo "$RAW_HELP" | awk '/^  [a-z]+ / {print $1}' | grep -vE "^(help|version)$" | sort | tr '\n' ' ' | sed 's/ $//')
+COMMANDS_LIST=$(echo "$RAW_HELP" | awk '/^  [a-z]+ / {print $1}' | grep -vE "^(help|version|capabilities|list|push|fetch)$" | sort | tr '\n' ' ' | sed 's/ $//')
 
 # Extract clean flags
 # Text: "    clean -f, --force    Actually delete files..."
 # We want: "-f --force -i --init" for Bash
-CLEAN_FLAGS_RAW=$(echo "$RAW_HELP" | grep "^    clean -" | awk -F'  ' '{print $2}' | sed 's/,//g')
+CLEAN_FLAGS_RAW=$(echo "$RAW_HELP" | grep "^    clean -" | awk '{print $2, $3}' | sed 's/,//g')
 CLEAN_FLAGS_BASH=$(echo "$CLEAN_FLAGS_RAW" | tr '\n' ' ' | sed 's/ $//')
 
 # For Zsh: we want simple list for now as per plan, user asked for dynamic but safe.
index dcfe0cbee0087273938e5d24d8d7bb2c8079cf7c..dcf66f8179243b5ae926bba2468f8cae4fcef6a1 100644 (file)
@@ -25,7 +25,7 @@ _git_remote_gcrypt() {
                        COMPREPLY=( $( compgen -W "{clean_flags_bash} $remotes" -- "$cur" ) )
                        return 0
                        ;;
-               check)
+               check|stat)
                        local remotes=$( git remote 2>/dev/null || : )
                        COMPREPLY=( $( compgen -W "$remotes" -- "$cur" ) )
                        return 0
index f8f187c39d0035c7c18627a466453db51561caf9..be8fa0a14eecd83d60a4d07bf70bfe487ef7adf8 100644 (file)
@@ -7,14 +7,10 @@ complete -c git-remote-gcrypt -s v -l version -d 'Show version information'
 # Subcommands
 complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from {not_sc_list}" -a 'check' -d 'Check if URL is a gcrypt repository'
 complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from {not_sc_list}" -a 'clean' -d 'Scan/Clean unencrypted files from remote'
+complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from {not_sc_list}" -a 'stat' -d 'Show diagnostics'
 complete -c git-remote-gcrypt -n "__fish_seen_subcommand_from clean" -a "(git remote -v 2>/dev/null | grep 'gcrypt::' | awk '{print \$1}' | sort -u)" -d 'Gcrypt Remote'
 complete -c git-remote-gcrypt -n "__fish_seen_subcommand_from check" -a "(git remote 2>/dev/null)" -d 'Git Remote'
+complete -c git-remote-gcrypt -n "__fish_seen_subcommand_from stat" -a "(git remote 2>/dev/null)" -d 'Git Remote'
 
 # Clean flags
 {clean_flags_fish}
-
-# Git protocol commands
-complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from {not_sc_list}" -a 'capabilities' -d 'Show git remote helper capabilities'
-complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from {not_sc_list}" -a 'list' -d 'List refs in remote repository'
-complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from {not_sc_list}" -a 'push' -d 'Push refs to remote repository'
-complete -c git-remote-gcrypt -f -n "not __fish_seen_subcommand_from {not_sc_list}" -a 'fetch' -d 'Fetch refs from remote repository'
index 60641e34fbb785cedaa94aca9aa28180f29b390b..69bac26ef73e206205a61772511d6284b680fdb1 100644 (file)
@@ -17,7 +17,7 @@ _git_remote_gcrypt() {
                _arguments {clean_flags_zsh} \
                        '*:gcrypt URL: _alternative "remotes:gcrypt remote:($(git remote -v 2>/dev/null | grep "gcrypt::" | awk "{print \$1}" | sort -u))" "files:file:_files"'
                ;;
-       check)
+       check|stat)
                _arguments \
                        '*:gcrypt URL: _alternative "remotes:git remote:($(git remote 2>/dev/null))" "files:file:_files"'
                ;;
index 14a84003ec6aa27276d85ce6633515fb4ad77e56..acda347826bb8383305f74cf25eb513f4951355f 100644 (file)
@@ -7,17 +7,17 @@ _git_remote_gcrypt() {
        args=(
                '(- *)'{-h,--help}'[show help message]'
                '(- *)'{-v,--version}'[show version information]'
-               '1:command:(capabilities check clean fetch list push)'
+               '1:command:(check clean stat)'
                '*::subcommand arguments:->args'
        )
        _arguments -s -S $args
 
        case $line[1] in
        clean)
-               _arguments  \
+               _arguments '(-f --force -i --init)'{-f,--force,-i,--init}'[flag]' \
                        '*:gcrypt URL: _alternative "remotes:gcrypt remote:($(git remote -v 2>/dev/null | grep "gcrypt::" | awk "{print \$1}" | sort -u))" "files:file:_files"'
                ;;
-       check)
+       check|stat)
                _arguments \
                        '*:gcrypt URL: _alternative "remotes:git remote:($(git remote 2>/dev/null))" "files:file:_files"'
                ;;
index 334c44c4b8752f1ec3f896148e5084b767414d87..3bc1970fee26e50ff498890f040277d4af08b265 100755 (executable)
@@ -48,7 +48,7 @@ Options:
   clean [URL|REMOTE] Scan/Clean unencrypted files from remote
     clean -f, --force    Actually delete files (default is scan only)
     clean -i, --init     Scan even if no manifest found (DANGEROUS with --force)
-
+  stat [URL|REMOTE] Show diagnostics (file counts, tracked vs untracked)
 Git Protocol Commands (for debugging):
   capabilities     List remote helper capabilities
   list             List refs in remote repository
@@ -102,6 +102,19 @@ while [ $# -gt 0 ]; do
                        done
                        break # Stop parsing outer loop
                        ;;
+               stat)
+                       NAME=gcrypt-stat
+                       shift
+                       if [ $# -gt 0 ]; then
+                               if [ -z "$URL" ]; then
+                                       URL="$1"
+                               else
+                                       echo "Error: Multiple URLs/remotes provided to stat" >&2
+                                       exit 1
+                               fi
+                               shift
+                       fi
+                       ;;
                -*)
                        echo "Unknown option: $1" >&2
                        exit 1
@@ -1510,6 +1523,54 @@ cmd_clean()
        exit 0
 }
 
+cmd_stat()
+{
+       local remote_files="" file_count=0 tracked_count=0 untracked_count=0 valid_files="" f=""
+       
+       if ! ensure_connected; then
+               echo_die "Could not connect to $URL."
+       fi
+       
+       get_remote_file_list @remote_files || echo_die "Failed to list remote files."
+       file_count=$(line_count "$remote_files")
+       if isnull "$remote_files"; then
+               file_count=0
+       fi
+       
+       echo "Remote: $URL"
+       echo "Total files: $file_count"
+       
+       if [ "$Did_find_repo" = "yes" ]; then
+               echo "Gcrypt repository: detected"
+               echo "Manifest: found"
+               
+               # Build whitelist of valid gcrypt files to count tracked
+               valid_files="$Manifestfile"
+               for f in $(xecho "$Packlist" | cut -d: -f3 | cut -d' ' -f1); do
+                       valid_files="$valid_files$Newline$f"
+               done
+               
+               tracked_count=0
+               for f in $remote_files; do
+                       if xfeed "$valid_files" grep -qxF "$f"; then
+                               tracked_count=$((tracked_count + 1))
+                       fi
+               done
+               untracked_count=$((file_count - tracked_count))
+               
+               echo "Tracked files: $tracked_count"
+               echo "Untracked files: $untracked_count"
+       else
+               echo "Gcrypt repository: not detected (no manifest)"
+               echo "Tracked files: 0"
+               echo "Untracked files: $file_count"
+       fi
+       
+       CLEAN_FINAL "$URL"
+       git remote remove "$NAME" 2>/dev/null || true
+       exit 0
+}
+
 if [ "$NAME" = "gcrypt-check" ]; then
        resolve_url check
        echo_info "Checking remote: $URL"
@@ -1526,6 +1587,11 @@ elif [ "$NAME" = "gcrypt-clean" ]; then
        echo_info "Checking remote: $URL"
        setup
        cmd_clean
+elif [ "$NAME" = "gcrypt-stat" ]; then
+       resolve_url stat
+       echo_info "Getting statistics for remote: $URL"
+       setup
+       cmd_stat
 elif [ "$1" = --version ] || [ "$1" = -v ]; then
        echo "git-remote-gcrypt version $VERSION"
        exit 0
diff --git a/tests/test-completions.sh b/tests/test-completions.sh
new file mode 100644 (file)
index 0000000..0aa9466
--- /dev/null
@@ -0,0 +1,104 @@
+#!/bin/bash
+# tests/test-completions.sh
+# Verifies that bash completion script offers correct commands and excludes plumbing.
+
+# Mock commands if necessary?
+# The completion script calls `git remote` etc. We can mock git.
+
+# Setup
+TEST_DIR=$(dirname "$0")
+COMP_FILE="$TEST_DIR/../completions/bash/git-remote-gcrypt"
+
+if [ ! -f "$COMP_FILE" ]; then
+       echo "FAIL: Completion file not found at $COMP_FILE"
+       exit 1
+fi
+
+# shellcheck source=/dev/null
+source "$COMP_FILE"
+
+# Mock variables used by completion
+COMP_WORDS=()
+COMP_CWORD=0
+COMPREPLY=()
+
+# --- Mock git ---
+# shellcheck disable=SC2329
+git() {
+       if [[ $1 == "remote" ]]; then
+               echo "origin"
+               echo "backup"
+       fi
+}
+export -f git
+
+# --- Helper to run completion ---
+run_completion() {
+       COMP_WORDS=("$@")
+       COMP_CWORD=$((${#COMP_WORDS[@]} - 1))
+       COMPREPLY=()
+       _git_remote_gcrypt
+}
+
+# --- Tests ---
+
+FAILURES=0
+
+echo "Test 1: Top-level commands (git-remote-gcrypt [TAB])"
+run_completion "git-remote-gcrypt" ""
+# Expect: check clean stat -h --help -v --version
+# Expect NO: capabilities list push fetch
+EXPECTED="check clean stat"
+FORBIDDEN="capabilities list push fetch"
+
+OUTPUT="${COMPREPLY[*]}"
+
+for cmd in $EXPECTED; do
+       if [[ ! $OUTPUT =~ $cmd ]]; then
+               echo "  FAIL: Expected '$cmd' in completion output."
+               FAILURES=$((FAILURES + 1))
+       fi
+done
+
+for cmd in $FORBIDDEN; do
+       if [[ $OUTPUT =~ $cmd ]]; then
+               echo "  FAIL: Forbidden '$cmd' found in completion output."
+               FAILURES=$((FAILURES + 1))
+       fi
+done
+
+if [[ $OUTPUT =~ check ]] && [[ ! $OUTPUT =~ capabilities ]]; then
+       echo "  PASS: Top-level commands look correct."
+fi
+
+echo "Test 2: 'stat' subcommand (git-remote-gcrypt stat [TAB])"
+run_completion "git-remote-gcrypt" "stat" ""
+# Should complete remotes (mocked as origin backup)
+OUTPUT="${COMPREPLY[*]}"
+if [[ $OUTPUT =~ "origin" ]] && [[ $OUTPUT =~ "backup" ]]; then
+       echo "  PASS: 'stat' completes remotes."
+else
+       echo "  FAIL: 'stat' did not complete remotes. Got: $OUTPUT"
+       FAILURES=$((FAILURES + 1))
+fi
+
+echo "Test 3: 'clean' subcommand flags (git-remote-gcrypt clean [TAB])"
+run_completion "git-remote-gcrypt" "clean" ""
+# Should have -f --force etc.
+OUTPUT="${COMPREPLY[*]}"
+if [[ $OUTPUT =~ "--force" ]] && [[ $OUTPUT =~ "--init" ]]; then
+       echo "  PASS: 'clean' completes flags."
+else
+       echo "  FAIL: 'clean' missing flags. Got: $OUTPUT"
+       FAILURES=$((FAILURES + 1))
+fi
+
+if [ "$FAILURES" -eq 0 ]; then
+       echo "--------------------------"
+       echo "All completion tests passed."
+       exit 0
+else
+       echo "--------------------------"
+       echo "$FAILURES completion tests FAILED."
+       exit 1
+fi
index 7e2925651b45e8791bf6dcb23ce4a4fecd81d1b2..d14b84d3b8c55e41315040e738a6911f922460fe 100755 (executable)
@@ -33,12 +33,12 @@ COMMANDS_HELP=$(echo "$RAW_HELP" | sed -n '/^Options:/,$p' | sed 's/^/    /' | s
 
 # 2. Parse Commands and Flags for Completions
 # Extract command names (first word after 2 spaces)
-COMMANDS_LIST=$(echo "$RAW_HELP" | awk '/^  [a-z]+ / {print $1}' | grep -vE "^(help|version)$" | sort | tr '\n' ' ' | sed 's/ $//')
+COMMANDS_LIST=$(echo "$RAW_HELP" | awk '/^  [a-z]+ / {print $1}' | grep -vE "^(help|version|capabilities|list|push|fetch)$" | sort | tr '\n' ' ' | sed 's/ $//')
 
 # Extract clean flags
 # Text: "    clean -f, --force    Actually delete files..."
 # We want: "-f --force -i --init" for Bash
-CLEAN_FLAGS_RAW=$(echo "$RAW_HELP" | grep "^    clean -" | awk -F'  ' '{print $2}' | sed 's/,//g')
+CLEAN_FLAGS_RAW=$(echo "$RAW_HELP" | grep "^    clean -" | awk '{print $2, $3}' | sed 's/,//g')
 CLEAN_FLAGS_BASH=$(echo "$CLEAN_FLAGS_RAW" | tr '\n' ' ' | sed 's/ $//')
 
 # For Zsh: we want simple list for now as per plan, user asked for dynamic but safe.
@@ -68,7 +68,8 @@ for line in $CLEAN_FLAGS_RAW; do
        short=$(echo "$line" | awk '{print $1}' | sed 's/-//')
        long=$(echo "$line" | awk '{print $2}' | sed 's/--//')
        # Escape quotes if needed (none usually)
-       CLEAN_FLAGS_FISH="${CLEAN_FLAGS_FISH}complete -c git-remote-gcrypt -f -n \"__fish_seen_subcommand_from clean\" -s $short -l $long -d 'Flag';\n"
+       CLEAN_FLAGS_FISH="${CLEAN_FLAGS_FISH}complete -c git-remote-gcrypt -f -n \"__fish_seen_subcommand_from clean\" -s $short -l $long -d 'Flag';
+"
 done
 unset IFS