Re: [RFC/PATCH 2/1] bash: eliminate dependency on bash_completion lib

5 messages, 2 authors, 2016-06-15 · open the first message on its own page

Re: [RFC/PATCH 2/1] bash: eliminate dependency on bash_completion lib

From: Junio C Hamano <hidden>
Date: 2016-06-15 22:50:12

Jonathan Nieder [off-list ref] writes:
SZEDER Gábor wrote:
quoted
On Thu, Dec 02, 2010 at 03:02:07PM -0600, Jonathan Nieder wrote:
quoted
quoted
Add a minimal implementation of _get_comp_words_by_ref,
the routine used to work around bash 4.0's COMP_WORDS semantics.

Based on bash-completion 2.x (commit bf763033, 2010-10-26) but
tweaked for simplicity and to allow zsh to at least parse the
code.
What is this patch based on?
Ah, sorry, applies to d93f4a297 (bash: work around bash 4.0 change in
COMP_WORDS semantics, 2010-12-02).
Sorry, but whose repository does that object live in?

[PATCH v5.1 0/3] Make git-completion Bash 4 compatible

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:50:15

Junio C Hamano wrote:
quoted
quoted
On Thu, Dec 02, 2010 at 03:02:07PM -0600, Jonathan Nieder wrote:
quoted
quoted
quoted
Add a minimal implementation of _get_comp_words_by_ref,
the routine used to work around bash 4.0's COMP_WORDS semantics.

Based on bash-completion 2.x (commit bf763033, 2010-10-26) but
tweaked for simplicity and to allow zsh to at least parse the
code.
[...]
Sorry, but whose repository does that object live in?
Agh, sorry for the mess.  Please fetch

  git://repo.or.cz/git/jrn.git pd/bash-4-completion 

to receive the following history:

  o [maint-1.7.2] --- A --- B --- M [FETCH_HEAD]
                                 /
                                o [master (early part)]

[A] bash: get --pretty=m<tab> completion to work with bash v4
(by Peter)

[B] bash: simple reimplementation of _get_comp_words_by_ref
(discussed above)

[M] Merge branch 'master' (early part) into pd/bash-4-completion
(zsh compatibility)

[master (early part)] completion: fix zsh check under bash with 'set -u'
(2010-10-28 11:45:00 -0700).

A and B are the patches sent previously in this thread, rebased on an
older code base to avoid the zsh support.  A combined diff for the
merge 'M' will follow.  Thoughts, suggestions, acks, test reports
would be welcome.

Thanks for your patience.
Jonathan

[PATCH 1/3] bash: get --pretty=m<tab> completion to work with bash v4

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:50:15

From: Peter van der Does <redacted>

Bash's programmable completion provides the COMP_WORDS array variable,
which holds the individual words in the current command line.  In bash
versions prior to v4 "words are split on shell metacharacters as the
shell parser would separate them" (quote from bash v3.2.48's man
page).  This behavior has changed with bash v4, and the command line
"is split into words as readline would split it, using COMP_WORDBREAKS
as" "the set of characters that the readline library treats as word
separators" (quote from bash v4's man page).

Since COMP_WORDBREAKS contains the characters : and = by default, this
behavior change in bash affects git's completion script.  For example,
before bash 4, running

	$ git log --pretty=m <tab><tab>

would give a list of pretty-printing formats starting with 'm' but now
it completes on branch names.

It would be possible to work around this by removing '=' and ':' from
COMP_WORDBREAKS, but as noticed in v1.5.6.4~9^2 (bash completion:
Resolve git show ref:path<tab> losing ref: portion, 2008-07-15), that
would break *other* completion scripts.  The bash-completion library
includes a better workaround: the _get_comp_words_by_ref function
re-assembles a copy of COMP_WORDS, excluding a collection of word
separators of the caller's choice.  Use it.

As a bonus, this improves behavior when tab is pressed with the cursor
in the middle of a word if the bash-completion lib is loaded.

To avoid breaking setups with the bash-completion library not already
loaded, if the _get_comp_words_by_ref function is not defined then use
a shim that just reads COMP_WORDS instead (no change from the current
behavior in that case).

