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:
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)