[pacman-dev] [PATCH] [bash_completion] Undeclared local vars/filenames with spaces/other
Fix: 3 Undeclared local vars with common enough names to warrant breakage Performance issues with _pacman trying to replicate /usr/bin/pacman with find and other slow tools. Performance issues with expanding an array (with sometimes hundreds of items) over three times. Expanding said array to remove already completed entries had the side effect of braking filenames with spaces and or \n. The full description of fixes are already posted at: http://bugs.archlinux.org/task/16630#comment55779 along with time diffs. Signed-off-by: Andres P <andres87p@gmail.com> --- contrib/bash_completion | 535 ++++++++++++++++++----------------------------- 1 files changed, 202 insertions(+), 333 deletions(-) diff --git a/contrib/bash_completion b/contrib/bash_completion index 62e5bc9..65051f5 100644 --- a/contrib/bash_completion +++ b/contrib/bash_completion @@ -1,365 +1,234 @@ -# vim: set ft=sh ts=2 sw=2 et: -# file: /etc/bash_completion.d/pacman - -# Bash completion for pacman -# Original: Manolis Tzanidakis <mtzanidakis@freemail.gr> +# pacman/makepkg completion by Andres Perera <andres87p gmail> # -# Distributed under the terms of the GNU General Public License, v2 or later. +# Distributed under the terms of the GNU General Public License v3 or +# later. # - -## initial functions - -rem_selected () -{ - # (Adapted from bash_completion by Ian Macdonald <ian@caliban.org>) - # This removes any options from the list of completions that have - # already been specified on the command line. - COMPREPLY=($(/bin/echo "${COMP_WORDS[@]}" | \ - (while read -d ' ' i; do - [ "${i}" == "" ] && continue - # flatten array with spaces on either side, - # otherwise we cannot grep on word boundaries of - # first and last word - COMPREPLY=" ${COMPREPLY[@]} " - # remove word from list of completions - COMPREPLY=(${COMPREPLY/ ${i%% *} / }) +# Local variables: common core cur glob list long m o prev query r +# remove s short sync upgrade w +# +# vim: ft=sh sts=2 sw=2 et: + +# Removes packages/files already present in the line +_arch_rem_selected() { + local w r + + for (( w=0; w<${#COMP_WORDS[@]}-1; w++)); do + # Don't touch operands + [[ ${COMP_WORDS[w]} == -* ]] && continue + for r in "${!COMPREPLY[@]}"; do + # Only deal with full matches + if [[ ${COMP_WORDS[w]} == ${COMPREPLY[r]} ]]; then + unset 'COMPREPLY[r]'; break + fi done - /bin/echo ${COMPREPLY[@]}))) - return 0 -} - -_available_repos () -{ - COMPREPLY=( $( compgen -W "$(/bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e 'options' -e '^#' | tr -d '[]' )" -- $cur ) ) -} - -_installed_pkgs () -{ - local installed_pkgs - installed_pkgs=$( /bin/ls /var/lib/pacman/local/ ) - COMPREPLY=( $( compgen -W "$( for i in $installed_pkgs; do /bin/echo ${i%-*-*}; done )" -- $cur ) ) + done } -_available_pkgs () -{ - #find balks easilly on a find /foo/*/* type dir, especially one like - # /var/lib/pacman/*/* - # This little change-up removes the find *and* only uses enabled repos - local available_pkgs - local enabled_repos - enabled_repos=$( /bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e 'options' -e '^#' | tr -d '[]' ) - available_pkgs=$( for r in $enabled_repos; do /bin/echo /var/lib/pacman/sync/$r/*; done ) - COMPREPLY=( $( compgen -W "$( for i in $available_pkgs; do j=${i##*/}; echo ${j%-*-*}; done )" -- $cur ) ) -} +# makepkg purge functions +_makepkg_deselect() { + local o -_installed_groups () -{ - local installed_groups - installed_groups=$( /bin/find /var/lib/pacman/local -name desc -exec /bin/sed -ne '/%GROUPS%/,/^$/{//d; p}' {} \; | /bin/sort -u ) - COMPREPLY=( $( compgen -W "$( for i in $installed_groups; do /bin/echo ${i%-*-*}; done )" -- $cur ) ) + if [[ $1 == l ]]; then + for o in "${!long[@]}"; do + if [[ ${COMP_WORDS[w]} == ${long[o]} ]]; then + unset 'long[o]'; break + fi + done + else + for o in "${!short[@]}"; do + if [[ ${COMP_WORDS[w]} == -*([^- ])${short[o]}* ]]; then + unset 'short[o]' + fi + done + fi } -_available_groups () -{ - #find balks easilly on a find /foo/*/* type dir, especially one like - # /var/lib/pacman/*/* - # This little change-up removes the find *and* only uses enabled repos - local available_groups - local enabled_repos - enabled_repos=$( /bin/grep '\[' /etc/pacman.conf | /bin/grep -v -e 'options' -e '^#' | tr -d '[]' ) - available_groups=$( for r in $enabled_repos; do /bin/sed '/%GROUPS%/,/^$/{//d; p}' /var/lib/pacman/sync/$r/*/desc | /bin/sort -u; done ) - COMPREPLY=( $( compgen -W "$( for i in $available_groups; do /bin/echo ${i%-*-*}; done )" -- $cur ) ) +_makepkg_count_words() { + local w + + for (( w=0; w<${#COMP_WORDS[@]}; w++)); do + [[ ${COMP_WORDS[w]} == @(-*([^- ])[hC]*|--@(help|cleancache)) ]] && return 0 + _makepkg_deselect "$1" + done } -## makepkg completion - -_makepkg () -{ - local cur prev +# makepkg main +_makepkg() { COMPREPLY=() - cur=${COMP_WORDS[COMP_CWORD]} - prev=${COMP_WORDS[COMP_CWORD-1]} - case "$prev" in - -p) - _filedir - return 0 - ;; - --help|--cleancache) - COMPREPLY='' - return 0 - ;; - esac - - if [[ "$cur" == -* ]]; then - COMPREPLY=( $( compgen -W '\ - -A --ignorearch \ - -b --builddeps \ - -c --clean \ - -C --cleancache \ - -d --nodeps \ - -e --noextract \ - -f --force \ - -g --geninteg \ - -h --help \ - -i --install \ - -L --log \ - -m --nocolor \ - -o --nobuild \ - -p \ - -r --rmdeps \ - -s --syncdeps \ - --asroot \ - --source \ - --noconfirm \ - --noprogressbar' -- $cur ) ) + local prev cur short long parse glob + prev=${COMP_WORDS[COMP_CWORD-1]} + cur=`_get_cword` + + short=( A C L R c d e f g h i m o p r s ) + + long=( --allsource --asroot --clean --cleancache --config --force + --geninteg --help --holdver --ignorearch --install --log + --nobuild --nocolor --noconfirm --nodeps --noextract + --noprogressbar --repackage --rmdeps --skipinteg --source + --syncdeps ) + + if (( COMP_CWORD == 1 )) && [[ $cur != -[^-]* ]]; then + # First word check + COMPREPLY=($(compgen -W "$(echo "${short[@]/#/-}" "${long[@]}")" -- "$cur")) + elif [[ $prev == @(--config|-*([^- ])p*) ]]; then + # Complete filenames for selected options + _arch_rem_selected + return 0 + else + # Generate the list depending on current word + case $cur in + -+($(IFS='|'; echo "${short[*]}"))) + _makepkg_count_words s + COMPREPLY=($(compgen -W "$(echo "${short[@]/#/$cur}")" -- "$cur")) + ;; + -*) + # If $cur isn't a combination option, fall back to displaying + # long options only. These are the prime target for completion + _makepkg_count_words l + COMPREPLY=($(compgen -W "$(echo "${long[@]}")" -- "$cur")) + ;; + esac fi - - rem_selected } -complete -o default -F _makepkg makepkg -## pacman completion - -_instring () -{ - str="${1}" - shift 1 - for c in "${@}"; do - if [ $(/bin/expr index "${str}" "${c}") -gt 0 ]; then - return 0 - fi - done - return 1 +# pacman packages +_pacman_pkg() { + COMPREPLY=($(compgen -W "$(if [[ $2 ]]; then + command pacman -"$1" |\ + command sed 's, .*,,' |\ + command sort -u + else + command pacman -"$1" + fi)" -- "$cur" )) } -_pacman () -{ - local a arg toparse op mod cur +# pacman main +_pacman() { COMPREPLY=() - # This argument parsing is done so we can check for flag existance later - # right now it's a tad crappy, but does the job - for (( i=1; i < ${#COMP_WORDS[@]}-1; i++ )); do - a=${COMP_WORDS[i]} - arg="${a:0:2}" - toparse="${a:2}" + local prev cur query remove sync upgrade core common list w m o s=() + prev=${COMP_WORDS[COMP_CWORD-1]} + cur=`_get_cword` - case "${arg}" in - -@(U|R|S|Q|h|V)) - op="${arg/-}" - mod="${mod}${a:2}" - ;; - --) - arg="${a:2}" - case "${arg}" in - remove) op="R" ;; - upgrade) op="U" ;; - query) op="Q" ;; - sync) op="S" ;; - help) op="h" ;; - version) op="V" ;; - verbose) mod="${mod}v" ;; - root) mod="${mod}r" ;; - dbpath) mod="${mod}b" ;; - nodeps) mod="${mod}d" ;; - force) mod="${mod}f" ;; - groups) mod="${mod}g" ;; - info) mod="${mod}i" ;; - list) mod="${mod}l" ;; - print-uris) mod="${mod}p" ;; - search) mod="${mod}s" ;; - sysupgrade) mod="${mod}u" ;; - upgrades) mod="${mod}u" ;; - downloadonly) mod="${mod}w" ;; - refresh) mod="${mod}y" ;; - changelog) mod="${mod}c" ;; - deps) mod="${mod}d" ;; - explicit) mod="${mod}e" ;; - unrequired) mod="${mod}t" ;; - foreign) mod="${mod}m" ;; - owns) mod="${mod}o" ;; - file) mod="${mod}p" ;; - search) mod="${mod}s" ;; - upgrades) mod="${mod}u" ;; - cascade) mod="${mod}c" ;; - check) mod="${mod}k" ;; - dbonly) mod="${mod}k" ;; - nosave) mod="${mod}n" ;; - recursive) mod="${mod}s" ;; - unneeded) mod="${mod}u" ;; - esac ;; - *) toparse="${a}" ;; - esac + # These are split in a subshell with IFS=' '. No point in listing + # them by \n since compgen -W can't handle spaces or new lines in + # operands + query=( '--changelog --check --deps --explicit --file --foreign --groups --info + --list --owns --quiet --search --unrequired --upgrades' 'c e g i k l m o p q s t u' ) - arglen=$(( ${#toparse}-1 )) - for c in $(/bin/seq 0 "${arglen}"); do - arg=${toparse:$c:1} - [ "${arg}" != "-" ] && mod="${mod}${arg}" - done - done + remove=( '--cascade --dbonly --nodeps --nosave --recursive --unneeded' 'c k n s u' ) - cur=${COMP_WORDS[COMP_CWORD]} + sync=( '--asdeps --asexplicit --clean --downloadonly --force --groups --ignore --ignoregroup + --info --list --needed --nodeps --print-uris --quiet --refresh --search --sysupgrade' + 'c f g i l p q s u w y' ) - if [ $COMP_CWORD -eq 1 ] && [[ "$cur" == -* ]]; then - COMPREPLY=( $( compgen -W '\ - -h --help \ - -Q --query \ - -R --remove \ - -S --sync \ - -U --upgrade \ - -V --version \ - ' -- $cur ) ) - rem_selected - return 0 - fi + upgrade=( '--asdeps --asexplicit --force --nodeps' f ) - if [[ "$cur" == -* ]]; then - case "${op}" in - U) - COMPREPLY=( $( compgen -W '\ - --asdeps \ - --asexplicit \ - -d --nodeps \ - -f --force \ - -h --help \ - --config \ - --logfile \ - --noconfirm \ - --noprogressbar \ - --noscriptlet \ - -v --verbose \ - -r --root \ - -b --dbpath \ - --cachedir \ - ' -- $cur ) ) - return 0 - ;; - R) - COMPREPLY=( $( compgen -W '\ - -c --cascade \ - -d --nodeps \ - -h --help \ - -k --dbonly \ - -n --nosave \ - -s --recursive \ - -u --unneeded \ - --config \ - --logfile \ - --noconfirm \ - --noprogressbar \ - --noscriptlet \ - -v --verbose \ - -r --root \ - -b --dbpath \ - --cachedir \ - ' -- $cur ) ) - return 0 - ;; - S) - COMPREPLY=( $( compgen -W '\ - --asdeps \ - --asexplicit \ - -c --clean \ - -d --nodeps \ - -f --force \ - -g --groups \ - -h --help \ - -i --info \ - -l --list \ - -p --print-uris \ - -s --search \ - -u --sysupgrade \ - -w --downloadonly \ - -y --refresh \ - --needed \ - --ignore \ - --ignoregroup \ - --config \ - --logfile \ - --noconfirm \ - --noprogressbar \ - --noscriptlet \ - -v --verbose \ - -r --root \ - -b --dbpath \ - --cachedir \ - ' -- $cur ) ) - return 0 - ;; - Q) - COMPREPLY=( $( compgen -W '\ - -c --changelog \ - -d --deps \ - -e --explicit \ - -g --groups \ - -h --help \ - -i --info \ - -k --check \ - -l --list \ - -m --foreign \ - -o --owns \ - -p --file \ - -s --search \ - -t --unrequired \ - -u --upgrades \ - --config \ - --logfile \ - --noconfirm \ - --noprogressbar \ - --noscriptlet \ - -v --verbose \ - -r --root \ - -b --dbpath \ - --cachedir \ - ' -- $cur ) ) - return 0 - ;; - esac - rem_selected + common=( '--cachedir --config --dbpath --debug --help --logfile --noconfirm + --noprogressbar --noscriptlet --root --verbose' 'b d h r v' ) + + core=( --query --remove --sync --upgrade --version -Q -R -S -U -V ) + + if (( COMP_CWORD == 1 )) && [[ $cur != -[^-]* ]]; then + COMPREPLY=($(compgen -W "$(echo "${core[@]}") '--help' '-h'" -- "$cur")) + return 0 + elif [[ $prev == @(-*([^- ])[br]*|--@(config|logfile|root|dbpath|cachedir)) ]]; then + _arch_rem_selected + return 0 else - case "${op}" in - U) - COMPREPLY=( $( compgen -d -- "$cur" ) \ - $( compgen -f -X '!*.pkg.tar.gz' -- "$cur" ) ) + # Main option check + for m in 'Q query' 'R remove' 'S sync' 'U upgrade' 'null'; do + if [[ $m == null ]]; then + # Go back to core if null + COMPREPLY=($(compgen -W "$(echo "${core[@]}")" -- "$cur")) return 0 - ;; - h|V) - COMPREPLY='' - return 0 - ;; - Q) - if _instring $mod g; then - _installed_groups - elif _instring $mod o; then - COMPREPLY=( $( compgen -d -- "$cur" ) \ - $( compgen -f -- "$cur" ) ) - elif _instring $mod p; then - COMPREPLY=( $( compgen -d -- "$cur" ) \ - $( compgen -f -X '!*.pkg.tar.gz' -- "$cur" ) ) - elif _instring $mod u; then - COMPREPLY='' + fi + # Break on match + [[ $COMP_LINE == @(* -*([^- ])${m% *}*|* --${m#* } *) ]] && break + done + fi + + # See if $cur is an operand before calling _pacman_pkg + case $cur in + + # Check if it's a combination option first + -"${m% *}"*) + # Generate the list from the option arrays. eval is being using on + # possible matches between query, remove, sync, upgrade and common + # arrays; completely sanitized input + eval "list=($(command sed 's,.*,\${&[1]},' <<<"${m#* }"; IFS=' '; echo ${common[1]}))" + # Parse previous arguments + for (( w=0; w<${#COMP_WORDS[@]}; w++)); do + if [[ ${COMP_WORDS[w]} == -*([^-])[hV]* ]]; then return 0 - else - _installed_pkgs fi - return 0 + for o in "${!list[@]}"; do + if [[ ${COMP_WORDS[w]} == -*([^-])*${list[o]}* ]]; then + unset 'list[o]' + fi + done + done + # Allow special double letters + case ${m% *} in + Q) [[ $cur == *([^i])i*([^i]) ]] && s+=(i) ;; + R) [[ $cur == *([^s])s*([^s]) ]] && s+=(s) ;; + S) [[ $cur == *([^c])c*([^c]) ]] && s+=(c) + [[ $cur == *([^u])u*([^u]) ]] && s+=(u) + [[ $cur == *([^y])y*([^y]) ]] && s+=(y) ;; + esac + list=("${list[@]/#/$cur}" "${s[@]/#/$cur}") + ;; + + # Use long array instead + -*) + eval "list=($(command sed 's,.*,\${&[0]},' <<<"${m#* }"; IFS=' '; echo ${common[0]}))" + for (( w=0; w<${#COMP_WORDS[@]}-1; w++)); do + if [[ ${COMP_WORDS[w]} == --@(help|version) ]]; then + return 0 + fi + for o in "${!list[@]}"; do + if [[ ${COMP_WORDS[w]} == ${list[o]} ]]; then + unset 'list[o]'; break + fi + done + done + ;; + + # The word isn't an operand; use pacman or _filedir + *) + case ${m% *} in + # The weak extglobs are due to a previous match already + # confirming validity + Q) + case $COMP_LINE in + @(* -*([^- ])g*|* --groups *)) _pacman_pkg Qgq sort ;; + @(* -*([^- ])o*|* --owns *)) ;; + @(* -*([^- ])p*|* --file *)) _filedir 'pkg.tar.gz' ;; + @(* -*([^- ])u*|* --upgrades *)) _pacman_pkg Quq ;; + *) _pacman_pkg Qq ;; + esac ;; - R) - _installed_pkgs - return 0 + R) + _pacman_pkg Qq ;; - S) - if _instring $mod l; then - _available_repos - else - _available_pkgs - fi - return 0 + S) + case $COMP_LINE in + @(* -*([^- ])l*|* --list *)) _pacman_pkg Sl sort ;; + @(* -*([^- ])g*|* --groups *)) _pacman_pkg Sg ;; + *) _pacman_pkg Slq ;; + esac ;; - esac - fi - - rem_selected + U) + _filedir 'pkg.tar.gz' + ;; + esac + _arch_rem_selected + return 0 + ;; + esac + COMPREPLY=($(compgen -W "$(echo "${list[@]}")" -- "$cur")) } -complete -o filenames -F _pacman pacman + +complete -F _makepkg -o default -o filenames -o plusdirs makepkg +complete -F _pacman -o default -o filenames -o plusdirs pacman -- 1.6.6
On Tue, Jan 19, 2010 at 11:26 PM, Andres Perera <andres87p@gmail.com> wrote:
Fix: 3 Undeclared local vars with common enough names to warrant breakage
Performance issues with _pacman trying to replicate /usr/bin/pacman with find and other slow tools.
Performance issues with expanding an array (with sometimes hundreds of items) over three times.
Expanding said array to remove already completed entries had the side effect of braking filenames with spaces and or \n.
The full description of fixes are already posted at: http://bugs.archlinux.org/task/16630#comment55779 along with time diffs.
Signed-off-by: Andres P <andres87p@gmail.com> --- First off, thanks. These fixes sound much needed.
contrib/bash_completion | 535 ++++++++++++++++++----------------------------- 1 files changed, 202 insertions(+), 333 deletions(-)
diff --git a/contrib/bash_completion b/contrib/bash_completion index 62e5bc9..65051f5 100644 --- a/contrib/bash_completion +++ b/contrib/bash_completion @@ -1,365 +1,234 @@ -# vim: set ft=sh ts=2 sw=2 et: -# file: /etc/bash_completion.d/pacman - -# Bash completion for pacman -# Original: Manolis Tzanidakis <mtzanidakis@freemail.gr> +# pacman/makepkg completion by Andres Perera <andres87p gmail> # -# Distributed under the terms of the GNU General Public License, v2 or later. +# Distributed under the terms of the GNU General Public License v3 or +# later. Is this strictly necessary? You are changing one piece of code in the entire codebase to require v3 or later and as weird as it sounds, I am not going to let that fly. I won't be merging this unless it is v2 or you give some darn good reasons why we should move to v3.
For the rest of the patch, I think I am just going to test it out locally and then I'll get back to you if I don't see anything that blows up as you are right in it being a total revision of the original. -Dan
On Wed, Jan 20, 2010 at 1:07 AM, Dan McGee <dpmcgee@gmail.com> wrote:
Is this strictly necessary? You are changing one piece of code in the entire codebase to require v3 or later and as weird as it sounds, I am not going to let that fly. I won't be merging this unless it is v2 or you give some darn good reasons why we should move to v3.
Not really necessary. You can change it to v2, or I can do that if it requires a commit from me.
On Tue, Jan 19, 2010 at 11:48 PM, Andres Perera <andres87p@gmail.com> wrote:
On Wed, Jan 20, 2010 at 1:07 AM, Dan McGee <dpmcgee@gmail.com> wrote:
Is this strictly necessary? You are changing one piece of code in the entire codebase to require v3 or later and as weird as it sounds, I am not going to let that fly. I won't be merging this unless it is v2 or you give some darn good reasons why we should move to v3.
Not really necessary. You can change it to v2, or I can do that if it requires a commit from me.
Nah that's good enough for me. I'll try to give this whole thing a workout in the next day or two and see if I can come up with anything else that we might want to tweak, however. -Dan
Uh, I broke the spaces with git's 72 textwidth. Same patch attached...
On Wed, Jan 20, 2010 at 9:27 AM, Andres Perera <andres87p@gmail.com> wrote:
Uh, I broke the spaces with git's 72 textwidth.
Same patch attached...
I don't understand what you mean, this is the diff between original and new patch : diff --git a/contrib/bash_completion b/contrib/bash_completion index 2f6cd06..b1162ad 100644 --- a/contrib/bash_completion +++ b/contrib/bash_completion @@ -1,6 +1,6 @@ # pacman/makepkg completion by Andres Perera <andres87p gmail> # -# Distributed under the terms of the GNU General Public License v3 or +# Distributed under the terms of the GNU General Public License v2 or # later. # # Local variables: common core cur glob list long m o prev query r @@ -56,7 +56,7 @@ _makepkg_count_words() { _makepkg() { COMPREPLY=() - local prev cur short long parse glob + local prev cur short long prev=${COMP_WORDS[COMP_CWORD-1]} cur=`_get_cword` I see you reverted the license and made another change, but no space change. I am not sure what you mean with git's 72 textwidth , isnt that just commit log ? Your original commit log was fine. Also it's much easier for us if you keep submitting git patches with the log. And finally the patch now only applies against maint, not master, but maybe that's our fault of not applying it earlier :)
On 06/08/10 at 09:54:16 CEST, Xavier Chantry <shiningxc at gmail.com> wrote:
On Wed, Jan 20, 2010 at 9:27 AM, Andres Perera <andres87p@gmail.com> wrote:
Uh, I broke the spaces with git's 72 textwidth.
Same patch attached...
I don't understand what you mean, this is the diff between original and new patch :
diff --git a/contrib/bash_completion b/contrib/bash_completion index 2f6cd06..b1162ad 100644 --- a/contrib/bash_completion +++ b/contrib/bash_completion @@ -1,6 +1,6 @@ # pacman/makepkg completion by Andres Perera <andres87p gmail> # -# Distributed under the terms of the GNU General Public License v3 or +# Distributed under the terms of the GNU General Public License v2 or # later. # # Local variables: common core cur glob list long m o prev query r @@ -56,7 +56,7 @@ _makepkg_count_words() { _makepkg() { COMPREPLY=()
- local prev cur short long parse glob + local prev cur short long prev=${COMP_WORDS[COMP_CWORD-1]} cur=`_get_cword`
I see you reverted the license and made another change, but no space change. I am not sure what you mean with git's 72 textwidth , isnt that just commit log ? Your original commit log was fine. Also it's much easier for us if you keep submitting git patches with
Oh, I was under the impression I broke spaces. Maybe it was the gmail client playing tricks on me the log.
And finally the patch now only applies against maint, not master, but maybe that's our fault of not applying it earlier :)
To be honest I'm not really satisfied with that patch anymore, since it could be made even more simple and faster. I also had put an option that made dirs show twice (+plusdirs), ie a new bug. I'll try and see if I can get something clean against master, Andres
participants (4)
-
Andres P
-
Andres Perera
-
Dan McGee
-
Xavier Chantry