Using the Fish shell, I would like to create a function (gcom
) to use as an alias for the git commit
command, accepting an arbitrary number of string arguments and passing them as -m
options, so that
git commit -m "a" -m "b" -m "c"
could be written, in short, gcom "a" "b" "c"
.
The gcom
function should accept N arguments, and run git command
with the respective N -m
options.
This could be done with a loop over the $argv
array, constructing a command manually using string join
and executing such command with eval
.
However, that looks clunky, and I would love to find a sleeker alternative.
Even better if additional gcom -?
options are passed through unmodified!
git commit -mfoo
uses "foo" as the message, so you can simply prefix every argument with -m
:
function gcom
git commit -m$argv
end
Since $argv is a list, this will add "-m" to every argument in it.
Unlike other shells, there is no word-splitting here. $argv is not split on whitespace, $argv is the list of all arguments as they have been given. So it works with spaces, newlines, etc. The arguments simply need to be quoted where you give them to gcom
:
gcom "first argument with spaces" "second"\n"argument"\n"with"\n"newlines"
will run the equivalent of
git commit -m"first argument with spaces" -m "second
argument
with
newlines"
Try it out with printf
:
set -l mylist "first argument with spaces" "second"\n"argument"\n"with"\n"newlines"
printf '<%s>\n' -m$mylist
will print
<-mfirst argument with spaces>
<-msecond
argument
with
newlines>
Even better if additional gcom -? options are passed through unmodified!
If you wanted to do that, you might leave all arguments starting with -
in an additional $args list without adding a -m
. This would be broken if any of the options itself takes an argument, like --author
.
Think about:
gcom --author "mycoolemail@example.com" these are my message words
This should execute like
git commit --author mycoolemail@example.com -m these -m are -m my -m message -m words
But the only way to figure out that the "mycoolemail@example.com" belongs to --author
and therefore shouldn't have a corresponding -m
is to know that --author
takes options.
To do that you could use fish's argparse builtin, and you would have to tell it about all of git-commit's options. This would be something like
function gcom
# The part before the `--` are descriptions for the options that git-commit takes
# A "=" means that the option takes a mandatory argument and so it can be written like "--author foo",
# A "=?" means it takes an optional argument and needs to be written like "-ufoo".
argparse author= a interactive patch s v 'u=?' -- $argv
# Argparse leaves all the non-option arguments in $argv.
# For each option it gives you a $_flag_option variable,
# but that only includes the *value* for the options that take arguments
#
# If we wanted to pass all --author= options we could use the `string split -m1` trick from above,
# but here we assume that only the last --author is of use.
#
# This needs to be done for all the options that take arguments,
# simple boolean flags are just stored as the flags
# - `$_flag_patch` will contain "--patch"
set -l flags $_flag_a $_flag_interactive $_flag_patch
if set -q _flag_author
set -a flags --author $_flag_author[-1]
end
if set -q _flag_u
set -a flags -u$_flag_u[-1]
end
# [ now do the -m trick with the leftover $argv ]
# ...
git commit $flags -m$argv
end
Unfortunately, there is no way around listing all the options that take arguments, because otherwise the argument to the option is confused with a normal message argument. There simply isn't anything that fish could do here, that's just how argument parsing works.
You could run argparse --ignore-unknown
, which would tell argparse to leave unknown options in $argv, and then still skip everything starting with a -
. This would break if you ever passed an option group (think ls -lah
, which has three short-options) and the last option takes an argument but you haven't listed one of the options before.
Because fish wouldn't be able to figure out that e.g. -sFfile
means -s -F=file
if you hadn't told it that "-s" exists - because it can't know that -s
takes no options. If it did, this would be the same as -s=Ffile
. So it can only leave the entire group intact.