Signed-off-by: Peter van der Does <redacted>
Signed-off-by: Jonathan Nieder <redacted>
Explained-by: SZEDER Gábor [off-list ref]
---
 contrib/completion/git-completion.bash |  236 +++++++++++++++++++++----------
 1 files changed, 160 insertions(+), 76 deletions(-)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 82e6609..68b68d0 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -321,11 +321,39 @@ __gitcomp_1 ()
 	done
 }
 
+if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
+_get_comp_words_by_ref ()
+{
+	while [ $# -gt 0 ]; do
+		case "$1" in
+		cur)
+			cur=${COMP_WORDS[COMP_CWORD]}
+			;;
+		prev)
+			prev=${COMP_WORDS[COMP_CWORD-1]}
+			;;
+		words)
+			words=("${COMP_WORDS[@]}")
+			;;
+		cword)
+			cword=$COMP_CWORD
+			;;
+		-n)
+			# assume COMP_WORDBREAKS is already set sanely
+			shift
+			;;
+		esac
+		shift
+	done
+}
+fi
+
 # __gitcomp accepts 1, 2, 3, or 4 arguments
 # generates completion reply with compgen
 __gitcomp ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	if [ $# -gt 2 ]; then
 		cur="$3"
 	fi
@@ -384,7 +412,8 @@ __git_tags ()
 __git_refs ()
 {
 	local i is_hash=y dir="$(__gitdir "${1-}")"
-	local cur="${COMP_WORDS[COMP_CWORD]}" format refs
+	local cur format refs
+	_get_comp_words_by_ref -n =: cur
 	if [ -d "$dir" ]; then
 		case "$cur" in
 		refs|refs/*)
@@ -482,7 +511,8 @@ __git_compute_merge_strategies ()
 
 __git_complete_file ()
 {
-	local pfx ls ref cur="${COMP_WORDS[COMP_CWORD]}"
+	local pfx ls ref cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	?*:*)
 		ref="${cur%%:*}"
@@ -530,7 +560,8 @@ __git_complete_file ()
 
 __git_complete_revlist ()
 {
-	local pfx cur="${COMP_WORDS[COMP_CWORD]}"
+	local pfx cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	*...*)
 		pfx="${cur%...*}..."
@@ -550,11 +581,12 @@ __git_complete_revlist ()
 
 __git_complete_remote_or_refspec ()
 {
-	local cmd="${COMP_WORDS[1]}"
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur words cword
+	_get_comp_words_by_ref -n =: cur words cword
+	local cmd="${words[1]}"
 	local i c=2 remote="" pfx="" lhs=1 no_complete_refspec=0
-	while [ $c -lt $COMP_CWORD ]; do
-		i="${COMP_WORDS[c]}"
+	while [ $c -lt $cword ]; do
+		i="${words[c]}"
 		case "$i" in
 		--mirror) [ "$cmd" = "push" ] && no_complete_refspec=1 ;;
 		--all)
@@ -622,13 +654,14 @@ __git_complete_remote_or_refspec ()
 
 __git_complete_strategy ()
 {
+	local cur prev
+	_get_comp_words_by_ref -n =: cur prev
 	__git_compute_merge_strategies
-	case "${COMP_WORDS[COMP_CWORD-1]}" in
+	case "$prev" in
 	-s|--strategy)
 		__gitcomp "$__git_merge_strategies"
 		return 0
 	esac
-	local cur="${COMP_WORDS[COMP_CWORD]}"
 	case "$cur" in
 	--strategy=*)
 		__gitcomp "$__git_merge_strategies" "" "${cur##--strategy=}"
@@ -788,10 +821,10 @@ __git_aliased_command ()
 # __git_find_on_cmdline requires 1 argument
 __git_find_on_cmdline ()
 {
-	local word subcommand c=1
-
-	while [ $c -lt $COMP_CWORD ]; do
-		word="${COMP_WORDS[c]}"
+	local word subcommand c=1 words cword
+	_get_comp_words_by_ref -n =: words cword
+	while [ $c -lt $cword ]; do
+		word="${words[c]}"
 		for subcommand in $1; do
 			if [ "$subcommand" = "$word" ]; then
 				echo "$subcommand"
@@ -804,9 +837,10 @@ __git_find_on_cmdline ()
 
 __git_has_doubledash ()
 {
-	local c=1
-	while [ $c -lt $COMP_CWORD ]; do
-		if [ "--" = "${COMP_WORDS[c]}" ]; then
+	local c=1 words cword
+	_get_comp_words_by_ref -n =: words cword
+	while [ $c -lt $cword ]; do
+		if [ "--" = "${words[c]}" ]; then
 			return 0
 		fi
 		c=$((++c))
@@ -818,7 +852,8 @@ __git_whitespacelist="nowarn warn error error-all fix"
 
 _git_am ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+	local cur dir="$(__gitdir)"
+	_get_comp_words_by_ref -n =: cur
 	if [ -d "$dir"/rebase-apply ]; then
 		__gitcomp "--skip --continue --resolved --abort"
 		return
@@ -842,7 +877,8 @@ _git_am ()
 
 _git_apply ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--whitespace=*)
 		__gitcomp "$__git_whitespacelist" "" "${cur##--whitespace=}"
@@ -865,7 +901,8 @@ _git_add ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "
@@ -879,7 +916,8 @@ _git_add ()
 
 _git_archive ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--format=*)
 		__gitcomp "$(git archive --list)" "" "${cur##--format=}"
@@ -923,10 +961,11 @@ _git_bisect ()
 
 _git_branch ()
 {
-	local i c=1 only_local_ref="n" has_r="n"
+	local i c=1 only_local_ref="n" has_r="n" cur words cword
 
-	while [ $c -lt $COMP_CWORD ]; do
-		i="${COMP_WORDS[c]}"
+	_get_comp_words_by_ref -n =: cur words cword
+	while [ $c -lt $cword ]; do
+		i="${words[c]}"
 		case "$i" in
 		-d|-m)	only_local_ref="y" ;;
 		-r)	has_r="y" ;;
@@ -934,7 +973,7 @@ _git_branch ()
 		c=$((++c))
 	done
 
-	case "${COMP_WORDS[COMP_CWORD]}" in
+	case "$cur" in
 	--*)
 		__gitcomp "
 			--color --no-color --verbose --abbrev= --no-abbrev
@@ -954,8 +993,10 @@ _git_branch ()
 
 _git_bundle ()
 {
-	local cmd="${COMP_WORDS[2]}"
-	case "$COMP_CWORD" in
+	local words cword
+	_get_comp_words_by_ref -n =: words cword
+	local cmd="${words[2]}"
+	case "$cword" in
 	2)
 		__gitcomp "create list-heads verify unbundle"
 		;;
@@ -976,7 +1017,8 @@ _git_checkout ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--conflict=*)
 		__gitcomp "diff3 merge" "" "${cur##--conflict=}"
@@ -1000,7 +1042,8 @@ _git_cherry ()
 
 _git_cherry_pick ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--edit --no-commit"
@@ -1015,7 +1058,8 @@ _git_clean ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--dry-run --quiet"
@@ -1027,7 +1071,8 @@ _git_clean ()
 
 _git_clone ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "
@@ -1054,7 +1099,8 @@ _git_commit ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--cleanup=*)
 		__gitcomp "default strip verbatim whitespace
@@ -1089,7 +1135,8 @@ _git_commit ()
 
 _git_describe ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "
@@ -1121,7 +1168,8 @@ _git_diff ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--cached --staged --pickaxe-all --pickaxe-regex
@@ -1142,7 +1190,8 @@ _git_difftool ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--tool=*)
 		__gitcomp "$__git_mergetools_common kompare" "" "${cur##--tool=}"
@@ -1167,7 +1216,8 @@ __git_fetch_options="
 
 _git_fetch ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "$__git_fetch_options"
@@ -1179,7 +1229,8 @@ _git_fetch ()
 
 _git_format_patch ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--thread=*)
 		__gitcomp "
@@ -1211,7 +1262,8 @@ _git_format_patch ()
 
 _git_fsck ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "
@@ -1226,7 +1278,8 @@ _git_fsck ()
 
 _git_gc ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--prune --aggressive"
@@ -1245,7 +1298,8 @@ _git_grep ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "
@@ -1268,7 +1322,8 @@ _git_grep ()
 
 _git_help ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--all --info --man --web"
@@ -1286,7 +1341,8 @@ _git_help ()
 
 _git_init ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--shared=*)
 		__gitcomp "
@@ -1306,7 +1362,8 @@ _git_ls_files ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--cached --deleted --modified --others --ignored
@@ -1360,12 +1417,13 @@ _git_log ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
 	local g="$(git rev-parse --git-dir 2>/dev/null)"
 	local merge=""
 	if [ -f "$g/MERGE_HEAD" ]; then
 		merge="--merge"
 	fi
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--pretty=*)
 		__gitcomp "$__git_log_pretty_formats
@@ -1419,7 +1477,8 @@ _git_merge ()
 {
 	__git_complete_strategy && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "$__git_merge_options"
@@ -1430,7 +1489,8 @@ _git_merge ()
 
 _git_mergetool ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--tool=*)
 		__gitcomp "$__git_mergetools_common tortoisemerge" "" "${cur##--tool=}"
@@ -1451,7 +1511,8 @@ _git_merge_base ()
 
 _git_mv ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--dry-run"
@@ -1469,12 +1530,14 @@ _git_name_rev ()
 _git_notes ()
 {
 	local subcommands="edit show"
+	local words cword
+	_get_comp_words_by_ref -n =: words cword
 	if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
 		__gitcomp "$subcommands"
 		return
 	fi
 
-	case "${COMP_WORDS[COMP_CWORD-1]}" in
+	case "${words[cword-1]}" in
 	-m|-F)
 		COMPREPLY=()
 		;;
@@ -1488,7 +1551,8 @@ _git_pull ()
 {
 	__git_complete_strategy && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "
@@ -1504,8 +1568,9 @@ _git_pull ()
 
 _git_push ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
-	case "${COMP_WORDS[COMP_CWORD-1]}" in
+	local cur prev
+	_get_comp_words_by_ref -n =: cur prev
+	case "$prev" in
 	--repo)
 		__gitcomp "$(__git_remotes)"
 		return
@@ -1528,7 +1593,9 @@ _git_push ()
 
 _git_rebase ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}" dir="$(__gitdir)"
+	local dir="$(__gitdir)"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	if [ -d "$dir"/rebase-apply ] || [ -d "$dir"/rebase-merge ]; then
 		__gitcomp "--continue --skip --abort"
 		return
@@ -1558,7 +1625,8 @@ __git_send_email_suppresscc_options="author self cc bodycc sob cccmd body all"
 
 _git_send_email ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--confirm=*)
 		__gitcomp "
@@ -1600,9 +1668,11 @@ _git_stage ()
 
 __git_config_get_set_variables ()
 {
-	local prevword word config_file= c=$COMP_CWORD
+	local words cword
+	_get_comp_words_by_ref -n =: words cword
+	local prevword word config_file= c=$cword
 	while [ $c -gt 1 ]; do
-		word="${COMP_WORDS[c]}"
+		word="${words[c]}"
 		case "$word" in
 		--global|--system|--file=*)
 			config_file="$word"
@@ -1630,9 +1700,9 @@ __git_config_get_set_variables ()
 
 _git_config ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
-	local prv="${COMP_WORDS[COMP_CWORD-1]}"
-	case "$prv" in
+	local cur prev
+	_get_comp_words_by_ref -n =: cur prev
+	case "$prev" in
 	branch.*.remote)
 		__gitcomp "$(__git_remotes)"
 		return
@@ -1642,13 +1712,13 @@ _git_config ()
 		return
 		;;
 	remote.*.fetch)
-		local remote="${prv#remote.}"
+		local remote="${prev#remote.}"
 		remote="${remote%.fetch}"
 		__gitcomp "$(__git_refs_remotes "$remote")"
 		return
 		;;
 	remote.*.push)
-		local remote="${prv#remote.}"
+		local remote="${prev#remote.}"
 		remote="${remote%.push}"
 		__gitcomp "$(git --git-dir="$(__gitdir)" \
 			for-each-ref --format='%(refname):%(refname)' \
@@ -2039,7 +2109,8 @@ _git_reset ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--merge --mixed --hard --soft --patch"
@@ -2051,7 +2122,8 @@ _git_reset ()
 
 _git_revert ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--edit --mainline --no-edit --no-commit --signoff"
@@ -2065,7 +2137,8 @@ _git_rm ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "--cached --dry-run --ignore-unmatch --quiet"
@@ -2079,7 +2152,8 @@ _git_shortlog ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "
@@ -2097,7 +2171,8 @@ _git_show ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--pretty=*)
 		__gitcomp "$__git_log_pretty_formats
@@ -2121,7 +2196,8 @@ _git_show ()
 
 _git_show_branch ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "
@@ -2138,7 +2214,8 @@ _git_show_branch ()
 
 _git_stash ()
 {
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
+	_get_comp_words_by_ref -n =: cur
 	local save_opts='--keep-index --no-keep-index --quiet --patch'
 	local subcommands='save list show apply clear drop pop create branch'
 	local subcommand="$(__git_find_on_cmdline "$subcommands")"
@@ -2183,7 +2260,8 @@ _git_submodule ()
 
 	local subcommands="add status init update summary foreach sync"
 	if [ -z "$(__git_find_on_cmdline "$subcommands")" ]; then
-		local cur="${COMP_WORDS[COMP_CWORD]}"
+		local cur
+		_get_comp_words_by_ref -n =: cur
 		case "$cur" in
 		--*)
 			__gitcomp "--quiet --cached"
@@ -2227,7 +2305,8 @@ _git_svn ()
 			--edit --rmdir --find-copies-harder --copy-similarity=
 			"
 
-		local cur="${COMP_WORDS[COMP_CWORD]}"
+		local cur
+		_get_comp_words_by_ref -n =: cur
 		case "$subcommand,$cur" in
 		fetch,--*)
 			__gitcomp "--revision= --fetch-all $fc_opts"
@@ -2299,8 +2378,10 @@ _git_svn ()
 _git_tag ()
 {
 	local i c=1 f=0
-	while [ $c -lt $COMP_CWORD ]; do
-		i="${COMP_WORDS[c]}"
+	local words cword prev
+	_get_comp_words_by_ref -n =: words cword prev
+	while [ $c -lt $cword ]; do
+		i="${words[c]}"
 		case "$i" in
 		-d|-v)
 			__gitcomp "$(__git_tags)"
@@ -2313,7 +2394,7 @@ _git_tag ()
 		c=$((++c))
 	done
 
-	case "${COMP_WORDS[COMP_CWORD-1]}" in
+	case "$prev" in
 	-m|-F)
 		COMPREPLY=()
 		;;
@@ -2339,8 +2420,10 @@ _git ()
 {
 	local i c=1 command __git_dir
 
-	while [ $c -lt $COMP_CWORD ]; do
-		i="${COMP_WORDS[c]}"
+	local cur words cword
+	_get_comp_words_by_ref -n =: cur words cword
+	while [ $c -lt $cword ]; do
+		i="${words[c]}"
 		case "$i" in
 		--git-dir=*) __git_dir="${i#--git-dir=}" ;;
 		--bare)      __git_dir="." ;;
@@ -2352,7 +2435,7 @@ _git ()
 	done
 
 	if [ -z "$command" ]; then
-		case "${COMP_WORDS[COMP_CWORD]}" in
+		case "$cur" in
 		--*)   __gitcomp "
 			--paginate
 			--no-pager
@@ -2385,12 +2468,13 @@ _gitk ()
 {
 	__git_has_doubledash && return
 
-	local cur="${COMP_WORDS[COMP_CWORD]}"
+	local cur
 	local g="$(__gitdir)"
 	local merge=""
 	if [ -f "$g/MERGE_HEAD" ]; then
 		merge="--merge"
 	fi
+	_get_comp_words_by_ref -n =: cur
 	case "$cur" in
 	--*)
 		__gitcomp "
-- 
1.7.3.3.573.g0bcfc

[PATCH 2/3] bash: simple reimplementation of _get_comp_words_by_ref

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:50:15

Add a minimal implementation of _get_comp_words_by_ref so

	$ git show head:g <tab><tab>

on bash 4 can complete paths within the head commit without requiring
the bash_completion functions to be loaded.  This is a follow-up to
the previous patch (bash: get --pretty=m<tab> completion to work with
bash v4).

Based on bash-completion 2.x (commit bf763033, 2010-10-26) but tweaked
for simplicity and to allow zsh to parse the code.

Signed-off-by: Jonathan Nieder <redacted>
Improved-by: SZEDER Gábor [off-list ref]
---
 contrib/completion/git-completion.bash |  118 +++++++++++++++++++++++++++++--
 1 files changed, 110 insertions(+), 8 deletions(-)
diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash
index 68b68d0..1747091 100755
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@ -321,26 +321,128 @@ __gitcomp_1 ()
 	done
 }
 
+# The following function is based on code from:
+#
+#   bash_completion - programmable completion functions for bash 3.2+
+#
+#   Copyright © 2006-2008, Ian Macdonald <ian@caliban.org>
+#             © 2009-2010, Bash Completion Maintainers
+#                     <bash-completion-devel@lists.alioth.debian.org>
+#
+#   This program is free software; you can redistribute it and/or modify
+#   it under the terms of the GNU General Public License as published by
+#   the Free Software Foundation; either version 2, or (at your option)
+#   any later version.
+#
+#   This program is distributed in the hope that it will be useful,
+#   but WITHOUT ANY WARRANTY; without even the implied warranty of
+#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#   GNU General Public License for more details.
+#
+#   You should have received a copy of the GNU General Public License
+#   along with this program; if not, write to the Free Software Foundation,
+#   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+#
+#   The latest version of this software can be obtained here:
+#
+#   http://bash-completion.alioth.debian.org/
+#
+#   RELEASE: 2.x
+
+# This function can be used to access a tokenized list of words
+# on the command line:
+#
+#	__git_reassemble_comp_words_by_ref '=:'
+#	if test "${words_[cword_-1]}" = -w
+#	then
+#		...
+#	fi
+#
+# The argument should be a collection of characters from the list of
+# word completion separators (COMP_WORDBREAKS) to treat as ordinary
+# characters.
+#
+# This is roughly equivalent to going back in time and setting
+# COMP_WORDBREAKS to exclude those characters.  The intent is to
+# make option types like --date=<type> and <rev>:<path> easy to
+# recognize by treating each shell word as a single token.
+#
+# It is best not to set COMP_WORDBREAKS directly because the value is
+# shared with other completion scripts.  By the time the completion
+# function gets called, COMP_WORDS has already been populated so local
+# changes to COMP_WORDBREAKS have no effect.
+#
+# Output: words_, cword_, cur_.
+
+__git_reassemble_comp_words_by_ref()
+{
+	local exclude i j first
+	# Which word separators to exclude?
+	exclude="${1//[^$COMP_WORDBREAKS]}"
+	cword_=$COMP_CWORD
+	if [ -z "$exclude" ]; then
+		words_=("${COMP_WORDS[@]}")
+		return
+	fi
+	# List of word completion separators has shrunk;
+	# re-assemble words to complete.
+	for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
+		# Append each nonempty word consisting of just
+		# word separator characters to the current word.
+		first=t
+		while
+			[ $i -gt 0 ] &&
+			[ -n "${COMP_WORDS[$i]}" ] &&
+			# word consists of excluded word separators
+			[ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
+		do
+			# Attach to the previous token,
+			# unless the previous token is the command name.
+			if [ $j -ge 2 ] && [ -n "$first" ]; then
+				((j--))
+			fi
+			first=
+			words_[$j]=${words_[j]}${COMP_WORDS[i]}
+			if [ $i = $COMP_CWORD ]; then
+				cword_=$j
+			fi
+			if (($i < ${#COMP_WORDS[@]} - 1)); then
+				((i++))
+			else
+				# Done.
+				return
+			fi
+		done
+		words_[$j]=${words_[j]}${COMP_WORDS[i]}
+		if [ $i = $COMP_CWORD ]; then
+			cword_=$j
+		fi
+	done
+}
+
 if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
 _get_comp_words_by_ref ()
 {
+	local exclude cur_ words_ cword_
+	if [ "$1" = "-n" ]; then
+		exclude=$2
+		shift 2
+	fi
+	__git_reassemble_comp_words_by_ref "$exclude"
+	cur_=${words_[cword_]}
 	while [ $# -gt 0 ]; do
 		case "$1" in
 		cur)
-			cur=${COMP_WORDS[COMP_CWORD]}
+			cur=$cur_
 			;;
 		prev)
-			prev=${COMP_WORDS[COMP_CWORD-1]}
+			prev=${words_[$cword_-1]}
 			;;
 		words)
-			words=("${COMP_WORDS[@]}")
+			words=("${words_[@]}")
 			;;
 		cword)
-			cword=$COMP_CWORD
-			;;
-		-n)
-			# assume COMP_WORDBREAKS is already set sanely
-			shift
+			cword=$cword_
 			;;
 		esac
 		shift
-- 
1.7.3.3.573.g0bcfc

[MERGE PATCH 3/3] Merge branch 'master' (early part) into pd/bash-4-completion

From: Jonathan Nieder <hidden>
Date: 2016-06-15 22:50:15

* 'master' (early part): (529 commits)
  completion: fix zsh check under bash with 'set -u'
  Fix copy-pasted comments related to tree diff handling.
  Git 1.7.3.2
  {cvs,svn}import: use the new 'git read-tree --empty'
  t/t9001-send-email.sh: fix stderr redirection in 'Invalid In-Reply-To'
  Clarify and extend the "git diff" format documentation
  git-show-ref.txt: clarify the pattern matching
  documentation: git-config minor cleanups
  Update test script annotate-tests.sh to handle missing/extra authors
  ...

Conflicts:
	GIT-VERSION-GEN
	RelNotes
	contrib/completion/git-completion.bash
---
Suggestions for further work:

 - check edge cases:

	git log --pretty m<tab><tab>		should complete formats
	git log --pretty =<tab><tab>		should complain
	git log --pretty= m<tab><tab>		should complete commits

 - use a custom function to avoid repeating

	_get_comp_words_by_ref -n =:

   As an application, consider teaching git to complete

	git show HEAD@{<tab><tab>		completes to numbers and "upstream"

   Would the argument to -n need an @ for that?

 - get the zsh completion to actually work. :)  Even without this
   series, it seems it is willing to complete subcommand names and
   switches for git but nothing more.

 - adopt the rest of bash_completion's _get_comp_words_by_ref logic,
   so

	git log --pretty=m<cursor>master <tab><tab>

   completes formats starting with 'm', not formats starting with 'mmaster'.

If something turned out buggy, I'd be glad to fix it, but aside from
that I probably will not be working much more on this topic.  (Perhaps
an actual tab completion user would have a better sense of which
aspects are worth working on.)  Please feel free to pick it up and run
in whatever direction you please.

Good night,
Jonathan

diff --cc contrib/completion/git-completion.bash
index 1747091,168669b..d117055
--- a/contrib/completion/git-completion.bash
+++ b/contrib/completion/git-completion.bash
@@@ -321,135 -327,6 +327,162 @@@ __gitcomp_1 (
  	done
  }
  
 +# The following function is based on code from:
 +#
 +#   bash_completion - programmable completion functions for bash 3.2+
 +#
 +#   Copyright © 2006-2008, Ian Macdonald [off-list ref]
 +#             © 2009-2010, Bash Completion Maintainers
 +#                     [off-list ref]
 +#
 +#   This program is free software; you can redistribute it and/or modify
 +#   it under the terms of the GNU General Public License as published by
 +#   the Free Software Foundation; either version 2, or (at your option)
 +#   any later version.
 +#
 +#   This program is distributed in the hope that it will be useful,
 +#   but WITHOUT ANY WARRANTY; without even the implied warranty of
 +#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 +#   GNU General Public License for more details.
 +#
 +#   You should have received a copy of the GNU General Public License
 +#   along with this program; if not, write to the Free Software Foundation,
 +#   Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 +#
 +#   The latest version of this software can be obtained here:
 +#
 +#   http://bash-completion.alioth.debian.org/
 +#
 +#   RELEASE: 2.x
 +
 +# This function can be used to access a tokenized list of words
 +# on the command line:
 +#
 +#	__git_reassemble_comp_words_by_ref '=:'
 +#	if test "${words_[cword_-1]}" = -w
 +#	then
 +#		...
 +#	fi
 +#
 +# The argument should be a collection of characters from the list of
 +# word completion separators (COMP_WORDBREAKS) to treat as ordinary
 +# characters.
 +#
 +# This is roughly equivalent to going back in time and setting
 +# COMP_WORDBREAKS to exclude those characters.  The intent is to
 +# make option types like --date=<type> and <rev>:<path> easy to
 +# recognize by treating each shell word as a single token.
 +#
 +# It is best not to set COMP_WORDBREAKS directly because the value is
 +# shared with other completion scripts.  By the time the completion
 +# function gets called, COMP_WORDS has already been populated so local
 +# changes to COMP_WORDBREAKS have no effect.
 +#
 +# Output: words_, cword_, cur_.
 +
 +__git_reassemble_comp_words_by_ref()
 +{
 +	local exclude i j first
 +	# Which word separators to exclude?
 +	exclude="${1//[^$COMP_WORDBREAKS]}"
 +	cword_=$COMP_CWORD
 +	if [ -z "$exclude" ]; then
 +		words_=("${COMP_WORDS[@]}")
 +		return
 +	fi
 +	# List of word completion separators has shrunk;
 +	# re-assemble words to complete.
 +	for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
 +		# Append each nonempty word consisting of just
 +		# word separator characters to the current word.
 +		first=t
 +		while
 +			[ $i -gt 0 ] &&
 +			[ -n "${COMP_WORDS[$i]}" ] &&
 +			# word consists of excluded word separators
 +			[ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
 +		do
 +			# Attach to the previous token,
 +			# unless the previous token is the command name.
 +			if [ $j -ge 2 ] && [ -n "$first" ]; then
 +				((j--))
 +			fi
 +			first=
 +			words_[$j]=${words_[j]}${COMP_WORDS[i]}
 +			if [ $i = $COMP_CWORD ]; then
 +				cword_=$j
 +			fi
 +			if (($i < ${#COMP_WORDS[@]} - 1)); then
 +				((i++))
 +			else
 +				# Done.
 +				return
 +			fi
 +		done
 +		words_[$j]=${words_[j]}${COMP_WORDS[i]}
 +		if [ $i = $COMP_CWORD ]; then
 +			cword_=$j
 +		fi
 +	done
 +}
 +
 +if ! type _get_comp_words_by_ref >/dev/null 2>&1; then
++if [[ -z ${ZSH_VERSION:+set} ]]; then
 +_get_comp_words_by_ref ()
 +{
 +	local exclude cur_ words_ cword_
 +	if [ "$1" = "-n" ]; then
 +		exclude=$2
 +		shift 2
 +	fi
 +	__git_reassemble_comp_words_by_ref "$exclude"
 +	cur_=${words_[cword_]}
 +	while [ $# -gt 0 ]; do
 +		case "$1" in
 +		cur)
 +			cur=$cur_
 +			;;
 +		prev)
 +			prev=${words_[$cword_-1]}
 +			;;
 +		words)
 +			words=("${words_[@]}")
 +			;;
 +		cword)
 +			cword=$cword_
 +			;;
 +		esac
 +		shift
 +	done
 +}
++else
++_get_comp_words_by_ref ()
++{
++	while [ $# -gt 0 ]; do
++		case "$1" in
++		cur)
++			cur=${COMP_WORDS[COMP_CWORD]}
++			;;
++		prev)
++			prev=${COMP_WORDS[COMP_CWORD-1]}
++			;;
++		words)
++			words=("${COMP_WORDS[@]}")
++			;;
++		cword)
++			cword=$COMP_CWORD
++			;;
++		-n)
++			# assume COMP_WORDBREAKS is already set sanely
++			shift
++			;;
++		esac
++		shift
++	done
++}
++fi
 +fi
 +
  # __gitcomp accepts 1, 2, 3, or 4 arguments
  # generates completion reply with compgen
  __gitcomp ()
@@@ -2522,10 -2345,13 +2555,15 @@@ _git (
  {
  	local i c=1 command __git_dir
  
+ 	if [[ -n ${ZSH_VERSION-} ]]; then
+ 		emulate -L bash
+ 		setopt KSH_TYPESET
+ 	fi
+ 
 -	while [ $c -lt $COMP_CWORD ]; do
 -		i="${COMP_WORDS[c]}"
 +	local cur words cword
 +	_get_comp_words_by_ref -n =: cur words cword
 +	while [ $c -lt $cword ]; do
 +		i="${words[c]}"
  		case "$i" in
  		--git-dir=*) __git_dir="${i#--git-dir=}" ;;
  		--bare)      __git_dir="." ;;
@@@ -2568,9 -2394,14 +2606,14 @@@
  
  _gitk ()
  {
+ 	if [[ -n ${ZSH_VERSION-} ]]; then
+ 		emulate -L bash
+ 		setopt KSH_TYPESET
+ 	fi
+ 
  	__git_has_doubledash && return
  
 -	local cur="${COMP_WORDS[COMP_CWORD]}"
 +	local cur
  	local g="$(__gitdir)"
  	local merge=""
  	if [ -f "$g/MERGE_HEAD" ]; then
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help