From 5164902706d097a6b85d47a020edf39152cdd2a5 Mon Sep 17 00:00:00 2001 From: James Murty Date: Mon, 4 May 2020 23:57:09 +1000 Subject: [PATCH] Improve handling of challenging file names when 'core.quotePath=true' As described in issue #78 transcrypt could fail to properly handle: - file names with non-ASCII characters when Git's configuration option 'core.quotePath' is set to 'true' (as it is by default) - file names containing spaces This change improves handling of non-ASCII or space characters in filenames and adds a test to exercise and confirm the fix. Changes in particular: - fix handling of space characters in file name in pre-commit hook - override repository's 'core.quotePath' setting in commands that output paths to ensure 'core.quotePath=false' is always applied even when 'core.quotePath' is explicitly or implicitly (default) set to true. --- tests/_test_helper.bash | 12 +++++------ tests/test_crypt.bats | 43 ++++++++++++++++++++++++++++++++++++++ tests/test_init.bats | 2 +- tests/test_pre_commit.bats | 2 +- transcrypt | 12 ++++++----- 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/tests/_test_helper.bash b/tests/_test_helper.bash index 01f06b9..6b145d1 100644 --- a/tests/_test_helper.bash +++ b/tests/_test_helper.bash @@ -30,7 +30,7 @@ function nuke_git_repo { function cleanup_all { nuke_git_repo rm $BATS_TEST_DIRNAME/.gitattributes - rm $BATS_TEST_DIRNAME/sensitive_file + rm -f $BATS_TEST_DIRNAME/sensitive_file } function init_transcrypt { @@ -38,14 +38,14 @@ function init_transcrypt { } function encrypt_named_file { - filename=$1 + filename="$1" content=$2 if [ "$content" ]; then - echo "$content" > $filename + echo "$content" > "$filename" fi - echo "$filename filter=crypt diff=crypt merge=crypt" >> .gitattributes - git add .gitattributes $filename - git commit -m "Encrypt file $filename" + echo "\"$filename\" filter=crypt diff=crypt merge=crypt" >> .gitattributes + git add .gitattributes "$filename" + run git commit -m "Encrypt file \"$filename\"" } function setup { diff --git a/tests/test_crypt.bats b/tests/test_crypt.bats index 560fc35..42576e4 100755 --- a/tests/test_crypt.bats +++ b/tests/test_crypt.bats @@ -88,3 +88,46 @@ function check_repo_is_clean { [ "${lines[0]}" != "$SECRET_CONTENT" ] [ "${lines[0]}" = "$SECRET_CONTENT_ENC" ] } + +@test "crypt: handle challenging file names when 'core.quotePath=true'" { + # Set core.quotePath=true which is the Git default prior to encrypting a + # file with non-ASCII characters and spaces in the name, to confirm + # transcrypt can handle the file properly. + # For info about the 'core.quotePath' setting see + # https://git-scm.com/docs/git-config#Documentation/git-config.txt-corequotePath + git config --local --add core.quotePath true + + FILENAME="Mig – røve" # Danish + SECRET_CONTENT_ENC="U2FsdGVkX19Fp9SwTyQ+tz1OgHNIN0OJ+6sMgHIqPMzfdZ6rZ2iVquS293WnjJMx" + + encrypt_named_file "$FILENAME" "$SECRET_CONTENT" + [[ "${lines[0]}" = *"Encrypt file \"$FILENAME\"" ]] + + # Working copy is decrypted + run cat "$FILENAME" + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT" ] + + # Git internal copy is encrypted + run git show HEAD:"$FILENAME" --no-textconv + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$SECRET_CONTENT_ENC" ] + + # transcrypt --show-raw shows encrypted content + run ../transcrypt --show-raw "$FILENAME" + [ "$status" -eq 0 ] + [ "${lines[0]}" = "==> $FILENAME <==" ] + [ "${lines[1]}" = "$SECRET_CONTENT_ENC" ] + + # git ls-crypt lists encrypted file + run git ls-crypt + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$FILENAME" ] + + # transcrypt --list lists encrypted file" + run ../transcrypt --list + [ "$status" -eq 0 ] + [ "${lines[0]}" = "$FILENAME" ] + + rm "$FILENAME" +} diff --git a/tests/test_init.bats b/tests/test_init.bats index 5d30fcc..90bbe78 100755 --- a/tests/test_init.bats +++ b/tests/test_init.bats @@ -53,7 +53,7 @@ SETUP_SKIP_INIT_TRANSCRYPT=1 [ `git config --get diff.crypt.binary` = "true" ] [ `git config --get merge.renormalize` = "true" ] - [[ `git config --get alias.ls-crypt` = "!git ls-files"* ]] + [[ `git config --get alias.ls-crypt` = "!git -c core.quotePath=false ls-files"* ]] } @test "init: show details for --display" { diff --git a/tests/test_pre_commit.bats b/tests/test_pre_commit.bats index 8edf809..c3c3571 100755 --- a/tests/test_pre_commit.bats +++ b/tests/test_pre_commit.bats @@ -26,7 +26,7 @@ load $BATS_TEST_DIRNAME/_test_helper.bash run git log --format=oneline [ "$status" -eq 0 ] [[ "${lines[0]}" = *"Added more" ]] - [[ "${lines[1]}" = *"Encrypt file sensitive_file" ]] + [[ "${lines[1]}" = *"Encrypt file \"sensitive_file\"" ]] } @test "pre-commit: reject commit of encrypted file with unencrypted content" { diff --git a/transcrypt b/transcrypt index 2e7d4d7..628d95c 100755 --- a/transcrypt +++ b/transcrypt @@ -379,7 +379,8 @@ save_helper_hooks() { cat <<-'EOF' >"$pre_commit_hook_installed" #!/usr/bin/env bash # Transcrypt pre-commit hook: fail if secret file in staging lacks the magic prefix "Salted" in B64 - for secret_file in $(git ls-files | git check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do + IFS=$'\n' + for secret_file in $(git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }'); do # Get prefix of raw file in Git's index using the :FILENAME revision syntax firstbytes=$(git show :$secret_file | head -c 8) # An empty file does not need to be, and is not, encrypted @@ -401,6 +402,7 @@ save_helper_hooks() { exit 1 fi done + unset IFS EOF # Activate hook by copying it to the pre-commit script name, if safe to do so @@ -455,7 +457,7 @@ save_configuration() { git config merge.crypt.name 'Merge transcrypt secret files' # add a git alias for listing encrypted files - git config alias.ls-crypt "!git ls-files | git check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; /crypt$/{ print \$1 }'" + git config alias.ls-crypt "!git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = \":\" }; /crypt$/{ print \$1 }'" } # display the current configuration settings @@ -622,7 +624,7 @@ uninstall_transcrypt() { list_files() { if [[ $IS_BARE == 'false' ]]; then cd "$REPO" || die 1 'could not change into the "%s" directory' "$REPO" - git ls-files | git check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }' + git -c core.quotePath=false ls-files | git -c core.quotePath=false check-attr --stdin filter | awk 'BEGIN { FS = ":" }; /crypt$/{ print $1 }' fi } @@ -631,8 +633,8 @@ show_raw_file() { if [[ -f $show_file ]]; then # ensure the file is currently being tracked local escaped_file=${show_file//\//\\\/} - if git ls-files --others -- "$show_file" | awk "/${escaped_file}/{ exit 1 }"; then - file_paths=$(git ls-tree --name-only --full-name HEAD "$show_file") + if git -c core.quotePath=false ls-files --others -- "$show_file" | awk "/${escaped_file}/{ exit 1 }"; then + file_paths=$(git -c core.quotePath=false ls-tree --name-only --full-name HEAD "$show_file") else die 1 'the file "%s" is not currently being tracked by git' "$show_file" fi