pashage

Yet Another Opinionated Re-engineering of the Unix Password Store
git clone https://git.instinctive.eu/pashage.git
Log | Files | Refs | README | LICENSE

commit fe78a3e62d7d2578455a98deb158cc9158ea09a7
parent d738b3bd8b5d452bacb6e73f5a4f5464a212175b
Author: Natasha Kerensikova <natgh@instinctive.eu>
Date:   Tue, 25 Nov 2025 19:33:14 +0000

New option for deep recursive re-encryption
Diffstat:
MREADME.md | 3++-
Mcompletions/_pashage | 1+
Mcompletions/pashage.bash | 2+-
Mcompletions/pashage.fish | 2++
Mpashage.1 | 3+++
Mspec/pashage_extra_spec.sh | 31++++++++++++++++++++++++++++++-
Mspec/usage_spec.sh | 65++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/pashage.sh | 16++++++++++++++--
8 files changed, 117 insertions(+), 6 deletions(-)

diff --git a/README.md b/README.md @@ -606,13 +606,14 @@ Environment: Syntax: ``` -pashage reencrypt [--interactive,-i] pass-name|subfolder ... +pashage reencrypt [--deep,-d] [--interactive,-i] pass-name|subfolder ... ``` This subcommand re-encrypts in place the given secrets, and all the secrets recursively in the given subfolders. Flags: +- `-d` or `--deep`: re-encrypt subfolders with their own recipient list - `-i` or `--interactive`: asks whether to re-encrypt or not for each secret Environment: diff --git a/completions/_pashage b/completions/_pashage @@ -122,6 +122,7 @@ function _pashage { ;; (reencrypt) _arguments -s -w -S \ + '(-d --deep)'{-d,--deep}'[re-encrypt subfolders with their own recipients]' \ '(-i --interactive)'{-i,--interactive}'[ask for each entry]' _pashage_entries_and_dirs ;; diff --git a/completions/pashage.bash b/completions/pashage.bash @@ -111,7 +111,7 @@ _pashage() _pashage_complete_entries ;; re-encrypt|reencrypt) - COMPREPLY+=($(compgen -W "-i --interactive" -- ${cur})) + COMPREPLY+=($(compgen -W "-d --deep -i --interactive" -- ${cur})) _pashage_complete_entries ;; git) diff --git a/completions/pashage.fish b/completions/pashage.fish @@ -103,6 +103,8 @@ complete -f -c pashage -n 'not __fish_pashage_any_command' \ # Option completion complete -f -c pashage -n '__fish_pashage_opt_command gen generate show' \ -s 'c' -l 'clip' -d 'paste secret into clipboard' +complete -f -c pashage -n '__fish_pashage_command re-encrypt reencrypt' \ + -s 'd' -l 'deep' -d 're-encrypt subfolders with their own recipients' complete -f -c pashage -n '__fish_pashage_command insert' \ -s 'e' -l 'echo' -d 'non-hidden password entry' complete -f -c pashage -n '__fish_pashage_command copy cp move mv' \ diff --git a/pashage.1 b/pashage.1 @@ -298,6 +298,7 @@ subcommand, then directly displays on the standard output without storing it. .Ss reencrypt .Nm .Cm reencrypt +.Op Fl d,--deep .Op Fl i,--interactive .Ar pass-name|subfolder .Ar ... @@ -307,6 +308,8 @@ recursively in the given subfolders. .Pp The options are as follows: .Bl -tag -compact -width \-i,--interactive +.It Fl d,--deep +re-encrypt subfolders with their own recipient list .It Fl i,--interactive asks whether to re-encrypt or not for each secret .El diff --git a/spec/pashage_extra_spec.sh b/spec/pashage_extra_spec.sh @@ -1464,7 +1464,7 @@ Describe 'Integrated Command Functions' Describe 'cmd_reencrypt' usage_text() { %text - #|Usage: prg reencrypt [--interactive,-i] pass-name|subfolder ... + #|Usage: prg reencrypt [--deep,-d] [--interactive,-i] pass-name|subfolder ... } It 'reencrypts a single file' @@ -1573,6 +1573,35 @@ Describe 'Integrated Command Functions' The result of function check_git_log should be successful End + It 'reencrypts directories deeply, recursively, and interactively' + Data + #|n + #|n + #|n + #|n + #|y + #|n + End + When call cmd_reencrypt -id '' + The status should be success + The error should be blank + The output should equal 'Re-encrypt extra/subdir/file? [y/n]Re-encrypt fluff/one? [y/n]Re-encrypt fluff/three? [y/n]Re-encrypt fluff/two? [y/n]Re-encrypt stale? [y/n]Re-encrypt subdir/file? [y/n]' + expected_file() { %text + #|ageRecipient:myself + #|age:0-password + } + The contents of file "${PREFIX}/stale.age" \ + should equal "$(expected_file)" + expected_log() { %text + #|Re-encrypt / + #| + #| stale.age | 1 - + #| 1 file changed, 1 deletion(-) + setup_log + } + The result of function check_git_log should be successful + End + It 'fails to reencrypt a file named like a flag without escape' PROGRAM=prg When run cmd_reencrypt -g diff --git a/spec/usage_spec.sh b/spec/usage_spec.sh @@ -126,6 +126,7 @@ Describe 'Command-Line Parsing' mocklog do_reencrypt "$@" %text:expand >&2 #|DECISION=${DECISION} + #|RECURSIVE=${RECURSIVE} } do_reencrypt_dir() { mocklog do_reencrypt_dir "$@" @@ -2238,10 +2239,13 @@ Describe 'Command-Line Parsing' #|$ check_sneaky_path sub/file-2 #|$ do_reencrypt file-1 #|DECISION=default + #|RECURSIVE=no #|$ do_reencrypt dir/ #|DECISION=default + #|RECURSIVE=no #|$ do_reencrypt sub/file-2 #|DECISION=default + #|RECURSIVE=no } When call cmd_reencrypt file-1 dir/ sub/file-2 The status should be success @@ -2249,12 +2253,55 @@ Describe 'Command-Line Parsing' The error should equal "$(result)" End + It 'deeply re-encrypts with a long option' + result() { + %text + #|$ check_sneaky_path arg + #|$ do_reencrypt arg + #|DECISION=default + #|RECURSIVE=yes + } + When call cmd_reencrypt --deep arg + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'deeply re-encrypts with a short option' + result() { + %text + #|$ check_sneaky_path arg + #|$ do_reencrypt arg + #|DECISION=default + #|RECURSIVE=yes + } + When call cmd_reencrypt -d arg + The status should be success + The output should be blank + The error should equal "$(result)" + End + + It 'deeply and interactively re-encrypts with short options' + result() { + %text + #|$ check_sneaky_path arg + #|$ do_reencrypt arg + #|DECISION=interactive + #|RECURSIVE=yes + } + When call cmd_reencrypt -di arg + The status should be success + The output should be blank + The error should equal "$(result)" + End + It 'interactively re-encrypts with a long option' result() { %text #|$ check_sneaky_path arg #|$ do_reencrypt arg #|DECISION=interactive + #|RECURSIVE=no } When call cmd_reencrypt --interactive arg The status should be success @@ -2268,6 +2315,7 @@ Describe 'Command-Line Parsing' #|$ check_sneaky_path arg #|$ do_reencrypt arg #|DECISION=interactive + #|RECURSIVE=no } When call cmd_reencrypt -i arg The status should be success @@ -2275,12 +2323,27 @@ Describe 'Command-Line Parsing' The error should equal "$(result)" End + It 'interactively and deeply re-encrypts with short options' + result() { + %text + #|$ check_sneaky_path arg + #|$ do_reencrypt arg + #|DECISION=interactive + #|RECURSIVE=yes + } + When call cmd_reencrypt -id arg + The status should be success + The output should be blank + The error should equal "$(result)" + End + It 're-encrypts a file named like a flag' result() { %text #|$ check_sneaky_path -s #|$ do_reencrypt -s #|DECISION=default + #|RECURSIVE=no } When call cmd_reencrypt -- -s The status should be success @@ -2289,7 +2352,7 @@ Describe 'Command-Line Parsing' End usage_text() { %text - #|Usage: prg reencrypt [--interactive,-i] pass-name|subfolder ... + #|Usage: prg reencrypt [--deep,-d] [--interactive,-i] pass-name|subfolder ... } It 'reports a bad option' diff --git a/src/pashage.sh b/src/pashage.sh @@ -1712,12 +1712,23 @@ cmd_reencrypt() { while [ $# -ge 1 ]; do case "$1" in + -d|--deep) + RECURSIVE=yes + shift ;; -i|--interactive) DECISION=interactive shift ;; --) shift break ;; + -[di]?*) + REST="${1#??}" + FIRST="${1%"${REST}"}" + shift + set -- "${FIRST}" "-${REST}" "$@" + unset FIRST + unset REST + ;; -*) PARSE_ERROR=yes break ;; @@ -1924,11 +1935,12 @@ EOF ;; reencrypt) cat <<EOF -${F}${PROGRAM} reencrypt [--interactive,-i] pass-name|subfolder ... +${F}${PROGRAM} reencrypt [--deep,-d] [--interactive,-i] pass-name|subfolder ... EOF [ "${VERBOSE}" = yes ] && cat <<EOF ${I} Re-encrypt in-place a secret or all the secrets in a subfolder, -${I} optionally asking before each one. +${I} optionally including subfolders with their own recipients, +${I} and optionally asking before each one. EOF ;; version)