password-store

Simple password manager using gpg and ordinary unix directories
git clone https://git.zx2c4.com/password-store
Log | Files | Refs | README | LICENSE

commit b24a90c8dde57e019b7c88039821c4a39361f1b3
parent b08781e2a6e183986eb1c24f51cdeff879b7a6af
Author: Jason A. Donenfeld <Jason@zx2c4.com>
Date:   Sat, 25 Feb 2017 16:56:58 +0100

git: use inner-most directory

Diffstat:
Mman/pass.1 | 11++++-------
Msrc/password-store.sh | 64++++++++++++++++++++++++++++++++++++++++++++--------------------
2 files changed, 48 insertions(+), 27 deletions(-)

diff --git a/man/pass.1 b/man/pass.1 @@ -41,8 +41,10 @@ Otherwise COMMAND must be one of the valid commands listed below. Several of the commands below rely on or provide additional functionality if the password store directory is also a git repository. If the password store directory is a git repository, all password store modification commands will -cause a corresponding git commit. See the \fIEXTENDED GIT EXAMPLE\fP section -for a detailed description using \fBinit\fP and +cause a corresponding git commit. Sub-directories may be separate nested git +repositories, and pass will use the inner-most directory relative to the +current password. See the \fIEXTENDED GIT EXAMPLE\fP section for a detailed +description using \fBinit\fP and .BR git (1). The \fBinit\fP command must be run before other commands in order to initialize @@ -410,11 +412,6 @@ Overrides the default gpg key identification set by \fBinit\fP. Keys must not contain spaces and thus use of the hexadecimal key signature is recommended. Multiple keys may be specified separated by spaces. .TP -.I PASSWORD_STORE_GIT -Overrides the default root of the git repository, which is helpful if -\fIPASSWORD_STORE_DIR\fP is temporarily set to a sub-directory of the default -password store. -.TP .I PASSWORD_STORE_GPG_OPTS Additional options to be passed to all invocations of GPG. .TP diff --git a/src/password-store.sh b/src/password-store.sh @@ -20,24 +20,30 @@ GENERATED_LENGTH="${PASSWORD_STORE_GENERATED_LENGTH:-25}" CHARACTER_SET="${PASSWORD_STORE_CHARACTER_SET:-[:graph:]}" CHARACTER_SET_NO_SYMBOLS="${PASSWORD_STORE_CHARACTER_SET_NO_SYMBOLS:-[:alnum:]}" -export GIT_DIR="${PASSWORD_STORE_GIT:-$PREFIX}/.git" -export GIT_WORK_TREE="${PASSWORD_STORE_GIT:-$PREFIX}" +export GIT_CEILING_DIRECTORIES="$PREFIX/.." # # BEGIN helper functions # +set_git() { + INNER_GIT_DIR="${1%/*}" + while [[ ! -d $INNER_GIT_DIR && ${INNER_GIT_DIR%/*}/ == "${PREFIX%/}/"* ]]; do + INNER_GIT_DIR="${INNER_GIT_DIR%/*}" + done + [[ $(git -C "$INNER_GIT_DIR" rev-parse --is-inside-work-tree 2>/dev/null) == true ]] || INNER_GIT_DIR="" +} git_add_file() { - [[ -e $GIT_DIR ]] || return - git add "$1" || return - [[ -n $(git status --porcelain "$1") ]] || return + [[ -n $INNER_GIT_DIR ]] || return + git -C "$INNER_GIT_DIR" add "$1" || return + [[ -n $(git -C "$INNER_GIT_DIR" status --porcelain "$1") ]] || return git_commit "$2" } git_commit() { local sign="" - [[ -e $GIT_DIR ]] || return - [[ $(git config --bool --get pass.signcommits) == "true" ]] && sign="-S" - git commit $sign -m "$1" + [[ -n $INNER_GIT_DIR ]] || return + [[ $(git -C "$INNER_GIT_DIR" config --bool --get pass.signcommits) == "true" ]] && sign="-S" + git -C "$INNER_GIT_DIR" commit $sign -m "$1" } yesno() { [[ -t 0 ]] || return 0 @@ -306,12 +312,13 @@ cmd_init() { [[ -n $id_path && ! -d $PREFIX/$id_path && -e $PREFIX/$id_path ]] && die "Error: $PREFIX/$id_path exists but is not a directory." local gpg_id="$PREFIX/$id_path/.gpg-id" + set_git "$gpg_id" if [[ $# -eq 1 && -z $1 ]]; then [[ ! -f "$gpg_id" ]] && die "Error: $gpg_id does not exist and so cannot be removed." rm -v -f "$gpg_id" || exit 1 - if [[ -e $GIT_DIR ]]; then - git rm -qr "$gpg_id" + if [[ -n $INNER_GIT_DIR ]]; then + git -C "$INNER_GIT_DIR" rm -qr "$gpg_id" git_commit "Deinitialize ${gpg_id}${id_path:+ ($id_path)}." fi rmdir -p "${gpg_id%/*}" 2>/dev/null @@ -419,6 +426,7 @@ cmd_insert() { local path="${1%/}" local passfile="$PREFIX/$path.gpg" check_sneaky_paths "$path" + set_git "$passfile" [[ $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?" @@ -459,6 +467,7 @@ cmd_edit() { mkdir -p -v "$PREFIX/$(dirname "$path")" set_gpg_recipients "$(dirname "$path")" local passfile="$PREFIX/$path.gpg" + set_git "$passfile" tmpdir #Defines $SECURE_TMPDIR local tmp_file="$(mktemp -u "$SECURE_TMPDIR/XXXXXX")-${path//\//-}.txt" @@ -500,6 +509,7 @@ cmd_generate() { mkdir -p -v "$PREFIX/$(dirname "$path")" set_gpg_recipients "$(dirname "$path")" local passfile="$PREFIX/$path.gpg" + set_git "$passfile" [[ $inplace -eq 0 && $force -eq 0 && -e $passfile ]] && yesno "An entry already exists for $path. Overwrite it?" @@ -545,14 +555,17 @@ cmd_delete() { local passdir="$PREFIX/${path%/}" local passfile="$PREFIX/$path.gpg" - [[ -f $passfile && -d $passdir && $path == */ || ! -f $passfile ]] && passfile="$passdir" + [[ -f $passfile && -d $passdir && $path == */ || ! -f $passfile ]] && passfile="${passdir%/}/" [[ -e $passfile ]] || die "Error: $path is not in the password store." + set_git "$passfile" [[ $force -eq 1 ]] || yesno "Are you sure you would like to delete $path?" rm $recursive -f -v "$passfile" - if [[ -e $GIT_DIR && ! -e $passfile ]]; then - git rm -qr "$passfile" + set_git "$passfile" + if [[ -n $INNER_GIT_DIR && ! -e $passfile ]]; then + git -C "$INNER_GIT_DIR" rm -qr "$passfile" + set_git "$passfile" git_commit "Remove $path from store." fi rmdir -p "${passfile%/*}" 2>/dev/null @@ -588,14 +601,23 @@ cmd_copy_move() { local interactive="-i" [[ ! -t 0 || $force -eq 1 ]] && interactive="-f" + set_git "$new_path" if [[ $move -eq 1 ]]; then mv $interactive -v "$old_path" "$new_path" || exit 1 [[ -e "$new_path" ]] && reencrypt_path "$new_path" - if [[ -e $GIT_DIR && ! -e $old_path ]]; then - git rm -qr "$old_path" + set_git "$new_path" + if [[ -n $INNER_GIT_DIR && ! -e $old_path ]]; then + git -C "$INNER_GIT_DIR" rm -qr "$old_path" 2>/dev/null + set_git "$new_path" git_add_file "$new_path" "Rename ${1} to ${2}." fi + set_git "$old_path" + if [[ -n $INNER_GIT_DIR && ! -e $old_path ]]; then + git -C "$INNER_GIT_DIR" rm -qr "$old_path" 2>/dev/null + set_git "$old_path" + [[ -n $(git -C "$INNER_GIT_DIR" status --porcelain "$old_path") ]] && git_commit "Remove ${1}." + fi rmdir -p "$old_dir" 2>/dev/null else cp $interactive -r -v "$old_path" "$new_path" || exit 1 @@ -605,18 +627,20 @@ cmd_copy_move() { } cmd_git() { + set_git "$PREFIX/" if [[ $1 == "init" ]]; then - git "$@" || exit 1 + INNER_GIT_DIR="$PREFIX" + git -C "$INNER_GIT_DIR" "$@" || exit 1 git_add_file "$PREFIX" "Add current contents of password store." echo '*.gpg diff=gpg' > "$PREFIX/.gitattributes" git_add_file .gitattributes "Configure git repository for gpg file diff." - git config --local diff.gpg.binary true - git config --local diff.gpg.textconv "$GPG -d ${GPG_OPTS[*]}" - elif [[ -e $GIT_DIR ]]; then + git -C "$INNER_GIT_DIR" config --local diff.gpg.binary true + git -C "$INNER_GIT_DIR" config --local diff.gpg.textconv "$GPG -d ${GPG_OPTS[*]}" + elif [[ -n $INNER_GIT_DIR ]]; then tmpdir nowarn #Defines $SECURE_TMPDIR. We don't warn, because at most, this only copies encrypted files. export TMPDIR="$SECURE_TMPDIR" - git "$@" + git -C "$INNER_GIT_DIR" "$@" else die "Error: the password store is not a git repository. Try \"$PROGRAM git init\"." fi