There is a function defined in /usr/share/zsh/functions/Completion/Unix/_git
(( $+functions[_git-diff] )) ||
_git-diff () {
local curcontext=$curcontext state line ret=1
declare -A opt_args
local -a diff_options diff_stage_options
__git_setup_diff_options
__git_setup_diff_stage_options
_arguments -C -s $endopt \
$* \
$diff_options \
'(--exit-code)--quiet[disable all output]' \
$diff_stage_options \
'(--cached --staged)--no-index[show diff between two paths on the filesystem]' \
'(--cached --staged --no-index)'{--cached,--staged}'[show diff between index and named commit]' \
'(-)--[start file arguments]' \
'*:: :->from-to-file' && ret=0
case $state in
(from-to-file)
# If "--" is part of $opt_args, this means it was specified before any
# $words arguments. This means that no heads are specified in front, so
# we need to complete *changed* files only.
if [[ -n ${opt_args[(I)--]} ]]; then
if [[ -n ${opt_args[(I)--cached|--staged]} ]]; then
__git_changed-in-index_files && ret=0
else
__git_changed-in-working-tree_files && ret=0
fi
return ret
fi
# If "--no-index" was given, only file paths need to be completed.
if [[ -n ${opt_args[(I)--no-index]} ]]; then
_alternative 'files::_files' && ret=0
return ret
fi
# Otherwise, more complex conditions need to be checked.
case $CURRENT in
(1)
local files_alt='files::__git_changed-in-working-tree_files'
if [[ -n ${opt_args[(I)--cached|--staged]} ]]; then
files_alt='files::__git_changed-in-index_files'
fi
_alternative \
'commit-ranges::__git_commit_ranges' \
'blobs-and-trees-in-treeish::__git_blobs_and_trees_in_treeish' \
$files_alt \
'blobs::__git_blobs ' && ret=0
;;
(2)
# Check if first argument is something special. In case of committish ranges and committishs offer a full list compatible completions.
if __git_is_committish_range $line[1]; then
# Example: git diff branch1..branch2 <tab>
__git_tree_files ${PREFIX:-.} $(__git_committish_range_last $line[1]) && ret=0
elif __git_is_committish $line[1] || __git_is_treeish $line[1]; then
# Example: git diff branch1 <tab>
_alternative \
'commits::__git_commits' \
'blobs-and-trees-in-treeish::__git_blobs_and_trees_in_treeish' \
'files::__git_tree_files ${PREFIX:-.} HEAD' && ret=0
elif __git_is_blob $line[1]; then
_alternative \
'files::__git_cached_files' \
'blobs::__git_blobs' && ret=0
elif [[ -n ${opt_args[(I)--cached|--staged]} ]]; then
# Example: git diff --cached file1 <tab>
__git_changed-in-index_files && ret=0
else
# Example: git diff file1 <tab>
__git_changed-in-working-tree_files && ret=0
fi
;;
(*)
if __git_is_committish_range $line[1]; then
# Example: git diff branch1..branch2 file1 <tab>
__git_tree_files ${PREFIX:-.} $(__git_committish_range_last $line[1]) && ret=0
elif { __git_is_committish $line[1] && __git_is_committish $line[2] } ||
__git_is_treeish $line[2]; then
# Example: git diff branch1 branch2 <tab>
__git_tree_files ${PREFIX:-.} $line[2] && ret=0
elif __git_is_committish $line[1] || __git_is_treeish $line[1]; then
# Example: git diff branch file1 <tab>
# Example: git diff branch -- f<tab>
__git_tree_files ${PREFIX:-.} HEAD && ret=0
elif __git_is_blob $line[1] && __git_is_blob $line[2]; then
_nothing
elif [[ -n ${opt_args[(I)--cached|--staged]} ]]; then
# Example: git diff --cached file1 file2 <tab>
__git_changed-in-index_files && ret=0
else
# Example: git diff file1 file2 <tab>
__git_changed-in-working-tree_files && ret=0
fi
;;
esac
;;
esac
return ret
}
I just need to append
'files::__git_changed_files ${PREFIX:-.} HEAD' \
Above
'files::__git_tree_files ${PREFIX:-.} HEAD' && ret=0
Currently I have copy-pasted the new function in my .zshrc
, and it is working.
However, I think there is a cleaner way to override existing function using .bashrc
or .zshrc
like the following:
_git-diff 2>/dev/null
functions[_git-diff-orig]=$functions[_git-diff]
_git-diff() {
_git-diff-orig "$@"
...
}
But I am not sure how to do it. Can anyone please help. Just to be clear, I want to override the function using .bashrc
or .zshrc
so that it can be more portable.
In zsh
, the value in the functions
associative array is the normalized code text. This means you can use any of the usual text manipulation methods to modify it:
foo() {
echo before
echo and after
}
foo
# => before
# => and after
functions[foo]=${functions[foo]/echo before/echo before;echo during}
foo
# => before
# => during
# => and after
functions foo
# => foo () {
# => echo before
# => echo during
# => echo and after
# => }
Interestingly, zsh will parse, validate, and normalize the code on assignment to the functions
array - it's essentially the same process as declaring a function normally. That's why the output from functions foo
has a newline, even though the string substitution used a semicolon.
Adding these lines to ~/.zshrc
should work for your example:
current="'files::__git_tree_files \${PREFIX:-.} HEAD'"
replacement="'files::__git_changed_files \${PREFIX:-.} HEAD' $current"
functions[_git-diff]=${functions[_git-diff]/$current/$replacement}
There are a number of ways to alter text in shell languages, and some of these may work better for more complex changes to functions. Note that more changes can make the process more fragile, since updates to the base code could trip up the patching process.
Also note that the changes are applied to the code that is stored in the
functions
array; that may not match what is in the original source file.
patch
An obvious choice, since we're patching code. The patch
utility modifies
text based on the output from diff
(usually diff -u
). The utility can handle
some whitespace differences, and some errors such as incorrect line numbers:
plan() {
forecast=${1}
if [[ $forecast == sun ]]; then
print "take sunscreen"
print "wear hat"
elif [[ $forecast == rain ]]; then
print "take umbrella"
print "carry raincoat"
else
print "stay home"
fi
}
plan sun
#=> take sunscreen
#=> wear hat
plan hail
#=> stay home
patchDiff='
--- plan1
+++ plan2
@@ -4,3 +4,5 @@
print "take sunscreen"
+ print "apply sunscreen"
print "wear hat"
+ print "use sunglasses"
elif [[ $forecast == rain ]]
@@ -11,2 +13,3 @@
print "stay home"
+ print "and relax"
fi'
functions[plan]=$(print -- $patchDiff \
| patch -ls -o >(cat) =(print -- $functions[plan]))
plan sun
#=> take sunscreen
#=> apply sunscreen
#=> wear hat
#=> use sunglasses
plan hail
#=> stay home
#=> and relax
sed
Using the sed
stream editor, and building on the previous example:
sedscript='s/print *\(.*\)/print ${(C):-\1}/'
functions[plan]=$(print $functions[plan] | sed -e $sedscript)
functions plan
#=> plan () {
#=> forecast=${1}
#=> if [[ $forecast == sun ]]
#=> then
#=> print ${(C):-"take sunscreen"}
#=> print ${(C):-"apply sunscreen"}
#=> print ${(C):-"wear hat"}
#=> print ${(C):-"use sunglasses"}
#=> elif [[ $forecast == rain ]]
#=> then
#=> print ${(C):-"take umbrella"}
#=> print ${(C):-"carry raincoat"}
#=> else
#=> print ${(C):-"stay home"}
#=> print ${(C):-"and relax"}
#=> fi
#=> }
plan rain
#=> Take Umbrella
#=> Carry Raincoat
This doesn't require matching anything specific in the function. That's both good and bad - it's easy to implement, but it can break with even simple changes to the base function:
# split the code into an array of lines (f)
lines=("${(f)functions[plan]}")
# add code in the middle
newCode=(
${lines[1]}
'if [[ $forecast == tornado ]]; then
print "find shelter"
print "go quickly"
return
fi'
${lines[2,-1]})
# rejoin array with newlines (F)
functions[plan]=${(F)newCode}
functions plan
#=> plan () {
#=> forecast=${1}
#=> if [[ $forecast == tornado ]]
#=> then
#=> print "find shelter"
#=> print "go quickly"
#=> return
#=> fi
#=> if [[ $forecast == sun ]]
#=> then
#=> print ${(C):-"take sunscreen"}
#=> print ${(C):-"apply sunscreen"}
#=> print ${(C):-"wear hat"}
#=> print ${(C):-"use sunglasses"}
#=> elif [[ $forecast == rain ]]
#=> then
#=> print ${(C):-"take umbrella"}
#=> print ${(C):-"carry raincoat"}
#=> else
#=> print ${(C):-"stay home"}
#=> print ${(C):-"and relax"}
#=> fi
#=> }
plan tornado
#=> find shelter
#=> go quickly
functions -c
If the updated function can be based on a call to the initial function, then functions -c
(in zsh 5.8 and later) can be used to create a copy to use in the new function:
functions -c plan plan_orig
plan() {
print "Here's the list for $1:"
plan_orig "$@"
}
plan rain
#=> Here's the list for rain:
#=> Take Umbrella
#=> Carry Raincoat
There is more information about modifying functions in this answer.