From 5f286eaaaa587ad96de2d61aa03a17ddcf4c0395 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Fri, 24 Aug 2018 08:32:23 +0700 Subject: [PATCH 01/23] Quote arrays to avoid splitting by $IFS --- lib/git/aliases.sh | 2 +- lib/git/branch_shortcuts.sh | 2 +- lib/git/repo_index.sh | 14 +++++++------- lib/git/shell_shortcuts.sh | 4 ++-- lib/git/status_shortcuts.sh | 8 ++++---- lib/git/tools.sh | 4 ++-- test/lib/git/shell_shortcuts_test.sh | 2 +- test/support/shunit2 | 6 +++--- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/lib/git/aliases.sh b/lib/git/aliases.sh index 28b0e31..e9c8f59 100644 --- a/lib/git/aliases.sh +++ b/lib/git/aliases.sh @@ -71,7 +71,7 @@ __git_alias () { alias_str="$1"; cmd_prefix="$2"; cmd="$3"; if [ $# -gt 2 ]; then shift 3 2>/dev/null - cmd_args=$@ + cmd_args=("$@") fi alias $alias_str="$cmd_prefix $cmd${cmd_args:+ }${cmd_args[*]}" diff --git a/lib/git/branch_shortcuts.sh b/lib/git/branch_shortcuts.sh index 61012e2..7157339 100644 --- a/lib/git/branch_shortcuts.sh +++ b/lib/git/branch_shortcuts.sh @@ -22,7 +22,7 @@ function _scmb_git_branch_shortcuts { # Use ruby to inject numbers into ls output ruby -e "$( cat < 9 && i < 9 ? " " : " ") diff --git a/lib/git/repo_index.sh b/lib/git/repo_index.sh index 3d2e9c7..9fc2b44 100644 --- a/lib/git/repo_index.sh +++ b/lib/git/repo_index.sh @@ -217,10 +217,10 @@ _git_index_update_all_branches() { # Ignore branch if remote and merge is not configured if [[ -n "$remote" ]] && [[ -n "$merge" ]]; then - branches=(${branches[@]} "$branch") - remotes=(${remotes[@]} "$remote") + branches=("${branches[@]}" "$branch") + remotes=("${remotes[@]}" "$remote") # Get branch from merge ref (refs/heads/master => master) - merges=(${merges[@]} "$(basename $merge)") + merges=("${merges[@]}" "$(basename "$merge")") else echo "=== Skipping $branch: remote and merge refs are not configured." fi @@ -232,12 +232,12 @@ _git_index_update_all_branches() { local index=0 # Iterate over branches, and update those that can be fast-forwarded - for branch in ${branches[@]}; do + for branch in "${branches[@]}"; do branch_rev="$(git rev-parse $branch)" # Local branch can be fast-forwarded if revision is ancestor of remote revision, and not the same. # (see http://stackoverflow.com/a/2934062/304706) - if [[ "$branch_rev" != "$(git rev-parse ${remotes[$index]}/${merges[$index]})" ]] && \ - [[ "$(git merge-base $branch_rev ${remotes[$index]}/${merges[$index]})" = "$branch_rev" ]]; then + if [[ "$branch_rev" != "$(git rev-parse "${remotes[$index]}/${merges[$index]}")" ]] && \ + [[ "$(git merge-base "$branch_rev" "${remotes[$index]}/${merges[$index]}")" = "$branch_rev" ]]; then echo "=== Updating $branch branch in $base_path from ${remotes[$index]}/${merges[$index]}..." # Checkout branch if we aren't already on it. if [[ "$branch" != "$(parse_git_branch)" ]]; then git checkout $branch; fi @@ -272,7 +272,7 @@ function _git_index_batch_cmd() { local base_path for base_path in $(sed -e "s/--.*//" "$GIT_REPO_DIR/.git_index" | \grep . | sort); do builtin cd "$base_path" - $@ + "$@" done else echo "Please give a command to run for all repos. (It may be useful to write your command as a function or script.)" diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index 4c427d8..2cadc24 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -126,7 +126,7 @@ if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then # Parse path from args IFS=$'\n' - for arg in $@; do + for arg in "$@"; do if [ -d "$arg" ]; then local rel_path="${arg%/}"; fi done unset IFS @@ -142,7 +142,7 @@ if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then puts o.lines.map{|l|l.sub(re){|m|\"%s%-#{u}s %-#{g}s%#{s}s \"%[\$1,*\$3.split]}}" } - ll_output=$(echo "$ll_output" | \sed -$SED_REGEX_ARG "s/ $USER/ $(/bin/cat $HOME/.user_sym)/g" | rejustify_ls_columns) + ll_output=$(echo "$ll_output" | \sed -$SED_REGEX_ARG "s/ $USER/ $(/bin/cat "$HOME/.user_sym")/g" | rejustify_ls_columns) fi if [ "$(echo "$ll_output" | wc -l)" -gt "50" ]; then diff --git a/lib/git/status_shortcuts.sh b/lib/git/status_shortcuts.sh index d63bb0c..ee0f804 100644 --- a/lib/git/status_shortcuts.sh +++ b/lib/git/status_shortcuts.sh @@ -21,7 +21,7 @@ git_status_shortcuts() { zsh_compat # Ensure shwordsplit is on for zsh git_clear_vars # Run ruby script, store output - local cmd_output="$(/usr/bin/env ruby "$scmbDir/lib/git/status_shortcuts.rb" $@)" + local cmd_output="$(/usr/bin/env ruby "$scmbDir/lib/git/status_shortcuts.rb" "$@")" # Print debug information if $scmbDebug = "true" if [ "${scmbDebug:-}" = "true" ]; then printf "status_shortcuts.rb output => \n$cmd_output\n------------------------\n" @@ -102,8 +102,8 @@ git_show_affected_files(){ fail_if_not_git_repo || return 1 f=0 # File count # Show colored revision and commit message - echo -n "# "; git show --oneline --name-only $@ | head -n1; echo "# " - for file in $(git show --pretty="format:" --name-only $@ | \grep -v '^$'); do + echo -n "# "; git show --oneline --name-only "$@" | head -n1; echo "# " + for file in $(git show --pretty="format:" --name-only "$@" | \grep -v '^$'); do let f++ export $git_env_char$f=$file # Export numbered variable. echo -e "# \033[2;37m[\033[0m$f\033[2;37m]\033[0m $file" @@ -210,7 +210,7 @@ git_commit_prompt() { fi if [ -n "$commit_msg" ]; then - eval $@ # run any prequisite commands + eval "$@" # run any prequisite commands # Add $APPEND to commit message, if given. (Used to append things like [ci skip] for Travis CI) if [ -n "$APPEND" ]; then commit_msg="$commit_msg $APPEND"; fi echo $commit_msg | git commit -F - | tail -n +2 diff --git a/lib/git/tools.sh b/lib/git/tools.sh index 0c62e72..e5e2c5d 100644 --- a/lib/git/tools.sh +++ b/lib/git/tools.sh @@ -23,8 +23,8 @@ git_remove_history() { return fi # Remove all paths passed as arguments from the history of the repo - files=$@ - $_git_cmd filter-branch --index-filter "$_git_cmd rm -rf --cached --ignore-unmatch $files" HEAD + files=("$@") + $_git_cmd filter-branch --index-filter "$_git_cmd rm -rf --cached --ignore-unmatch ${files[*]}" HEAD # Remove the temporary history git-filter-branch otherwise leaves behind for a long time rm -rf .git/refs/original/ && $_git_cmd reflog expire --all && $_git_cmd gc --aggressive --prune } diff --git a/test/lib/git/shell_shortcuts_test.sh b/test/lib/git/shell_shortcuts_test.sh index f699085..3842590 100755 --- a/test/lib/git/shell_shortcuts_test.sh +++ b/test/lib/git/shell_shortcuts_test.sh @@ -38,7 +38,7 @@ oneTimeSetUp() { alias rvm="test" # Ensure tests run if RVM isn't loaded but $HOME/.rvm is present # Test functions - function ln() { ln $@; } + function ln() { ln "$@"; } # Before aliasing, get original locations so we can compare them in the test unalias mv rm sed cat 2>/dev/null diff --git a/test/support/shunit2 b/test/support/shunit2 index 8ca4b4b..df37583 100755 --- a/test/support/shunit2 +++ b/test/support/shunit2 @@ -25,9 +25,9 @@ SHUNIT_ERROR=2 # enable strict mode by default SHUNIT_STRICT=${SHUNIT_STRICT:-${SHUNIT_TRUE}} -_shunit_warn() { echo "shunit2:WARN $@" >&2; } -_shunit_error() { echo "shunit2:ERROR $@" >&2; } -_shunit_fatal() { echo "shunit2:FATAL $@" >&2; exit ${SHUNIT_ERROR}; } +_shunit_warn() { echo "shunit2:WARN $*" >&2; } +_shunit_error() { echo "shunit2:ERROR $*" >&2; } +_shunit_fatal() { echo "shunit2:FATAL $*" >&2; exit ${SHUNIT_ERROR}; } # specific shell checks if [ -n "${ZSH_VERSION:-}" ]; then From 3a5b7a685afc184b64ff95b2883edf2000c6bb48 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Fri, 24 Aug 2018 08:34:47 +0700 Subject: [PATCH 02/23] _print_path: remove unnecessary pipeline --- lib/git/status_shortcuts.sh | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/git/status_shortcuts.sh b/lib/git/status_shortcuts.sh index ee0f804..eb81eaf 100644 --- a/lib/git/status_shortcuts.sh +++ b/lib/git/status_shortcuts.sh @@ -147,12 +147,13 @@ scmb_expand_args() { IFS="$OLDIFS" } +# Expand a variable into a (possibly relative) pathname _print_path() { - if [ "$1" = 1 ]; then - eval printf '%s' "\"\$$2\"" | sed -e "s%$(pwd)/%%" | awk '{printf("%s", $0)}' - else - eval printf '%s' "\"\$$2\"" + local pathname=$(eval printf '%s' "\"\${$2}\"") + if [ "$1" = 1 ]; then # print relative + pathname=${pathname#$PWD/} # Remove $PWD from beginning of the path fi + printf '%s' "$pathname" } # Execute a command with expanded args, e.g. Delete files 6 to 12: $ ge rm 6-12 From d5c60b2cc542b337ccae1435b5c1e99291949edf Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Fri, 24 Aug 2018 18:12:07 +0700 Subject: [PATCH 03/23] git_status_shortcuts: don't clobber $IFS --- lib/design.sh | 3 +-- lib/git/branch_shortcuts.sh | 4 +--- lib/git/fallback/status_shortcuts_shell.sh | 5 ++--- lib/git/repo_index.sh | 22 ++++++++-------------- lib/git/shell_shortcuts.sh | 6 ++---- lib/git/status_shortcuts.sh | 3 +-- test/lib/git/repo_index_test.sh | 6 ++---- 7 files changed, 17 insertions(+), 32 deletions(-) diff --git a/lib/design.sh b/lib/design.sh index 125175e..cd48cdb 100644 --- a/lib/design.sh +++ b/lib/design.sh @@ -34,7 +34,7 @@ design() { local project=`basename $(pwd)` local all_project_dirs="$design_base_dirs $design_av_dirs" # Ensure design dir contains all subdirectories - IFS=$' \t\n' + local IFS=$' \t\n' # Create root design dirs for dir in $design_ext_dirs; do mkdir -p "$root_design_dir/$dir"; done # Create project design dirs @@ -102,6 +102,5 @@ design() { printf "Invalid command.\n\n" design fi - unset IFS } diff --git a/lib/git/branch_shortcuts.sh b/lib/git/branch_shortcuts.sh index 7157339..27b0ce8 100644 --- a/lib/git/branch_shortcuts.sh +++ b/lib/git/branch_shortcuts.sh @@ -32,14 +32,12 @@ EOF )" # Set numbered file shortcut in variable - local e=1 - IFS=$'\n' + local e=1 IFS=$'\n' for branch in $($_git_cmd branch "$@" | sed "s/^[* ]\{2\}//"); do export $git_env_char$e="$branch" if [ "${scmbDebug:-}" = "true" ]; then echo "Set \$$git_env_char$e => $file"; fi let e++ done - unset IFS } __git_alias "$git_branch_alias" "_scmb_git_branch_shortcuts" "" diff --git a/lib/git/fallback/status_shortcuts_shell.sh b/lib/git/fallback/status_shortcuts_shell.sh index 9368588..406da56 100755 --- a/lib/git/fallback/status_shortcuts_shell.sh +++ b/lib/git/fallback/status_shortcuts_shell.sh @@ -16,7 +16,7 @@ # -------------------------------------------------------------------- git_status_shortcuts() { zsh_compat # Ensure shwordsplit is on for zsh - IFS=$'\n' + local IFS=$'\n' local git_status="$(git status --porcelain 2> /dev/null)" local i @@ -95,7 +95,7 @@ git_status_shortcuts() { fi done - IFS=" " + local IFS=" " grp_num=1 for heading in 'Changes to be committed' 'Unmerged paths' 'Changes not staged for commit' 'Untracked files'; do # If no group specified as param, or specified group is current group @@ -114,7 +114,6 @@ git_status_shortcuts() { # so just use plain 'git status' git status fi - unset IFS zsh_reset # Reset zsh environment to default } # Template function for 'git_status_shortcuts'. diff --git a/lib/git/repo_index.sh b/lib/git/repo_index.sh index 9fc2b44..b3f4005 100644 --- a/lib/git/repo_index.sh +++ b/lib/git/repo_index.sh @@ -51,7 +51,7 @@ function git_index() { - IFS=$'\n' + local IFS=$'\n' if [ -z "$1" ]; then # Just change to $GIT_REPO_DIR if no params given. "cd" $GIT_REPO_DIR @@ -103,7 +103,7 @@ function git_index() { # -------------------- # Go to our base path if [ -n "$base_path" ]; then - IFS=$' \t\n' + local IFS=$' \t\n' # evaluate ~ if necessary if [[ "$base_path" == "~"* ]]; then base_path=$(eval echo ${base_path%%/*})/${base_path#*/} @@ -116,7 +116,6 @@ function git_index() { fi fi fi - unset IFS } _git_index_dirs_without_home() { @@ -126,12 +125,11 @@ _git_index_dirs_without_home() { # Recursively searches for git repos in $GIT_REPO_DIR function _find_git_repos() { # Find all unarchived projects - IFS=$'\n' + local IFS=$'\n' for repo in $(find -L "$GIT_REPO_DIR" -maxdepth 5 -name ".git" -type d \! -wholename '*/archive/*'); do echo ${repo%/.git} # Return project folder, with trailing ':' _find_git_submodules $repo # Detect any submodules done - unset IFS } # List all submodules for a git repo, if any. @@ -146,11 +144,10 @@ function _find_git_submodules() { function _rebuild_git_index() { if [ "$1" != "--silent" ]; then echo -e "== Scanning $GIT_REPO_DIR for git repos & submodules..."; fi # Get repos from src dir and custom dirs, then sort by basename - IFS=$'\n' + local IFS=$'\n' for repo in $(echo -e "$(_find_git_repos)\n$(echo $GIT_REPOS | sed "s/:/\\\\n/g")"); do echo $(basename $repo | sed "s/ /_/g"):$repo done | sort -t ":" -k1,1 | cut -d ":" -f2- >| "$GIT_REPO_DIR/.git_index" - unset IFS if [ "$1" != "--silent" ]; then echo -e "===== Indexed $_bld_col$(_git_index_count)$_txt_col repos in $GIT_REPO_DIR/.git_index" @@ -207,7 +204,7 @@ _git_index_update_all_branches() { local remotes merges branches # Get branch configuration from .git/config - IFS=$'\n' + local IFS=$'\n' for branch in $($GIT_BINARY branch 2> /dev/null | sed -e 's/.\{2\}\(.*\)/\1/'); do # Skip '(no branch)' if [[ "$branch" = "(no branch)" ]]; then continue; fi @@ -225,7 +222,6 @@ _git_index_update_all_branches() { echo "=== Skipping $branch: remote and merge refs are not configured." fi done - unset IFS # Update all remotes if there are any branches to update if [ -n "${branches[*]}" ]; then git fetch --all 2> /dev/null; fi @@ -268,7 +264,7 @@ function _git_index_batch_cmd() { cwd="$PWD" if [ -n "$1" ]; then echo -e "== Running command for $_bld_col$(_git_index_count)$_txt_col repos...\n" - unset IFS + local IFS=$'\n' local base_path for base_path in $(sed -e "s/--.*//" "$GIT_REPO_DIR/.git_index" | \grep . | sort); do builtin cd "$base_path" @@ -285,8 +281,7 @@ if [ $shell = 'bash' ]; then # Bash tab completion function for git_index() function _git_index_tab_completion() { _check_git_index - local curw - IFS=$'\n' + local curw IFS=$'\n' COMPREPLY=() curw=${COMP_WORDS[COMP_CWORD]} @@ -313,10 +308,9 @@ if [ $shell = 'bash' ]; then else COMPREPLY=($(compgen -W '$(sed -e "s:.*/::" -e "s:$:/:" "$GIT_REPO_DIR/.git_index" | sort)' -- $curw)) fi - unset IFS return 0 } -else +else # Zsh tab completion function for git_index() function _git_index_tab_completion() { typeset -A opt_args local state state_descr context line diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index 2cadc24..6c4a972 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -125,11 +125,10 @@ if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then fi # Parse path from args - IFS=$'\n' + local IFS=$'\n' for arg in "$@"; do if [ -d "$arg" ]; then local rel_path="${arg%/}"; fi done - unset IFS # Replace user/group with user symbol, if defined at ~/.user_sym # Before : -rw-rw-r-- 1 ndbroadbent ndbroadbent 1.1K Sep 19 21:39 scm_breeze.sh @@ -175,14 +174,13 @@ EOF ll_files="$(\ls "$@")" fi - IFS=$'\n' + local IFS=$'\n' for file in $ll_files; do if [ -n "$rel_path" ]; then file="$rel_path/$file"; fi export $git_env_char$e="$(eval $_abs_path_command \"${file//\"/\\\"}\")" if [ "${scmbDebug:-}" = "true" ]; then echo "Set \$$git_env_char$e => $file"; fi let e++ done - unset IFS # Turn off shwordsplit unless it was on previously if [[ $shell == "zsh" ]] && [ -z "$SHWORDSPLIT_ON" ]; then unsetopt shwordsplit; fi diff --git a/lib/git/status_shortcuts.sh b/lib/git/status_shortcuts.sh index eb81eaf..69410a2 100644 --- a/lib/git/status_shortcuts.sh +++ b/lib/git/status_shortcuts.sh @@ -36,14 +36,13 @@ git_status_shortcuts() { files="$(echo "$cmd_output" | \grep '@@filelist@@::' | sed 's%@@filelist@@::%%g')" if [ "${scmbDebug:-}" = "true" ]; then echo "filelist => $files"; fi # Export numbered env variables for each file - IFS="|" + local IFS="|" local e=1 for file in $files; do export $git_env_char$e="$file" if [ "${scmbDebug:-}" = "true" ]; then echo "Set \$$git_env_char$e => $file"; fi let e++ done - unset IFS if [ "${scmbDebug:-}" = "true" ]; then echo "------------------------"; fi # Print status diff --git a/test/lib/git/repo_index_test.sh b/test/lib/git/repo_index_test.sh index c866ec4..eb98334 100755 --- a/test/lib/git/repo_index_test.sh +++ b/test/lib/git/repo_index_test.sh @@ -68,13 +68,12 @@ EOF done # Setup some custom repos outside the main repo dir - IFS=":" + local IFS=":" for dir in $GIT_REPOS; do mkdir -p $dir cd $dir git init done - unset IFS verboseGitCommands @@ -83,9 +82,8 @@ EOF oneTimeTearDown() { rm -rf "${GIT_REPO_DIR}" - IFS=":" + local IFS=":" for dir in $GIT_REPOS; do rm -rf $dir; done - unset IFS } ensureIndex() { From fb7d227c30d705ddafc982fac390bcded0867bee Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Fri, 24 Aug 2018 19:30:16 +0700 Subject: [PATCH 04/23] Add _safe_eval: quote $@ elements before eval --- lib/scm_breeze.sh | 14 ++++++++++++++ test/lib/scm_breeze_test.sh | 26 ++++++++++++++++++++++++++ test/support/test_helper.sh | 16 ++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100755 test/lib/scm_breeze_test.sh diff --git a/lib/scm_breeze.sh b/lib/scm_breeze.sh index 2c692bb..b177db9 100644 --- a/lib/scm_breeze.sh +++ b/lib/scm_breeze.sh @@ -18,6 +18,20 @@ _alias() { fi } +# Quote "$@" before `eval` to prevent arbitrary code execution. +# Eg, the following will run `date`: +# evil() { eval "$@"; }; evil "echo" "foo;date" +function _safe_eval() { + if [[ $shell = bash ]]; then + # ${parameter@operator} where parameter is ${@} and operator is 'Q' + # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + eval "${@@Q}" + else # zsh + # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags + eval "${(q-)@}" + fi +} + find_binary(){ if [ $shell = "zsh" ]; then builtin type -p "$1" | sed "s/$1 is //" | head -1 diff --git a/test/lib/scm_breeze_test.sh b/test/lib/scm_breeze_test.sh new file mode 100755 index 0000000..f74beef --- /dev/null +++ b/test/lib/scm_breeze_test.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +export scmbDir="$( cd -P "$( dirname "$0" )" && pwd )/../.." + +# Zsh compatibility +if [ -n "${ZSH_VERSION:-}" ]; then shell="zsh"; SHUNIT_PARENT=$0; setopt shwordsplit; fi + +# Load test helpers +source "$scmbDir/test/support/test_helper.sh" + +# Load functions to test +source "$scmbDir/lib/scm_breeze.sh" + +#----------------------------------------------------------------------------- +# Unit tests +#----------------------------------------------------------------------------- + +test__safe_eval() { + assertEquals "runs eval with simple words" "'one' 'two' 'three'" "$(_safe_eval token_quote one two three)" + assertEquals "quotes spaces" "'a' 'b c' 'd'" "$(_safe_eval token_quote a b\ c d)" + assertEquals "quotes special chars" "'a b' '\$dollar' '\\slash' 'c d'" "$(_safe_eval token_quote a\ b '$dollar' '\slash' c\ d)" +} + + +# load and run shUnit2 +source "$scmbDir/test/support/shunit2" diff --git a/test/support/test_helper.sh b/test/support/test_helper.sh index a021b03..4649373 100644 --- a/test/support/test_helper.sh +++ b/test/support/test_helper.sh @@ -26,11 +26,27 @@ tab_completions(){ echo "${COMPREPLY[@]}"; } silentGitCommands() { git() { /usr/bin/env git "$@" > /dev/null 2>&1; } } + # Cancel silent git commands verboseGitCommands() { unset -f git } +# Quote the contents of "$@" in single quotes +# Avoid printf '%q' as 'a b' becomes a\ b in both {ba,z}sh +# See also quote_args and double_quote +function token_quote { + if [[ $shell = bash ]]; then + # Single quotes are always added + echo "${@@Q}" + else # zsh + # Single quotes only added when needed + #shellcheck disable=2154 # zsh + echo "${(qq)@}" + fi +} + + # Asserts #----------------------------------------------------------------------------- From 73ed8cb53e52e4f30f36558202c2652fa01e4e54 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Sat, 25 Aug 2018 20:30:42 +0700 Subject: [PATCH 05/23] scmb_expand_args: return an array to fix quoting issues --- lib/git/status_shortcuts.sh | 34 ++++++++++++--------------- test/lib/git/status_shortcuts_test.sh | 20 ++++++++++------ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/lib/git/status_shortcuts.sh b/lib/git/status_shortcuts.sh index 69410a2..f7c57cf 100644 --- a/lib/git/status_shortcuts.sh +++ b/lib/git/status_shortcuts.sh @@ -79,8 +79,8 @@ git_add_shortcuts() { git_silent_add_shortcuts() { if [ -n "$1" ]; then # Expand args and process resulting set of files. - IFS=$'\t' - for file in $(scmb_expand_args "$@"); do + eval "args=$(scmb_expand_args "$@")" # create $args array + for file in "${args[@]}"; do # Use 'git rm' if file doesn't exist and 'ga_auto_remove' is enabled. if [[ $ga_auto_remove == "yes" ]] && ! [ -e "$file" ]; then echo -n "# " @@ -90,7 +90,6 @@ git_silent_add_shortcuts() { echo -e "# Added '$file'" fi done - unset IFS echo "#" fi } @@ -121,32 +120,29 @@ scmb_expand_args() { shift fi - first=1 - OLDIFS="$IFS"; IFS=" " # We need to split on spaces to loop over expanded range + local -a args=() # initially empty array for arg in "$@"; do if [[ "$arg" =~ ^[0-9]{0,4}$ ]] ; then # Substitute $e{*} variables for any integers - if [ "$first" -eq 1 ]; then first=0; else printf '\t'; fi if [ -e "$arg" ]; then # Don't expand files or directories with numeric names - printf '%s' "$arg" + args+=("$arg") else - _print_path "$relative" "$git_env_char$arg" + args+=("$(_print_path "$relative" "$git_env_char$arg")") fi elif [[ "$arg" =~ ^[0-9]+-[0-9]+$ ]]; then # Expand ranges into $e{*} variables - for i in $(eval echo {${arg/-/..}}); do - if [ "$first" -eq 1 ]; then first=0; else printf '\t'; fi - _print_path "$relative" "$git_env_char$i" + args+=("$(_print_path "$relative" "$git_env_char$i")") done else # Otherwise, treat $arg as a normal string. - if [ "$first" -eq 1 ]; then first=0; else printf '\t'; fi - printf '%s' "$arg" + args+=("$arg") fi done - IFS="$OLDIFS" + args=$(declare -p args) # Get $args array as a string which can be `eval`-ed to recreate itself + args=${args#*=} # Remove `typeset -a args=` from beginning of string to allow caller to name variable + echo "$args" } -# Expand a variable into a (possibly relative) pathname +# Expand a variable (named by $2) into a (possibly relative) pathname _print_path() { local pathname=$(eval printf '%s' "\"\${$2}\"") if [ "$1" = 1 ]; then # print relative @@ -158,7 +154,8 @@ _print_path() { # Execute a command with expanded args, e.g. Delete files 6 to 12: $ ge rm 6-12 # Fails if command is a number or range (probably not worth fixing) exec_scmb_expand_args() { - eval "$(scmb_expand_args "$@" | sed -e "s/\([][|;()<>^ \"'&]\)/"'\\\1/g')" + eval "args=$(scmb_expand_args "$@")" # create $args array + _safe_eval "${args[@]}" } # Clear numbered env variables @@ -180,13 +177,12 @@ git_clear_vars() { _git_resolve_merge_conflict() { if [ -n "$2" ]; then # Expand args and process resulting set of files. - IFS=$'\t' - for file in $(scmb_expand_args "${@:2}"); do + eval "args=$(scmb_expand_args "$@")" # create $args array + for file in "${args[@]:2}"; do git checkout "--$1""s" "$file" # "--$1""s" is expanded to --ours or --theirs git add "$file" echo -e "# Added $1 version of '$file'" done - unset IFS echo -e "# -- If you have finished resolving conflicts, commit the resolutions with 'git commit'" fi } diff --git a/test/lib/git/status_shortcuts_test.sh b/test/lib/git/status_shortcuts_test.sh index d83d9ca..57b861b 100755 --- a/test/lib/git/status_shortcuts_test.sh +++ b/test/lib/git/status_shortcuts_test.sh @@ -49,16 +49,22 @@ setupTestRepo() { #----------------------------------------------------------------------------- test_scmb_expand_args() { - local e1="one"; local e2="two"; local e3="three"; local e4="four"; local e5="five"; local e6="six"; local e7="seven" + local e1="one" e2="two" e3="three" e4="four" e5="five" e6="six" e7='$dollar' e8='two words' local error="Args not expanded correctly" - assertEquals "$error" "$(printf 'one\tthree\tseven')" "$(scmb_expand_args 1 3 7)" - assertEquals "$error" "$(printf 'one\ttwo\tthree\tsix')" "$(scmb_expand_args 1-3 6)" - assertEquals "$error" "$(printf 'seven\ttwo\tthree\tfour\tfive\tone')" "$(scmb_expand_args seven 2-5 1)" + assertEquals "$error" "'one' 'three' 'six'" \ + "$(eval a=$(scmb_expand_args 1 3 6); token_quote "${a[@]}")" + assertEquals "$error" "'one' 'two' 'three' 'five'" \ + "$(eval a=$(scmb_expand_args 1-3 5); token_quote "${a[@]}")" + assertEquals "$error" "'\$dollar' 'two' 'three' 'four' 'one'" \ + "$(eval a=$(scmb_expand_args 7 2-4 1); token_quote "${a[@]}")" # Test that any args with spaces remain quoted - assertEquals "$error" "$(printf -- '-m\tTest Commit Message\tone')" "$(scmb_expand_args -m "Test Commit Message" 1)" - assertEquals "$error" "$(printf -- '-ma\tTest Commit Message\tUnquoted')"\ - "$(scmb_expand_args -ma "Test Commit Message" "Unquoted")" + assertEquals "$error" "'-m' 'Test Commit Message' 'one'" \ + "$(eval a=$(scmb_expand_args -m "Test Commit Message" 1); token_quote "${a[@]}")" + assertEquals "$error" "'-ma' 'Test Commit Message' 'Unquoted'"\ + "$(eval a=$(scmb_expand_args -ma "Test Commit Message" "Unquoted"); token_quote "${a[@]}")" + assertEquals "$error" "'\$dollar' 'one' 'two words'" \ + "$(eval a=$(scmb_expand_args 7 1-1 8); token_quote "${a[@]}")" } test_command_wrapping_escapes_special_characters() { From b193438ca1fbadcd0495cb43c40487b17f3602e4 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Mon, 27 Aug 2018 18:00:36 +0700 Subject: [PATCH 06/23] shell_shortcuts_test: Unset $QUOTING_STYLE for predictable ls output --- test/lib/git/shell_shortcuts_test.sh | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test/lib/git/shell_shortcuts_test.sh b/test/lib/git/shell_shortcuts_test.sh index 3842590..77dd21c 100755 --- a/test/lib/git/shell_shortcuts_test.sh +++ b/test/lib/git/shell_shortcuts_test.sh @@ -69,6 +69,15 @@ assertAliasEquals(){ } +#----------------------------------------------------------------------------- +# Setup and tear down +#----------------------------------------------------------------------------- + +setUp() { + unset QUOTING_STYLE # Use default quoting style for ls +} + + #----------------------------------------------------------------------------- # Unit tests #----------------------------------------------------------------------------- @@ -93,7 +102,7 @@ test_ls_with_file_shortcuts() { # full physical path to be absolutely certain when doing comparisons later, # because thats how the Ruby status_shortcuts.rb script is going to obtain # them. - cd $TEST_DIR + cd "$TEST_DIR" TEST_DIR=$(pwd -P) touch 'test file' 'test_file' @@ -103,8 +112,8 @@ test_ls_with_file_shortcuts() { # Run command in shell, load output from temp file into variable # (This is needed so that env variables are exported in the current shell) temp_file=$(mktemp -t scm_breeze.XXXXXXXXXX) - ls_with_file_shortcuts > $temp_file - ls_output=$(<$temp_file strip_colors) + ls_with_file_shortcuts > "$temp_file" + ls_output=$(<"$temp_file" strip_colors) # Compare as fixed strings (F), instead of normal grep behavior assertIncludes "$ls_output" '[1] a "b"' F From ed63b41182a9abe2c4339adde2fe9ae9b7430eaf Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Mon, 27 Aug 2018 18:04:20 +0700 Subject: [PATCH 07/23] test_helper: Don't clobber global name/email config Config included by gitconfig [include] is not considered global. Test git config values without --global to include local config as well. Change only local config (not global) if needed. --- test/support/test_helper.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/support/test_helper.sh b/test/support/test_helper.sh index 4649373..d5a9854 100644 --- a/test/support/test_helper.sh +++ b/test/support/test_helper.sh @@ -4,9 +4,9 @@ orig_cwd="$PWD" source "$scmbDir/lib/git/helpers.sh" # Set up demo git user if not configured -if [ -z "$(git config --global user.email)" ]; then - git config --global user.email "testuser@example.com" - git config --global user.name "Test User" +if [ -z "$(git config user.email)" ]; then + git config user.email "testuser@example.com" + git config user.name "Test User" fi # From 5a8c2fec1264366602d8c60bc3e495251a96e8be Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Mon, 27 Aug 2018 18:21:03 +0700 Subject: [PATCH 08/23] assert(Not)?Includes: check for failure of _includes Call _includes and then check its return value. shunit2 uses the exit code to test, not a string of 0 or 1 as was done previously. --- test/support/test_helper.sh | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/test/support/test_helper.sh b/test/support/test_helper.sh index d5a9854..de8cd1b 100644 --- a/test/support/test_helper.sh +++ b/test/support/test_helper.sh @@ -50,16 +50,22 @@ function token_quote { # Asserts #----------------------------------------------------------------------------- +# Return 0 (shell's true) if "$1" contains string "$2" _includes() { if [ -n "$3" ]; then regex="$3"; else regex=''; fi - if echo "$1" | grep -q$regex "$2"; then echo 0; else echo 1; fi + echo "$1" | grep -q"$regex" "$2" # exit status of quiet grep is returned } # assert $1 contains $2 assertIncludes() { - assertTrue "'$1' should have contained '$2'" $(_includes "$@") + _includes "$@" + local grep_exit=$? + assertTrue "'$1' should have contained '$2'" '[[ $grep_exit == 0 ]]' } + # assert $1 does not contain $2 assertNotIncludes() { - assertFalse "'$1' should not have contained '$2'" $(_includes "$@") + _includes "$@" + local grep_return=$? + assertTrue "'$1' should not have contained '$2'" '[[ ! $grep_exit = 0 ]]' } From 0b5a29bbd60bc65fd5c9377fece9ad3a14e2bb56 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Thu, 30 Aug 2018 18:42:41 +0700 Subject: [PATCH 09/23] Invoke readlink -f in a way which can succeed --- lib/git/shell_shortcuts.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index 6c4a972..d7fb8c4 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -100,10 +100,10 @@ if ! ls --color=auto > /dev/null 2>&1; then fi # Test if readlink supports -f option, otherwise use perl (a bit slower) -if ! readlink -f > /dev/null 2>&1; then - _abs_path_command='perl -e "use Cwd "abs_path"; print abs_path(shift)"' +if ! readlink -f / > /dev/null 2>&1; then + _abs_path_command=(perl -e 'use Cwd abs_path; print abs_path(shift)') else - _abs_path_command="readlink -f" + _abs_path_command=(readlink -f) fi # Function wrapper around 'll' From f55ce1e6a3cb04b6211430e48da9fd1a08dd928f Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Fri, 31 Aug 2018 17:03:30 +0700 Subject: [PATCH 10/23] test_ls_with_file_shortcuts: clean up temp files --- test/lib/git/shell_shortcuts_test.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/lib/git/shell_shortcuts_test.sh b/test/lib/git/shell_shortcuts_test.sh index 77dd21c..7bab1d3 100755 --- a/test/lib/git/shell_shortcuts_test.sh +++ b/test/lib/git/shell_shortcuts_test.sh @@ -138,6 +138,9 @@ test_ls_with_file_shortcuts() { # Test arg with no quotes ls_output=$(ls_with_file_shortcuts a\ \"b\" | strip_colors) assertIncludes "$ls_output" '[1] c' F + + cd - + rm -r "$TEST_DIR" "$temp_file" } # load and run shUnit2 From a8092bffd9cb45c5845c3e43b62b3b333b01d69f Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Sat, 1 Sep 2018 13:27:53 +0700 Subject: [PATCH 11/23] Clean up ls_with_file_shortcuts() shwordsplit tests --- lib/git/shell_shortcuts.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index d7fb8c4..1cf72f7 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -120,7 +120,7 @@ if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then if [[ $shell == "zsh" ]]; then # Ensure sh_word_split is on - if setopt | grep -q shwordsplit; then SHWORDSPLIT_ON=true; fi + [[ -o shwordsplit ]] && SHWORDSPLIT_ON=true setopt shwordsplit fi @@ -144,6 +144,7 @@ if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then ll_output=$(echo "$ll_output" | \sed -$SED_REGEX_ARG "s/ $USER/ $(/bin/cat "$HOME/.user_sym")/g" | rejustify_ls_columns) fi + # Bail if there are two many lines to process if [ "$(echo "$ll_output" | wc -l)" -gt "50" ]; then echo -e "\033[33mToo many files to create shortcuts. Running plain ll command...\033[0m" echo "$ll_output" @@ -183,7 +184,7 @@ EOF done # Turn off shwordsplit unless it was on previously - if [[ $shell == "zsh" ]] && [ -z "$SHWORDSPLIT_ON" ]; then unsetopt shwordsplit; fi + if [[ $shell == "zsh" && -z $SHWORDSPLIT_ON ]]; then unsetopt shwordsplit; fi } # Setup aliases From 695941b596091a70c11e2ea6f29a8a6f86245055 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Sat, 1 Sep 2018 16:25:36 +0700 Subject: [PATCH 12/23] Add tests for exec_scmb_expand_args() --- test/lib/git/status_shortcuts_test.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/lib/git/status_shortcuts_test.sh b/test/lib/git/status_shortcuts_test.sh index 57b861b..3f54c5d 100755 --- a/test/lib/git/status_shortcuts_test.sh +++ b/test/lib/git/status_shortcuts_test.sh @@ -67,6 +67,19 @@ test_scmb_expand_args() { "$(eval a=$(scmb_expand_args 7 1-1 8); token_quote "${a[@]}")" } +test_exec_scmb_expand_args() { + local e1="one" e2="a b c" e3='$dollar' e4="single'quote" e5='double"quote' e6='a(){:;};a&' + assertEquals "literals with spaces not preserved" "'foo' 'bar baz'" \ + "$(eval a="$(scmb_expand_args foo 'bar baz')"; token_quote "${a[@]}")" + assertEquals "variables with spaces not preserved" "'one' 'a b c'" \ + "$(eval a="$(scmb_expand_args 1-2)"; token_quote "${a[@]}")" + # Expecting text: '$dollar' "single'quote" 'double"quote' + # Generate quoted expected string with: token_quote "$(cat)" then copy/paste, ^D + assertEquals "special characters are preserved" \ + "'\$dollar' 'single'\\''quote' 'double\"quote' 'a(){:;};a&'" \ + "$(eval a="$(scmb_expand_args 3-6)"; token_quote "${a[@]}")" +} + test_command_wrapping_escapes_special_characters() { assertEquals 'should escape | the pipe' "$(exec_scmb_expand_args echo "should escape | the pipe")" assertEquals 'should escape ; the semicolon' "$(exec_scmb_expand_args echo "should escape ; the semicolon")" From 86d9d4b189ee8bdeae78783817d18ee1c8106409 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Sat, 1 Sep 2018 16:26:54 +0700 Subject: [PATCH 13/23] shell_shortcuts.sh: fix quoting --- lib/git/shell_shortcuts.sh | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index 1cf72f7..d852f8e 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -29,13 +29,13 @@ if [ "$shell_command_wrapping_enabled" = "true" ] || [ "$bash_command_wrapping_e # Special check for 'cd', to make sure SCM Breeze is loaded after RVM if [ "$cmd" = 'cd' ]; then if [ -e "$HOME/.rvm" ] && ! type rvm > /dev/null 2>&1; then - echo -e "\033[0;31mSCM Breeze must be loaded \033[1;31mafter\033[0;31m RVM, otherwise there will be a conflict when RVM wraps the 'cd' command.\033[0m" - echo -e "\033[0;31mPlease move the line that loads SCM Breeze to the bottom of your ~/.bashrc\033[0m" + echo -e "\\033[0;31mSCM Breeze must be loaded \\033[1;31mafter\\033[0;31m RVM, otherwise there will be a conflict when RVM wraps the 'cd' command.\\033[0m" + echo -e "\\033[0;31mPlease move the line that loads SCM Breeze to the bottom of your ~/.bashrc\\033[0m" continue fi fi - case "$(LC_MESSAGES="C" type $cmd 2>&1)" in + case "$(LC_MESSAGES="C" type "$cmd" 2>&1)" in # Don't do anything if command already aliased, or not found. *'exec_scmb_expand_args'*) @@ -49,10 +49,10 @@ if [ "$shell_command_wrapping_enabled" = "true" ] || [ "$bash_command_wrapping_e # Store original alias local original_alias="$(whence $cmd)" # Remove alias, so that we can find binary - unalias $cmd + unalias "$cmd" # Detect original $cmd type, and escape - case "$(LC_MESSAGES="C" type $cmd 2>&1)" in + case "$(LC_MESSAGES="C" type "$cmd" 2>&1)" in # Escape shell builtins with 'builtin' *'is a shell builtin'*) local escaped_cmd="builtin $cmd";; # Get full path for files with 'find_binary' function @@ -67,9 +67,9 @@ if [ "$shell_command_wrapping_enabled" = "true" ] || [ "$bash_command_wrapping_e *'is a'*'function'*) if [ "${scmbDebug:-}" = "true" ]; then echo "SCMB: $cmd is a function"; fi # Copy old function into new name - eval "$(declare -f $cmd | sed -$SED_REGEX_ARG "s/^$cmd \(\)/__original_$cmd ()/")" + eval "$(declare -f "$cmd" | sed -"$SED_REGEX_ARG" "s/^$cmd \\(\\)/__original_$cmd ()/")" # Remove function - unset -f $cmd + unset -f "$cmd" # Create function that wraps old function eval "${cmd}(){ exec_scmb_expand_args __original_${cmd} \"\$@\"; }";; @@ -133,7 +133,7 @@ if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then # Replace user/group with user symbol, if defined at ~/.user_sym # Before : -rw-rw-r-- 1 ndbroadbent ndbroadbent 1.1K Sep 19 21:39 scm_breeze.sh # After : -rw-rw-r-- 1 𝐍 𝐍 1.1K Sep 19 21:39 scm_breeze.sh - if [ -e $HOME/.user_sym ]; then + if [ -e "$HOME"/.user_sym ]; then # Little bit of ruby golf to rejustify the user/group/size columns after replacement function rejustify_ls_columns(){ ruby -e "o=STDIN.read;re=/^(([^ ]* +){2})(([^ ]* +){3})/;\ @@ -178,7 +178,7 @@ EOF local IFS=$'\n' for file in $ll_files; do if [ -n "$rel_path" ]; then file="$rel_path/$file"; fi - export $git_env_char$e="$(eval $_abs_path_command \"${file//\"/\\\"}\")" + export $git_env_char$e="$(_safe_eval "${_abs_path_command[@]}" "$file")" if [ "${scmbDebug:-}" = "true" ]; then echo "Set \$$git_env_char$e => $file"; fi let e++ done From a6eeebfa91e51ec7aae21f65e0df49e461a4ca68 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Sat, 1 Sep 2018 16:27:24 +0700 Subject: [PATCH 14/23] shell_shortcuts.sh: remove unnecessary subshell --- lib/git/shell_shortcuts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index d852f8e..583b77a 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -23,7 +23,7 @@ if [ "$shell_command_wrapping_enabled" = "true" ] || [ "$bash_command_wrapping_e # Define 'whence' for bash, to get the value of an alias type whence > /dev/null 2>&1 || function whence() { LC_MESSAGES="C" type "$@" | sed -$SED_REGEX_ARG -e "s/.*is aliased to \`//" -e "s/'$//"; } local cmd='' - for cmd in $(echo $scmb_wrapped_shell_commands); do + for cmd in $scmb_wrapped_shell_commands; do if [ "${scmbDebug:-}" = "true" ]; then echo "SCMB: Wrapping $cmd..."; fi # Special check for 'cd', to make sure SCM Breeze is loaded after RVM From a580c5300463f3071ed02176bd4b507ee1cf551b Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Sat, 1 Sep 2018 16:28:31 +0700 Subject: [PATCH 15/23] ls_with_file_shortcuts: print error to STDERR --- lib/git/shell_shortcuts.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index 583b77a..e3ca7f5 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -146,7 +146,7 @@ if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then # Bail if there are two many lines to process if [ "$(echo "$ll_output" | wc -l)" -gt "50" ]; then - echo -e "\033[33mToo many files to create shortcuts. Running plain ll command...\033[0m" + echo -e '\033[33mToo many files to create shortcuts. Running plain ll command...\033[0m' >&2 echo "$ll_output" return 1 fi From 21d155776ab0b368af1df67995af06ba03fbd182 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Wed, 12 Sep 2018 15:44:51 +0700 Subject: [PATCH 16/23] Allow run_tests.sh to be executed from anywhere --- run_tests.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/run_tests.sh b/run_tests.sh index e25c5eb..8827f8b 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -8,8 +8,9 @@ failed=false if [ -z "$TEST_SHELLS" ]; then TEST_SHELLS="bash zsh" fi - echo "== Will run all tests with following shells: ${TEST_SHELLS}" + +builtin cd -P -- "${0%/*}" # Change to directory this script lives in for test in $(find test/lib -name *_test.sh); do for shell in $TEST_SHELLS; do echo "== Running tests with [$shell]: $test" From 80ec1ad2adc962274f9dd0a2fd8d543dda344318 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Wed, 12 Sep 2018 17:57:46 +0700 Subject: [PATCH 17/23] Replace built-in shell array quoting with printf %q --- lib/scm_breeze.sh | 43 +++++++++++++++++---- test/lib/git/status_shortcuts_test.sh | 54 +++++++++++++++++++++------ test/lib/scm_breeze_test.sh | 6 +-- test/support/test_helper.sh | 14 ------- 4 files changed, 81 insertions(+), 36 deletions(-) diff --git a/lib/scm_breeze.sh b/lib/scm_breeze.sh index b177db9..c9cb87e 100644 --- a/lib/scm_breeze.sh +++ b/lib/scm_breeze.sh @@ -18,18 +18,45 @@ _alias() { fi } +# Quote the contents of "$@" +function token_quote { + # Older versions of {ba,z}sh don't support the built-in quoting, so fall back to printf %q + local quoted=() + for token; do + quoted+=( "$(printf '%q' "$token")" ) + done + printf '%s\n' "${quoted[*]}" + + # Keep this code for use when minimum versions of {ba,z}sh can be increased. + # See https://github.com/scmbreeze/scm_breeze/issues/260 + # + # if [[ $shell = bash ]]; then + # # ${parameter@operator} where parameter is ${@} and operator is 'Q' + # # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + # eval "${@@Q}" + # else # zsh + # # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags + # eval "${(q-)@}" + # fi +} + # Quote "$@" before `eval` to prevent arbitrary code execution. # Eg, the following will run `date`: # evil() { eval "$@"; }; evil "echo" "foo;date" function _safe_eval() { - if [[ $shell = bash ]]; then - # ${parameter@operator} where parameter is ${@} and operator is 'Q' - # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html - eval "${@@Q}" - else # zsh - # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags - eval "${(q-)@}" - fi + eval $(token_quote "$@") + + # Keep this code for use when minimum versions of {ba,z}sh can be increased. + # See https://github.com/scmbreeze/scm_breeze/issues/260 + # + # if [[ $shell = bash ]]; then + # # ${parameter@operator} where parameter is ${@} and operator is 'Q' + # # https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html + # eval "${@@Q}" + # else # zsh + # # http://zsh.sourceforge.net/Doc/Release/Expansion.html#Parameter-Expansion-Flags + # eval "${(q-)@}" + # fi } find_binary(){ diff --git a/test/lib/git/status_shortcuts_test.sh b/test/lib/git/status_shortcuts_test.sh index 3f54c5d..0cbf23b 100755 --- a/test/lib/git/status_shortcuts_test.sh +++ b/test/lib/git/status_shortcuts_test.sh @@ -51,38 +51,70 @@ setupTestRepo() { test_scmb_expand_args() { local e1="one" e2="two" e3="three" e4="four" e5="five" e6="six" e7='$dollar' e8='two words' local error="Args not expanded correctly" - assertEquals "$error" "'one' 'three' 'six'" \ + assertEquals "$error" 'one three six' \ "$(eval a=$(scmb_expand_args 1 3 6); token_quote "${a[@]}")" - assertEquals "$error" "'one' 'two' 'three' 'five'" \ + assertEquals "$error" 'one two three five' \ "$(eval a=$(scmb_expand_args 1-3 5); token_quote "${a[@]}")" - assertEquals "$error" "'\$dollar' 'two' 'three' 'four' 'one'" \ + assertEquals "$error" '\$dollar two three four one' \ "$(eval a=$(scmb_expand_args 7 2-4 1); token_quote "${a[@]}")" # Test that any args with spaces remain quoted - assertEquals "$error" "'-m' 'Test Commit Message' 'one'" \ + assertEquals "$error" '-m Test\ Commit\ Message one' \ "$(eval a=$(scmb_expand_args -m "Test Commit Message" 1); token_quote "${a[@]}")" - assertEquals "$error" "'-ma' 'Test Commit Message' 'Unquoted'"\ + assertEquals "$error" '-ma Test\ Commit\ Message Unquoted'\ "$(eval a=$(scmb_expand_args -ma "Test Commit Message" "Unquoted"); token_quote "${a[@]}")" - assertEquals "$error" "'\$dollar' 'one' 'two words'" \ + assertEquals "$error" '\$dollar one two\ words' \ "$(eval a=$(scmb_expand_args 7 1-1 8); token_quote "${a[@]}")" + + # Keep this code for use when minimum versions of {ba,z}sh can be increased. + # See token_quote() source and https://github.com/scmbreeze/scm_breeze/issues/260 + # local e1="one" e2="two" e3="three" e4="four" e5="five" e6="six" e7='$dollar' e8='two words' + # local error="Args not expanded correctly" + # assertEquals "$error" "'one' 'three' 'six'" \ + # "$(eval a=$(scmb_expand_args 1 3 6); token_quote "${a[@]}")" + # assertEquals "$error" "'one' 'two' 'three' 'five'" \ + # "$(eval a=$(scmb_expand_args 1-3 5); token_quote "${a[@]}")" + # assertEquals "$error" "'\$dollar' 'two' 'three' 'four' 'one'" \ + # "$(eval a=$(scmb_expand_args 7 2-4 1); token_quote "${a[@]}")" + # + # # Test that any args with spaces remain quoted + # assertEquals "$error" "'-m' 'Test Commit Message' 'one'" \ + # "$(eval a=$(scmb_expand_args -m "Test Commit Message" 1); token_quote "${a[@]}")" + # assertEquals "$error" "'-ma' 'Test Commit Message' 'Unquoted'"\ + # "$(eval a=$(scmb_expand_args -ma "Test Commit Message" "Unquoted"); token_quote "${a[@]}")" + # assertEquals "$error" "'\$dollar' 'one' 'two words'" \ + # "$(eval a=$(scmb_expand_args 7 1-1 8); token_quote "${a[@]}")" } test_exec_scmb_expand_args() { local e1="one" e2="a b c" e3='$dollar' e4="single'quote" e5='double"quote' e6='a(){:;};a&' - assertEquals "literals with spaces not preserved" "'foo' 'bar baz'" \ + assertEquals "literals with spaces not preserved" 'foo bar\ baz' \ "$(eval a="$(scmb_expand_args foo 'bar baz')"; token_quote "${a[@]}")" - assertEquals "variables with spaces not preserved" "'one' 'a b c'" \ + assertEquals "variables with spaces not preserved" 'one a\ b\ c' \ "$(eval a="$(scmb_expand_args 1-2)"; token_quote "${a[@]}")" # Expecting text: '$dollar' "single'quote" 'double"quote' # Generate quoted expected string with: token_quote "$(cat)" then copy/paste, ^D assertEquals "special characters are preserved" \ - "'\$dollar' 'single'\\''quote' 'double\"quote' 'a(){:;};a&'" \ + '\$dollar single\'\''quote double\"quote a\(\)\{:\;\}\;a\&' \ "$(eval a="$(scmb_expand_args 3-6)"; token_quote "${a[@]}")" + + # Keep this code for use when minimum versions of {ba,z}sh can be increased. + # See token_quote() source and https://github.com/scmbreeze/scm_breeze/issues/260 + # local e1="one" e2="a b c" e3='$dollar' e4="single'quote" e5='double"quote' e6='a(){:;};a&' + # assertEquals "literals with spaces not preserved" "'foo' 'bar baz'" \ + # "$(eval a="$(scmb_expand_args foo 'bar baz')"; token_quote "${a[@]}")" + # assertEquals "variables with spaces not preserved" "'one' 'a b c'" \ + # "$(eval a="$(scmb_expand_args 1-2)"; token_quote "${a[@]}")" + # # Expecting text: '$dollar' "single'quote" 'double"quote' + # # Generate quoted expected string with: token_quote "$(cat)" then copy/paste, ^D + # assertEquals "special characters are preserved" \ + # "'\$dollar' 'single'\\''quote' 'double\"quote' 'a(){:;};a&'" \ + # "$(eval a="$(scmb_expand_args 3-6)"; token_quote "${a[@]}")" } test_command_wrapping_escapes_special_characters() { - assertEquals 'should escape | the pipe' "$(exec_scmb_expand_args echo "should escape | the pipe")" - assertEquals 'should escape ; the semicolon' "$(exec_scmb_expand_args echo "should escape ; the semicolon")" + assertEquals 'should escape | the pipe' "$(exec_scmb_expand_args echo should escape '|' the pipe)" + assertEquals 'should escape ; the semicolon' "$(exec_scmb_expand_args echo should escape ';' the semicolon)" } test_git_status_shortcuts() { diff --git a/test/lib/scm_breeze_test.sh b/test/lib/scm_breeze_test.sh index f74beef..5a5a8da 100755 --- a/test/lib/scm_breeze_test.sh +++ b/test/lib/scm_breeze_test.sh @@ -16,9 +16,9 @@ source "$scmbDir/lib/scm_breeze.sh" #----------------------------------------------------------------------------- test__safe_eval() { - assertEquals "runs eval with simple words" "'one' 'two' 'three'" "$(_safe_eval token_quote one two three)" - assertEquals "quotes spaces" "'a' 'b c' 'd'" "$(_safe_eval token_quote a b\ c d)" - assertEquals "quotes special chars" "'a b' '\$dollar' '\\slash' 'c d'" "$(_safe_eval token_quote a\ b '$dollar' '\slash' c\ d)" + assertEquals "runs eval with simple words" 'one two three' "$(_safe_eval token_quote one two three)" + assertEquals "quotes spaces" 'a b\ c d' "$(_safe_eval token_quote a b\ c d)" + assertEquals "quotes special chars" 'a\ b \$dollar \\slash c\ d' "$(_safe_eval token_quote a\ b '$dollar' '\slash' c\ d)" } diff --git a/test/support/test_helper.sh b/test/support/test_helper.sh index de8cd1b..67a6155 100644 --- a/test/support/test_helper.sh +++ b/test/support/test_helper.sh @@ -32,20 +32,6 @@ verboseGitCommands() { unset -f git } -# Quote the contents of "$@" in single quotes -# Avoid printf '%q' as 'a b' becomes a\ b in both {ba,z}sh -# See also quote_args and double_quote -function token_quote { - if [[ $shell = bash ]]; then - # Single quotes are always added - echo "${@@Q}" - else # zsh - # Single quotes only added when needed - #shellcheck disable=2154 # zsh - echo "${(qq)@}" - fi -} - # Asserts #----------------------------------------------------------------------------- From e46a7c3309b98d31f0c2e93a1221f5659891a1ba Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Fri, 14 Sep 2018 15:42:43 +0700 Subject: [PATCH 18/23] ls_with_file_shortcuts: fail rather than create incorrect variables Removes the danger from issue #274. The proper fix will need to address issue #260. --- lib/git/shell_shortcuts.sh | 28 +++++++++++++++++++++++----- test/lib/git/shell_shortcuts_test.sh | 15 +++++++++++++-- 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index e3ca7f5..9167787 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -124,11 +124,29 @@ if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then setopt shwordsplit fi - # Parse path from args + # Get the directory that `ls` is being run relative to. + # Only allow one directory to avoid incorrect $e# variables when listing + # multiple directories (issue #274) local IFS=$'\n' + local rel_path for arg in "$@"; do - if [ -d "$arg" ]; then local rel_path="${arg%/}"; fi + if [[ -e $arg ]]; then # Path rather than option to ls + if [[ -z $rel_path ]]; then # We are seeing our first pathname + if [[ -d $arg ]]; then # It's a directory + rel_path=$arg + else # It's a file, expand the current directory + rel_path=. + fi + elif [[ -d $arg || ( -f $arg && $rel_path != . ) ]]; then + if [[ -f $arg ]]; then arg=$PWD; fi # Get directory for current argument + # We've already seen a different directory. Quit to avoid damage (issue #274) + printf 'scm_breeze: Cannot list relative to both directories:\n %s\n %s\n' "$arg" "$rel_path" >&2 + printf 'Currently only listing a single directory is supported. See issue #274.\n' >&2 + return 1 + fi + fi done + rel_path=$("${_abs_path_command[@]}" ${rel_path:-$PWD}) # Replace user/group with user symbol, if defined at ~/.user_sym # Before : -rw-rw-r-- 1 ndbroadbent ndbroadbent 1.1K Sep 19 21:39 scm_breeze.sh @@ -177,9 +195,9 @@ EOF local IFS=$'\n' for file in $ll_files; do - if [ -n "$rel_path" ]; then file="$rel_path/$file"; fi - export $git_env_char$e="$(_safe_eval "${_abs_path_command[@]}" "$file")" - if [ "${scmbDebug:-}" = "true" ]; then echo "Set \$$git_env_char$e => $file"; fi + file=$rel_path/$file + export $git_env_char$e=$("${_abs_path_command[@]}" "$file") + if [[ ${scmbDebug:-} = true ]]; then echo "Set \$$git_env_char$e => $file"; fi let e++ done diff --git a/test/lib/git/shell_shortcuts_test.sh b/test/lib/git/shell_shortcuts_test.sh index 7bab1d3..7f1f6f4 100755 --- a/test/lib/git/shell_shortcuts_test.sh +++ b/test/lib/git/shell_shortcuts_test.sh @@ -107,7 +107,7 @@ test_ls_with_file_shortcuts() { touch 'test file' 'test_file' mkdir -p "a [b]" 'a "b"' "a 'b'" - touch "a \"b\"/c" + touch 'a "b"/c' # Run command in shell, load output from temp file into variable # (This is needed so that env variables are exported in the current shell) @@ -134,11 +134,22 @@ test_ls_with_file_shortcuts() { ls_output=$(<$temp_file strip_colors) assertIncludes "$ls_output" '[1] c' F # Test that env variable is set correctly - assertEquals "$TEST_DIR/a \"b\"/c" "$e1" + assertEquals "$TEST_DIR/"'a "b"/c' "$e1" # Test arg with no quotes ls_output=$(ls_with_file_shortcuts a\ \"b\" | strip_colors) assertIncludes "$ls_output" '[1] c' F + # Listing two directories fails (see issue #275) + mkdir 1 2 + touch 1/file + assertFalse 'Only one directory supported' 'ls_with_file_shortcuts 1 2' + assertFalse 'Fails on ' 'ls_with_file_shortcuts 1 test_file' + assertFalse 'Fails on ' 'ls_with_file_shortcuts test_file 1' + assertFalse 'Fails on /' 'ls_with_file_shortcuts 1 1/file' + + # Files under the root directory + assertTrue 'Shortcuts under /' 'ls_with_file_shortcuts / > /dev/null && [[ $e1 =~ ^/[^/]+$ ]]' + cd - rm -r "$TEST_DIR" "$temp_file" } From 4adf7714924709a3a17c960b3af2513bfe48c738 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Fri, 14 Sep 2018 19:20:39 +0700 Subject: [PATCH 19/23] Ensure file ordering matches in both invocations of ls --- lib/git/shell_shortcuts.sh | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index 9167787..c41d6d0 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -112,10 +112,13 @@ if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then unalias ll > /dev/null 2>&1; unset -f ll > /dev/null 2>&1 function ls_with_file_shortcuts { local ll_output + local ll_command # Ensure sort ordering of the two invocations is the same if [ "$_ls_bsd" != "BSD" ]; then - ll_output="$(\ls -lhv --group-directories-first --color "$@")" + ll_command=(\ls -hv --group-directories-first) + ll_output="$("${ll_command[@]}" -l --color "$@")" else - ll_output="$(CLICOLOR_FORCE=1 \ls -l -G "$@")" + ll_command=(\ls) + ll_output="$(CLICOLOR_FORCE=1 "${ll_command[@]}" -lG "$@")" fi if [[ $shell == "zsh" ]]; then @@ -187,10 +190,14 @@ EOF local ll_files='' local file='' + # XXX FIXME XXX + # There is a race condition here: If a file is removed between the above + # and this second call of `ls` then the $e# variables can refer to the + # wrong files. if [ -z $_ls_bsd ]; then - ll_files="$(\ls -v --group-directories-first --color=never "$@")" + ll_files="$(QUOTING_STYLE=literal "${ll_command[@]}" --color=never "$@")" else - ll_files="$(\ls "$@")" + ll_files="$("${ll_command[@]}" "$@")" fi local IFS=$'\n' From f85b9a4727835c4a0fa3fdad369976a3316b02d8 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Fri, 14 Sep 2018 19:21:47 +0700 Subject: [PATCH 20/23] git_show_affected_files: make $f local --- lib/git/status_shortcuts.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/git/status_shortcuts.sh b/lib/git/status_shortcuts.sh index f7c57cf..05f8a80 100644 --- a/lib/git/status_shortcuts.sh +++ b/lib/git/status_shortcuts.sh @@ -82,7 +82,7 @@ git_silent_add_shortcuts() { eval "args=$(scmb_expand_args "$@")" # create $args array for file in "${args[@]}"; do # Use 'git rm' if file doesn't exist and 'ga_auto_remove' is enabled. - if [[ $ga_auto_remove == "yes" ]] && ! [ -e "$file" ]; then + if [[ $ga_auto_remove = yes && ! -e $file ]]; then echo -n "# " git rm "$file" else @@ -98,7 +98,7 @@ git_silent_add_shortcuts() { # and exports numbered environment variables for each file. git_show_affected_files(){ fail_if_not_git_repo || return 1 - f=0 # File count + local f=0 # File count # Show colored revision and commit message echo -n "# "; git show --oneline --name-only "$@" | head -n1; echo "# " for file in $(git show --pretty="format:" --name-only "$@" | \grep -v '^$'); do From e5da83665a79ab9b2b253f7fba8d005f4917d969 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Fri, 14 Sep 2018 19:29:06 +0700 Subject: [PATCH 21/23] bin_path(): use builtin commands and avoid `which` --- lib/git/aliases.sh | 2 +- lib/git/helpers.sh | 9 ++++++++- lib/git/shell_shortcuts.sh | 2 +- test/lib/git/shell_shortcuts_test.sh | 7 ------- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/git/aliases.sh b/lib/git/aliases.sh index e9c8f59..b7b3f84 100644 --- a/lib/git/aliases.sh +++ b/lib/git/aliases.sh @@ -17,7 +17,7 @@ unalias git > /dev/null 2>&1 unset -f git > /dev/null 2>&1 # Use the full path to git to avoid infinite loop with git function -export _git_cmd="$(\which git)" +export _git_cmd="$(bin_path git)" # Wrap git with the 'hub' github wrapper, if installed (https://github.com/defunkt/hub) if type hub > /dev/null 2>&1; then export _git_cmd="hub"; fi diff --git a/lib/git/helpers.sh b/lib/git/helpers.sh index f2d4107..785bd60 100644 --- a/lib/git/helpers.sh +++ b/lib/git/helpers.sh @@ -13,4 +13,11 @@ function fail_if_not_git_repo() { return 1 fi return 0 -} \ No newline at end of file +} + +bin_path() { + if [[ -n ${ZSH_VERSION:-} ]]; + then builtin whence -cp "$1" 2> /dev/null + else builtin type -P "$1" + fi +} diff --git a/lib/git/shell_shortcuts.sh b/lib/git/shell_shortcuts.sh index c41d6d0..03348a1 100644 --- a/lib/git/shell_shortcuts.sh +++ b/lib/git/shell_shortcuts.sh @@ -108,7 +108,7 @@ fi # Function wrapper around 'll' # Adds numbered shortcuts to output of ls -l, just like 'git status' -if [ "$shell_ls_aliases_enabled" = "true" ] && which ruby > /dev/null 2>&1; then +if [ "$shell_ls_aliases_enabled" = "true" ] && builtin command -v ruby > /dev/null 2>&1; then unalias ll > /dev/null 2>&1; unset -f ll > /dev/null 2>&1 function ls_with_file_shortcuts { local ll_output diff --git a/test/lib/git/shell_shortcuts_test.sh b/test/lib/git/shell_shortcuts_test.sh index 7f1f6f4..6e470a7 100755 --- a/test/lib/git/shell_shortcuts_test.sh +++ b/test/lib/git/shell_shortcuts_test.sh @@ -21,13 +21,6 @@ fi source "$scmbDir/test/support/test_helper.sh" source "$scmbDir/lib/scm_breeze.sh" -bin_path() { - if [ -n "${ZSH_VERSION:-}" ]; - then where "$@" | tail -1 - else which "$@" - fi -} - # Setup #----------------------------------------------------------------------------- oneTimeSetUp() { From 48302bcd8c70115598d1d7323e235b591ff8b884 Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Sun, 16 Sep 2018 20:31:15 +0700 Subject: [PATCH 22/23] Be compatible with shells of Ubuntu 14.04 --- lib/git/repo_index.sh | 7 ++++--- lib/git/status_shortcuts.sh | 30 +++++++++++++++++++-------- lib/git/tools.sh | 3 ++- lib/scm_breeze.sh | 3 ++- run_tests.sh | 2 +- test/lib/git/status_shortcuts_test.sh | 18 ++++++++-------- 6 files changed, 39 insertions(+), 24 deletions(-) diff --git a/lib/git/repo_index.sh b/lib/git/repo_index.sh index b3f4005..9edcd24 100644 --- a/lib/git/repo_index.sh +++ b/lib/git/repo_index.sh @@ -202,15 +202,16 @@ _git_index_update_all_branches() { return fi - local remotes merges branches + # zsh 5.0.2 requires local separate to assignment for arrays + local remote merge remotes merges branches # Get branch configuration from .git/config local IFS=$'\n' for branch in $($GIT_BINARY branch 2> /dev/null | sed -e 's/.\{2\}\(.*\)/\1/'); do # Skip '(no branch)' if [[ "$branch" = "(no branch)" ]]; then continue; fi - local remote=$(git config --get branch.$branch.remote) - local merge=$(git config --get branch.$branch.merge) + remote=$(git config --get "branch.$branch.remote") + merge=$(git config --get "branch.$branch.merge") # Ignore branch if remote and merge is not configured if [[ -n "$remote" ]] && [[ -n "$merge" ]]; then diff --git a/lib/git/status_shortcuts.sh b/lib/git/status_shortcuts.sh index 05f8a80..04f0c96 100644 --- a/lib/git/status_shortcuts.sh +++ b/lib/git/status_shortcuts.sh @@ -79,7 +79,8 @@ git_add_shortcuts() { git_silent_add_shortcuts() { if [ -n "$1" ]; then # Expand args and process resulting set of files. - eval "args=$(scmb_expand_args "$@")" # create $args array + local args + eval args="$(scmb_expand_args "$@")" # populate $args array for file in "${args[@]}"; do # Use 'git rm' if file doesn't exist and 'ga_auto_remove' is enabled. if [[ $ga_auto_remove = yes && ! -e $file ]]; then @@ -108,8 +109,8 @@ git_show_affected_files(){ done; echo "# " } - # Allows expansion of numbered shortcuts, ranges of shortcuts, or standard paths. +# Return a string which can be `eval`ed like: eval args="$(scmb_expand_args "$@")" # Numbered shortcut variables are produced by various commands, such as: # * git_status_shortcuts() - git status implementation # * git_show_affected_files() - shows files affected by a given SHA1, etc. @@ -120,7 +121,8 @@ scmb_expand_args() { shift fi - local -a args=() # initially empty array + local args + args=() # initially empty array. zsh 5.0.2 from Ubuntu 14.04 requires this to be separated for arg in "$@"; do if [[ "$arg" =~ ^[0-9]{0,4}$ ]] ; then # Substitute $e{*} variables for any integers if [ -e "$arg" ]; then @@ -137,14 +139,22 @@ scmb_expand_args() { args+=("$arg") fi done - args=$(declare -p args) # Get $args array as a string which can be `eval`-ed to recreate itself - args=${args#*=} # Remove `typeset -a args=` from beginning of string to allow caller to name variable - echo "$args" + + # "declare -p" with zsh 5.0.2 on Ubuntu 14.04 creates a string that it cannot process: + # typeset -a args args=(one three six) + # There should be a ; between the two "args" tokens + # "declare -p" with bash 4.3.11(1) on Ubuntu 14.04 creates a string like: + # declare -a a='([0]="a" [1]="b c" [2]="d")' + # The RHS of this string is incompatible with zsh 5.0.2 and "eval args=" + + # Generate a quoted array string to assign to "eval args=" + echo "( $(token_quote "${args[@]}") )" } # Expand a variable (named by $2) into a (possibly relative) pathname _print_path() { - local pathname=$(eval printf '%s' "\"\${$2}\"") + local pathname + pathname=$(eval printf '%s' "\"\${$2}\"") if [ "$1" = 1 ]; then # print relative pathname=${pathname#$PWD/} # Remove $PWD from beginning of the path fi @@ -154,7 +164,8 @@ _print_path() { # Execute a command with expanded args, e.g. Delete files 6 to 12: $ ge rm 6-12 # Fails if command is a number or range (probably not worth fixing) exec_scmb_expand_args() { - eval "args=$(scmb_expand_args "$@")" # create $args array + local args + eval "args=$(scmb_expand_args "$@")" # populate $args array _safe_eval "${args[@]}" } @@ -177,7 +188,8 @@ git_clear_vars() { _git_resolve_merge_conflict() { if [ -n "$2" ]; then # Expand args and process resulting set of files. - eval "args=$(scmb_expand_args "$@")" # create $args array + local args + eval "args=$(scmb_expand_args "$@")" # populate $args array for file in "${args[@]:2}"; do git checkout "--$1""s" "$file" # "--$1""s" is expanded to --ours or --theirs git add "$file" diff --git a/lib/git/tools.sh b/lib/git/tools.sh index e5e2c5d..c158a77 100644 --- a/lib/git/tools.sh +++ b/lib/git/tools.sh @@ -23,6 +23,7 @@ git_remove_history() { return fi # Remove all paths passed as arguments from the history of the repo + local files files=("$@") $_git_cmd filter-branch --index-filter "$_git_cmd rm -rf --cached --ignore-unmatch ${files[*]}" HEAD # Remove the temporary history git-filter-branch otherwise leaves behind for a long time @@ -142,4 +143,4 @@ git_branch_delete_all() { commit_docs() { git commit -m "Update README / Documentation [ci skip]" -} \ No newline at end of file +} diff --git a/lib/scm_breeze.sh b/lib/scm_breeze.sh index c9cb87e..2744b00 100644 --- a/lib/scm_breeze.sh +++ b/lib/scm_breeze.sh @@ -21,7 +21,8 @@ _alias() { # Quote the contents of "$@" function token_quote { # Older versions of {ba,z}sh don't support the built-in quoting, so fall back to printf %q - local quoted=() + local quoted + quoted=() # Assign separately for zsh 5.0.2 of Ubuntu 14.04 for token; do quoted+=( "$(printf '%q' "$token")" ) done diff --git a/run_tests.sh b/run_tests.sh index 8827f8b..c0a3149 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -10,7 +10,7 @@ if [ -z "$TEST_SHELLS" ]; then fi echo "== Will run all tests with following shells: ${TEST_SHELLS}" -builtin cd -P -- "${0%/*}" # Change to directory this script lives in +cd -P -- "${0%/*}" # Change to directory this script lives in for test in $(find test/lib -name *_test.sh); do for shell in $TEST_SHELLS; do echo "== Running tests with [$shell]: $test" diff --git a/test/lib/git/status_shortcuts_test.sh b/test/lib/git/status_shortcuts_test.sh index 0cbf23b..d8b9e47 100755 --- a/test/lib/git/status_shortcuts_test.sh +++ b/test/lib/git/status_shortcuts_test.sh @@ -52,19 +52,19 @@ test_scmb_expand_args() { local e1="one" e2="two" e3="three" e4="four" e5="five" e6="six" e7='$dollar' e8='two words' local error="Args not expanded correctly" assertEquals "$error" 'one three six' \ - "$(eval a=$(scmb_expand_args 1 3 6); token_quote "${a[@]}")" + "$(eval args="$(scmb_expand_args 1 3 6)"; token_quote "${args[@]}")" assertEquals "$error" 'one two three five' \ - "$(eval a=$(scmb_expand_args 1-3 5); token_quote "${a[@]}")" + "$(eval args="$(scmb_expand_args 1-3 5)"; token_quote "${args[@]}")" assertEquals "$error" '\$dollar two three four one' \ - "$(eval a=$(scmb_expand_args 7 2-4 1); token_quote "${a[@]}")" + "$(eval args="$(scmb_expand_args 7 2-4 1)"; token_quote "${args[@]}")" # Test that any args with spaces remain quoted assertEquals "$error" '-m Test\ Commit\ Message one' \ - "$(eval a=$(scmb_expand_args -m "Test Commit Message" 1); token_quote "${a[@]}")" + "$(eval args="$(scmb_expand_args -m "Test Commit Message" 1)"; token_quote "${args[@]}")" assertEquals "$error" '-ma Test\ Commit\ Message Unquoted'\ - "$(eval a=$(scmb_expand_args -ma "Test Commit Message" "Unquoted"); token_quote "${a[@]}")" + "$(eval args="$(scmb_expand_args -ma "Test Commit Message" "Unquoted")"; token_quote "${args[@]}")" assertEquals "$error" '\$dollar one two\ words' \ - "$(eval a=$(scmb_expand_args 7 1-1 8); token_quote "${a[@]}")" + "$(eval args="$(scmb_expand_args 7 1-1 8)"; token_quote "${args[@]}")" # Keep this code for use when minimum versions of {ba,z}sh can be increased. # See token_quote() source and https://github.com/scmbreeze/scm_breeze/issues/260 @@ -89,14 +89,14 @@ test_scmb_expand_args() { test_exec_scmb_expand_args() { local e1="one" e2="a b c" e3='$dollar' e4="single'quote" e5='double"quote' e6='a(){:;};a&' assertEquals "literals with spaces not preserved" 'foo bar\ baz' \ - "$(eval a="$(scmb_expand_args foo 'bar baz')"; token_quote "${a[@]}")" + "$(eval args="$(scmb_expand_args foo 'bar baz')"; token_quote "${args[@]}")" assertEquals "variables with spaces not preserved" 'one a\ b\ c' \ - "$(eval a="$(scmb_expand_args 1-2)"; token_quote "${a[@]}")" + "$(eval args="$(scmb_expand_args 1-2)"; token_quote "${args[@]}")" # Expecting text: '$dollar' "single'quote" 'double"quote' # Generate quoted expected string with: token_quote "$(cat)" then copy/paste, ^D assertEquals "special characters are preserved" \ '\$dollar single\'\''quote double\"quote a\(\)\{:\;\}\;a\&' \ - "$(eval a="$(scmb_expand_args 3-6)"; token_quote "${a[@]}")" + "$(eval args="$(scmb_expand_args 3-6)"; token_quote "${args[@]}")" # Keep this code for use when minimum versions of {ba,z}sh can be increased. # See token_quote() source and https://github.com/scmbreeze/scm_breeze/issues/260 From 29db6322893b8b5c6e0f518121e6a5bd9e62432c Mon Sep 17 00:00:00 2001 From: "Tom \"Ravi\" Hale" Date: Tue, 18 Sep 2018 14:28:45 +0700 Subject: [PATCH 23/23] .travis.yml: add sudo: required Avoid Travis CI error for zsh on linux: $ ./test/support/travisci_deps.sh This job is running on container-based infrastructure, which does not allow use of 'sudo', setuid, and setgid executables. If you require sudo, add 'sudo: required' to your .travis.yml --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index d038f60..a16628d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,8 @@ env: - TEST_SHELLS=bash - TEST_SHELLS=zsh +sudo: required + install: - ./test/support/travisci_deps.sh