From 7b6b07dc3aa94f0868a55fa487db91436397ea9f Mon Sep 17 00:00:00 2001 From: Shane Jaroch Date: Fri, 16 Jan 2026 21:56:06 -0500 Subject: [PATCH] wip --- Makefile | 14 +++++---- README.rst | 5 ++-- completions/bash/git-remote-gcrypt | 28 +++++++++--------- completions/fish/git-remote-gcrypt.fish | 5 ++-- completions/gen_docs.sh | 7 ++--- completions/templates/bash.in | 28 +++++++++--------- completions/templates/zsh.in | 2 +- completions/zsh/_git-remote-gcrypt | 4 +-- git-remote-gcrypt | 38 +++++++++++++++++++++---- tests/system-test-clean-command.sh | 17 +++++++++++ 10 files changed, 99 insertions(+), 49 deletions(-) diff --git a/Makefile b/Makefile index 0ba4d0a..02c5128 100644 --- a/Makefile +++ b/Makefile @@ -111,11 +111,15 @@ test/installer: ##H Test installer logic @mkdir -p $(COV_INSTALL) @export COV_DIR=$(COV_INSTALL); \ for test_script in tests/installer-test*.sh; do \ - kcov --bash-dont-parse-binary-dir \ - --include-pattern=install.sh \ - --exclude-path=$(PWD)/.git,$(PWD)/tests \ - $(COV_INSTALL) \ - "$$test_script" 2>&1 | tee -a .tmp/kcov.log; \ + if [ "$$test_script" = "tests/installer-test-logic.sh" ]; then \ + kcov --bash-dont-parse-binary-dir \ + --include-pattern=install.sh \ + --exclude-path=$(PWD)/.git,$(PWD)/tests \ + $(COV_INSTALL) \ + "$$test_script" 2>&1 | tee -a .tmp/kcov.log; \ + else \ + bash "$$test_script" 2>&1 | tee -a .tmp/kcov.log; \ + fi; \ done; \ if grep -q 'kcov: error:' .tmp/kcov.log; then \ echo "FAIL: kcov errors detected (see above)"; exit 1; \ diff --git a/README.rst b/README.rst index 134d4a4..b34465d 100644 --- a/README.rst +++ b/README.rst @@ -60,8 +60,9 @@ Command Reference version Show version information check [URL] Check if URL is a gcrypt repository 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) + clean --force Actually delete files (default is scan only) + clean --init Allow cleaning valid files (requires --force) + clean --hard Override safety checks (requires --force) stat [URL|REMOTE] Show diagnostics (file counts, tracked vs untracked) Git Protocol Commands (for debugging): capabilities List remote helper capabilities diff --git a/completions/bash/git-remote-gcrypt b/completions/bash/git-remote-gcrypt index a9fe8f1..6415eaf 100644 --- a/completions/bash/git-remote-gcrypt +++ b/completions/bash/git-remote-gcrypt @@ -20,20 +20,20 @@ _git_remote_gcrypt() { # 2. Handle subcommands case "${COMP_WORDS[1]}" in - clean) - local remotes=$( git remote -v 2>/dev/null | grep 'gcrypt::' | awk '{print $1}' | sort -u || : ) - COMPREPLY=( $( compgen -W "-f --force -i --init $remotes" -- "$cur" ) ) - return 0 - ;; - check|stat) - local remotes=$( git remote 2>/dev/null || : ) - COMPREPLY=( $( compgen -W "$remotes" -- "$cur" ) ) - return 0 - ;; - capabilities|fetch|list|push) - COMPREPLY=($(compgen -W "-h --help" -- "$cur")) - return 0 - ;; + clean) + local remotes=$(git remote -v 2>/dev/null | grep 'gcrypt::' | awk '{print $1}' | sort -u || :) + COMPREPLY=($(compgen -W "--force Actually --init Allow --hard Override $remotes" -- "$cur")) + return 0 + ;; + check | stat) + local remotes=$(git remote 2>/dev/null || :) + COMPREPLY=($(compgen -W "$remotes" -- "$cur")) + return 0 + ;; + capabilities | fetch | list | push) + COMPREPLY=($(compgen -W "-h --help" -- "$cur")) + return 0 + ;; esac # 3. Fallback (global flags if not in a known subcommand?) diff --git a/completions/fish/git-remote-gcrypt.fish b/completions/fish/git-remote-gcrypt.fish index 583101a..52496c1 100644 --- a/completions/fish/git-remote-gcrypt.fish +++ b/completions/fish/git-remote-gcrypt.fish @@ -13,6 +13,7 @@ complete -c git-remote-gcrypt -n "__fish_seen_subcommand_from check" -a "(git re 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'; +complete -c git-remote-gcrypt -f -n "__fish_seen_subcommand_from clean" -s -force -l Actually -d 'Flag'; +complete -c git-remote-gcrypt -f -n "__fish_seen_subcommand_from clean" -s -init -l Allow -d 'Flag'; +complete -c git-remote-gcrypt -f -n "__fish_seen_subcommand_from clean" -s -hard -l Override -d 'Flag'; diff --git a/completions/gen_docs.sh b/completions/gen_docs.sh index 93b2a92..23277e8 100755 --- a/completions/gen_docs.sh +++ b/completions/gen_docs.sh @@ -86,14 +86,13 @@ SAFE_CMDS=$(echo "$COMMANDS_LIST" | sed 's/ / /g') # just space separated # We'll read the template line by line? No, sed is standard. # We use a temp file for the replacement string to avoid sed escaping hell for large blocks? # Or just keep it simple. -sed "s/{commands}/$COMMANDS_LIST/" "$ZSH_TMPL" \ - | sed "s|{clean_flags_zsh}|$CLEAN_FLAGS_ZSH|" >"$ZSH_OUT" +sed "s/{commands}/$COMMANDS_LIST/" "$ZSH_TMPL" | + sed "s|{clean_flags_zsh}|$CLEAN_FLAGS_ZSH|" >"$ZSH_OUT" # 6. Generate Fish echo "Generating Fish completions..." # Fish needs {not_sc_list} which matches {commands} (space separated) -sed "s/{not_sc_list}/$COMMANDS_LIST/g" "$FISH_TMPL" \ - | +sed "s/{not_sc_list}/$COMMANDS_LIST/g" "$FISH_TMPL" | # Multi-line replacement in sed is hard. Use awk? # Or just injecting the string with escaped newlines. sed "s|{clean_flags_fish}|$CLEAN_FLAGS_FISH|" >"$FISH_OUT" diff --git a/completions/templates/bash.in b/completions/templates/bash.in index dcf66f8..a6b6985 100644 --- a/completions/templates/bash.in +++ b/completions/templates/bash.in @@ -20,20 +20,20 @@ _git_remote_gcrypt() { # 2. Handle subcommands case "${COMP_WORDS[1]}" in - clean) - local remotes=$( git remote -v 2>/dev/null | grep 'gcrypt::' | awk '{print $1}' | sort -u || : ) - COMPREPLY=( $( compgen -W "{clean_flags_bash} $remotes" -- "$cur" ) ) - return 0 - ;; - check|stat) - local remotes=$( git remote 2>/dev/null || : ) - COMPREPLY=( $( compgen -W "$remotes" -- "$cur" ) ) - return 0 - ;; - capabilities|fetch|list|push) - COMPREPLY=($(compgen -W "-h --help" -- "$cur")) - return 0 - ;; + clean) + local remotes=$(git remote -v 2>/dev/null | grep 'gcrypt::' | awk '{print $1}' | sort -u || :) + COMPREPLY=($(compgen -W "{clean_flags_bash} $remotes" -- "$cur")) + return 0 + ;; + check | stat) + local remotes=$(git remote 2>/dev/null || :) + COMPREPLY=($(compgen -W "$remotes" -- "$cur")) + return 0 + ;; + capabilities | fetch | list | push) + COMPREPLY=($(compgen -W "-h --help" -- "$cur")) + return 0 + ;; esac # 3. Fallback (global flags if not in a known subcommand?) diff --git a/completions/templates/zsh.in b/completions/templates/zsh.in index 69bac26..0a44fe1 100644 --- a/completions/templates/zsh.in +++ b/completions/templates/zsh.in @@ -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|stat) + check | stat) _arguments \ '*:gcrypt URL: _alternative "remotes:git remote:($(git remote 2>/dev/null))" "files:file:_files"' ;; diff --git a/completions/zsh/_git-remote-gcrypt b/completions/zsh/_git-remote-gcrypt index acda347..415c3d1 100644 --- a/completions/zsh/_git-remote-gcrypt +++ b/completions/zsh/_git-remote-gcrypt @@ -14,10 +14,10 @@ _git_remote_gcrypt() { case $line[1] in clean) - _arguments '(-f --force -i --init)'{-f,--force,-i,--init}'[flag]' \ + _arguments '(--force Actually --init Allow --hard Override)'{--force,Actually,--init,Allow,--hard,Override}'[flag]' \ '*:gcrypt URL: _alternative "remotes:gcrypt remote:($(git remote -v 2>/dev/null | grep "gcrypt::" | awk "{print \$1}" | sort -u))" "files:file:_files"' ;; - check|stat) + check | stat) _arguments \ '*:gcrypt URL: _alternative "remotes:git remote:($(git remote 2>/dev/null))" "files:file:_files"' ;; diff --git a/git-remote-gcrypt b/git-remote-gcrypt index 91e526b..97bcc8b 100755 --- a/git-remote-gcrypt +++ b/git-remote-gcrypt @@ -46,8 +46,9 @@ Options: version Show version information check [URL] Check if URL is a gcrypt repository 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) + clean --force Actually delete files (default is scan only) + clean --init Allow cleaning valid files (requires --force) + clean --hard Override safety checks (requires --force) stat [URL|REMOTE] Show diagnostics (file counts, tracked vs untracked) Git Protocol Commands (for debugging): capabilities List remote helper capabilities @@ -86,8 +87,9 @@ while [ $# -gt 0 ]; do shift while [ $# -gt 0 ]; do case "$1" in - --force|-f) FORCE_CLEAN=yes ;; - --init|-i) FORCE_INIT=yes ;; + --force) FORCE_CLEAN=yes ;; + --hard) HARD_FORCE=yes ;; + --init) FORCE_INIT=yes ;; -*) echo "Unknown option: $1" >&2; exit 1 ;; *) if [ -z "$URL" ]; then @@ -100,6 +102,15 @@ while [ $# -gt 0 ]; do esac shift done + if [ -n "$HARD_FORCE" ] && [ -z "$FORCE_CLEAN" ]; then + echo "Error: --hard requires --force" >&2 + exit 1 + fi + if [ "${FORCE_INIT:-}" = "yes" ] && [ -z "$FORCE_CLEAN" ]; then + echo "Error: --init requires --force" >&2 + echo "(To scan a repository, use 'clean' without arguments)" >&2 + exit 1 + fi break # Stop parsing outer loop ;; stat) @@ -415,7 +426,24 @@ gitception_remove() ( export GIT_INDEX_FILE="$temp_index" git read-tree "$Gref" - git rm --cached --ignore-unmatch -q "$2" + if [ -n "$HARD_FORCE" ]; then + git rm --cached --ignore-unmatch -q -f "$2" + else + if ! git rm --cached --ignore-unmatch -q "$2"; then + echo_info "Error: Failed to remove '$2' because it has local modifications." + echo_info "Manual removal command: git rm --cached -f '$2'" + + suggest_args="--force --hard" + # If we are in init mode (FORCE_INIT set), the user MUST pass --init again + if [ "${FORCE_INIT:-}" = "yes" ]; then + suggest_args="--init $suggest_args" + fi + + echo_info "To force remove via tool, run:" + echo_info " git-remote-gcrypt clean $suggest_args $URL" + exit 1 + fi + fi tree_id=$(git write-tree) if [ "$tree_id" != "$(git rev-parse "$Gref^{tree}")" ]; then commit_id=$(anon_commit "$tree_id") && diff --git a/tests/system-test-clean-command.sh b/tests/system-test-clean-command.sh index 7b486de..b5d9e78 100755 --- a/tests/system-test-clean-command.sh +++ b/tests/system-test-clean-command.sh @@ -218,11 +218,28 @@ if ! $GIT ls-tree -r "$GREF" | grep -q "garbage_file"; then fi print_info "Injected garbage_file into remote $GREF" +# 2.5 Inject a DOTFILE garbage file +DOT_GARBAGE_BLOB=$(echo "HIDDEN GARBAGE" | $GIT hash-object -w --stdin) +export GIT_INDEX_FILE=index.dotgarbage +$GIT read-tree "$NEW_TREE" +$GIT update-index --add --cacheinfo 100644 "$DOT_GARBAGE_BLOB" ".garbage_file" +NEW_TREE_DOT=$($GIT write-tree) +rm index.dotgarbage +NEW_COMMIT_DOT=$(echo "Inject dot garbage" | $GIT commit-tree "$NEW_TREE_DOT" -p "$NEW_COMMIT") +$GIT update-ref "$GREF" "$NEW_COMMIT_DOT" + +if ! $GIT ls-tree -r "$GREF" | grep -q "\.garbage_file"; then + print_err "Failed to inject .garbage_file into $GREF" + exit 1 +fi +print_info "Injected .garbage_file into remote $GREF" + # 3. Scan (expect to find garbage_file) set -x output=$("$SCRIPT_DIR/git-remote-gcrypt" clean "gcrypt::$tempdir/valid.git" 2>&1) set +x assert_grep "garbage_file" "$output" "clean identified unencrypted file in valid repo" +assert_grep "\.garbage_file" "$output" "clean identified unencrypted DOTFILE in valid repo" assert_grep "NOTE: This is a scan" "$output" "clean scan-only mode confirmed" # 4. Clean Force -- 2.52.0