* [PATCH] alias: document caveats and add trace of prepared command
@ 2024-05-22 2:41 Ian Wienand
2024-05-22 3:29 ` Eric Sunshine
` (2 more replies)
0 siblings, 3 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-22 2:41 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
There are a number of hidden caveats when using command/shell
expansion ("!") in an alias. The command is exec'd, unless it can be
split, when it is then run as an argument to "sh -c". Split commands
have "$@" appended.
Firstly this updates the documentation to explain this a little
clearer.
Secondly, this adds a trace point in prepare_cmd where this
substitution is done, so we can see the command being run without
having to resort to strace/code inspection. e.g. "test = !echo" you
will show:
$ GIT_TRACE=1 git test hello
10:58:56.877234 git.c:755 trace: exec: git-test hello
10:58:56.877382 run-command.c:657 trace: run_command: git-test hello
10:58:56.878655 run-command.c:657 trace: run_command: echo hello
10:58:56.878747 run-command.c:437 trace: prepare_cmd: /usr/bin/echo hello
hello
For a "split" alias, e.g. test = "!echo $*" you will see
$ GIT_TRACE=1 git test hello
11:00:45.959420 git.c:755 trace: exec: git-test hello
11:00:45.959737 run-command.c:657 trace: run_command: git-test hello
11:00:45.961424 run-command.c:657 trace: run_command: 'echo $*' hello
11:00:45.961528 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'echo $* "$@"' 'echo $*' hello
hello hello
which clearly shows you the appended "$@". This can be very helpful
when an alias is giving you an unexpected synatx error that is very
difficult figure out from only the run_command trace point,
e.g. test = "!for i in 1 2 3; do echo $i; done"
$ GIT_TRACE=1 test hello
11:02:39.813030 git.c:755 trace: exec: git-test hello
11:02:39.813233 run-command.c:657 trace: run_command: git-test hello
11:02:39.814384 run-command.c:657 trace: run_command: 'for i in 1 2 3; do echo $i; done' hello
11:02:39.814468 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'for i in 1 2 3; do echo $i; done "$@"' 'for i in 1 2 3; do echo $i; done' hello
for i in 1 2 3; do echo $i; done: -c: line 1: syntax error near unexpected token `"$@"'
for i in 1 2 3; do echo $i; done: -c: line 1: `for i in 1 2 3; do echo $i; done "$@"'
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 26 +++++++++++++++++++++-----
run-command.c | 1 +
2 files changed, 22 insertions(+), 5 deletions(-)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 01df96fab3..a3f090d79d 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -21,8 +21,24 @@ If the alias expansion is prefixed with an exclamation point,
it will be treated as a shell command. For example, defining
`alias.new = !gitk --all --not ORIG_HEAD`, the invocation
`git new` is equivalent to running the shell command
-`gitk --all --not ORIG_HEAD`. Note that shell commands will be
-executed from the top-level directory of a repository, which may
-not necessarily be the current directory.
-`GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
-from the original current directory. See linkgit:git-rev-parse[1].
+`gitk --all --not ORIG_HEAD`. Note:
++
+* Shell commands will be executed from the top-level directory of a
+ repository, which may not necessarily be the current directory.
+* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
+ from the original current directory. See linkgit:git-rev-parse[1].
+* Single commands will be executed directly. Commands that can be "split"
+ (contain whitespace or shell-reserved characters) will be run as shell
+ commands via an argument to `sh -c`.
+* When arguments are present to a "split" command running in a shell,
+ the shell command will have `"$@"` appended. The first non-command
+ argument to `sh -c` (i.e. `$0` to the command) is always the alias
+ string, and other user specified arguments will follow.
+** This may initially be confusing if your command references argument
+ variables or is not expecting the presence of `"$@"`. For example:
+ `alias.echo = "!echo $1"` when run as `git echo arg` will execute
+ `sh -c "echo $1 $@" "echo $1" "1"` resulting in output `1 1`.
+ An alias `alias.for = "!for i in 1 2 3; do echo $i; done"` will fail
+ if any arguments are specified to `git for` as the appended `"$@"` will
+ create invalid shell syntax. Setting `GIT_TRACE=1` can help debug
+ the command being run.
diff --git a/run-command.c b/run-command.c
index 1b821042b4..36b2b2f194 100644
--- a/run-command.c
+++ b/run-command.c
@@ -435,6 +435,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
}
}
+ trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
return 0;
}
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH] alias: document caveats and add trace of prepared command
2024-05-22 2:41 [PATCH] alias: document caveats and add trace of prepared command Ian Wienand
@ 2024-05-22 3:29 ` Eric Sunshine
2024-05-22 16:07 ` Junio C Hamano
2024-05-23 4:20 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Ian Wienand
2 siblings, 0 replies; 43+ messages in thread
From: Eric Sunshine @ 2024-05-22 3:29 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
On Tue, May 21, 2024 at 10:42 PM Ian Wienand <iwienand@redhat.com> wrote:
> [...]
> For a "split" alias, e.g. test = "!echo $*" you will see
>
> $ GIT_TRACE=1 git test hello
> 11:00:45.959420 git.c:755 trace: exec: git-test hello
> 11:00:45.959737 run-command.c:657 trace: run_command: git-test hello
> 11:00:45.961424 run-command.c:657 trace: run_command: 'echo $*' hello
> 11:00:45.961528 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'echo $* "$@"' 'echo $*' hello
> hello hello
>
> which clearly shows you the appended "$@". This can be very helpful
> when an alias is giving you an unexpected synatx error that is very
s/synatx/syntax/
> difficult figure out from only the run_command trace point,
> e.g. test = "!for i in 1 2 3; do echo $i; done"
> [...]
> Signed-off-by: Ian Wienand <iwienand@redhat.com>
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH] alias: document caveats and add trace of prepared command
2024-05-22 2:41 [PATCH] alias: document caveats and add trace of prepared command Ian Wienand
2024-05-22 3:29 ` Eric Sunshine
@ 2024-05-22 16:07 ` Junio C Hamano
2024-05-23 0:38 ` Ian Wienand
2024-05-23 4:20 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Ian Wienand
2 siblings, 1 reply; 43+ messages in thread
From: Junio C Hamano @ 2024-05-22 16:07 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> For a "split" alias, e.g. test = "!echo $*" you will see
>
> $ GIT_TRACE=1 git test hello
> 11:00:45.959420 git.c:755 trace: exec: git-test hello
> 11:00:45.959737 run-command.c:657 trace: run_command: git-test hello
> 11:00:45.961424 run-command.c:657 trace: run_command: 'echo $*' hello
> 11:00:45.961528 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'echo $* "$@"' 'echo $*' hello
> hello hello
>
> which clearly shows you the appended "$@".
A question and a comment on this part.
Who are "you" in this context?
It is somewhat surprising if we do not consistently add "$@" (or do
an equivalent), as the point of adding "$@" is to allow an alias to
specify "the initial part of a command line", e.g.
[alias] lg = log --oneline
to let you say "git lg" to mean "git log --oneline" and also you can
say "git lg --graph" to mean "git log --oneline --graph". In other
words, what you type after "git lg" makes "the rest of the command
line", while alias gives "the initial part".
Are you sure that with
[alias] lg = log
you get the rest of the command line ignored, in other words, if you
say "git lg --oneline", it does not do "git log --oneline"?
> Documentation/config/alias.txt | 26 +++++++++++++++++++++-----
> run-command.c | 1 +
> 2 files changed, 22 insertions(+), 5 deletions(-)
>
> diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
> index 01df96fab3..a3f090d79d 100644
> --- a/Documentation/config/alias.txt
> +++ b/Documentation/config/alias.txt
> @@ -21,8 +21,24 @@ If the alias expansion is prefixed with an exclamation point,
> it will be treated as a shell command. For example, defining
> `alias.new = !gitk --all --not ORIG_HEAD`, the invocation
> `git new` is equivalent to running the shell command
> -`gitk --all --not ORIG_HEAD`. Note that shell commands will be
> -executed from the top-level directory of a repository, which may
> -not necessarily be the current directory.
> -`GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
> -from the original current directory. See linkgit:git-rev-parse[1].
> +`gitk --all --not ORIG_HEAD`. Note:
> ++
> +* Shell commands will be executed from the top-level directory of a
> + repository, which may not necessarily be the current directory.
> +* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
> + from the original current directory. See linkgit:git-rev-parse[1].
> +* Single commands will be executed directly. Commands that can be "split"
> + (contain whitespace or shell-reserved characters) will be run as shell
> + commands via an argument to `sh -c`.
> +* When arguments are present to a "split" command running in a shell,
> + the shell command will have `"$@"` appended. The first non-command
> + argument to `sh -c` (i.e. `$0` to the command) is always the alias
> + string, and other user specified arguments will follow.
> +** This may initially be confusing if your command references argument
> + variables or is not expecting the presence of `"$@"`. For example:
> + `alias.echo = "!echo $1"` when run as `git echo arg` will execute
> + `sh -c "echo $1 $@" "echo $1" "1"` resulting in output `1 1`.
> + An alias `alias.for = "!for i in 1 2 3; do echo $i; done"` will fail
> + if any arguments are specified to `git for` as the appended `"$@"` will
> + create invalid shell syntax. Setting `GIT_TRACE=1` can help debug
> + the command being run.
The above does a bit too many things in a single patch to be
reviewable. Perhaps make the above change in two patches?
(1) Reformulate the existing test into "Note:" followed by a
bulletted list.
(2) Add new items to the updated and easier-to-read form prepared
by the first patch.
You have a lengthy new text in the documentation to help confused
users, but it looks to me that it places too much stress on the
mechanics (i.e. '$@' is added to the command line) without saying
much about the intent (i.e. you need to use '$@' to allow the
command line invocation to supply "the rest of the command line"
when you are running the alias via the shell). I've learned over
the course of this project that readers learn better when the intent
behind the mechanics is given in an understandable form.
I think the idea is "we want the 'concatenation of what the alias
supplies and what the user gives when invoking the alias from the
command line' to work sensibly". The most generic way to do so when
processing "git lg --graph -3" with "[alias] lg = !git log --oneline" is
sh -c 'git log --oneline "$@"' - '--graph' '-3'
(readers can try this at home by adding 'echo ' before 'log'). We
may try to "optimize" the most generic pattern when there is no need
to use "sh -c" go a more direct route. For example, if you have
[alias] !foo = bar
then there is no need to use "sh -c" in the first place. "git foo
baz quux" should behave just like when you said "sh bar baz quux" on
the command line, i.e. execute your shell script "bar" and give
"baz" and "quux" as two arguments to that script. We can prepare
argv[] = ("sh", "bar", "baz", "quux") and fork-exec that, so the
command line the underlying exec(2) call sees may not have "$@" (and
it will not have "-c", either).
You can undo the "optimization" and rewrite the command line to
sh -c 'bar "$@"' - 'baz' 'quux'
and it should mean the same thing.
> diff --git a/run-command.c b/run-command.c
> index 1b821042b4..36b2b2f194 100644
> --- a/run-command.c
> +++ b/run-command.c
> @@ -435,6 +435,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
> }
> }
>
> + trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
> return 0;
> }
Hmph, we do not have much tests that look for 'trace: foo' in our
test suite, but t0014 seems to use it. Don't we need to cover this
new trace output in the test in the same patch (probably becomes
patch 3 after the two documentation changes)?
Thanks.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH] alias: document caveats and add trace of prepared command
2024-05-22 16:07 ` Junio C Hamano
@ 2024-05-23 0:38 ` Ian Wienand
0 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-23 0:38 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
On Wed, May 22, 2024 at 09:07:55AM -0700, Junio C Hamano wrote:
> Ian Wienand <iwienand@redhat.com> writes:
>
> > For a "split" alias, e.g. test = "!echo $*" you will see
> >
> > $ GIT_TRACE=1 git test hello
> > 11:00:45.959420 git.c:755 trace: exec: git-test hello
> > 11:00:45.959737 run-command.c:657 trace: run_command: git-test hello
> > 11:00:45.961424 run-command.c:657 trace: run_command: 'echo $*' hello
> > 11:00:45.961528 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'echo $* "$@"' 'echo $*' hello
> > hello hello
> >
> > which clearly shows you the appended "$@".
>
> A question and a comment on this part.
>
> Who are "you" in this context?
Heh, "you" is the person who has been given a large git alias that
probably should have been written some other way (but likely due to
the fact that stuffing all the workflow logic into a git config-file
means that nothing extra like shell/python scripts needed to be
distributed) that "you" have to now debug :) I feel the extra info in
GIT_TRACE output showing what is _actually_ happening after
prepare_cmd would really help this person (who was me :).
> It is somewhat surprising if we do not consistently add "$@" (or do
> an equivalent), as the point of adding "$@" is to allow an alias to
> specify "the initial part of a command line", e.g.
>
> [alias] lg = log --oneline
>
> to let you say "git lg" to mean "git log --oneline" and also you can
> say "git lg --graph" to mean "git log --oneline --graph". In other
> words, what you type after "git lg" makes "the rest of the command
> line", while alias gives "the initial part".
>
> Are you sure that with
>
> [alias] lg = log
>
> you get the rest of the command line ignored, in other words, if you
> say "git lg --oneline", it does not do "git log --oneline"?
I'm not quite sure I'm following here, sorry. The behaviour is
different for "shell" aliases prefixed with !. There the stragtegy is
to append "$@", but only if there are arguments. Whatever the merits
or not of that, I feel like it's impossible to change because it's out
there now.
Now I've been looking, I've seen a number of interesting ways people
have been dealing with this. Some people have ignored it, for example
I've seen credential helpers use ":" to ignore it, like
"!echo password=\$${TOKEN_NAME}; :"
The git documentation for credential helpers gives another way,
defining an inline function, so the "$@" appended becomes function
arguments to "f()" like
"!f() { echo $1 $2 ; }; f"
Then I've seen other people nest in a "sh -c" call, and "fake" $0 so
that the appended "$@" becomes the $1 arguments to the interior shell
call (cue quoting nightmares!) like
"!sh -c 'echo $1 $2' foo"
Once you realise what is going on, these are somewhat understandable,
but the documentation as written doesn't guide you in the right
direction.
> The above does a bit too many things in a single patch to be
> reviewable. Perhaps make the above change in two patches?
Sure, I am happy to split this up
> You have a lengthy new text in the documentation to help confused
> users, but it looks to me that it places too much stress on the
> mechanics (i.e. '$@' is added to the command line) without saying
> much about the intent (i.e. you need to use '$@' to allow the
> command line invocation to supply "the rest of the command line"
> when you are running the alias via the shell). I've learned over
> the course of this project that readers learn better when the intent
> behind the mechanics is given in an understandable form.
Let me take your feedback here and work it into the split up change,
and we can discuss further. Thanks.
> > diff --git a/run-command.c b/run-command.c
> > index 1b821042b4..36b2b2f194 100644
> > --- a/run-command.c
> > +++ b/run-command.c
> > @@ -435,6 +435,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
> > }
> > }
> >
> > + trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
> > return 0;
> > }
>
> Hmph, we do not have much tests that look for 'trace: foo' in our
> test suite, but t0014 seems to use it. Don't we need to cover this
> new trace output in the test in the same patch (probably becomes
> patch 3 after the two documentation changes)?
Ok, thanks for the pointer, I'll look at expanding this one.
-i
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v2 1/3] Documentation: alias: rework notes into points
2024-05-22 2:41 [PATCH] alias: document caveats and add trace of prepared command Ian Wienand
2024-05-22 3:29 ` Eric Sunshine
2024-05-22 16:07 ` Junio C Hamano
@ 2024-05-23 4:20 ` Ian Wienand
2024-05-23 4:20 ` [PATCH v2 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
` (3 more replies)
2 siblings, 4 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-23 4:20 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
There are a number of caveats when using aliases. Rather than
stuffing them all together in a paragraph, let's separate them out
into individual points to make it clearer what's going on.
---
Documentation/config/alias.txt | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 01df96fab3..40851ef429 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -21,8 +21,9 @@ If the alias expansion is prefixed with an exclamation point,
it will be treated as a shell command. For example, defining
`alias.new = !gitk --all --not ORIG_HEAD`, the invocation
`git new` is equivalent to running the shell command
-`gitk --all --not ORIG_HEAD`. Note that shell commands will be
-executed from the top-level directory of a repository, which may
-not necessarily be the current directory.
-`GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
-from the original current directory. See linkgit:git-rev-parse[1].
+`gitk --all --not ORIG_HEAD`. Note:
++
+* Shell commands will be executed from the top-level directory of a
+ repository, which may not necessarily be the current directory.
+* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
+ from the original current directory. See linkgit:git-rev-parse[1].
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 2/3] Documentation: alias: add notes on shell expansion
2024-05-23 4:20 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Ian Wienand
@ 2024-05-23 4:20 ` Ian Wienand
2024-05-23 4:20 ` [PATCH v2 3/3] run-command: show prepared command Ian Wienand
` (2 subsequent siblings)
3 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-23 4:20 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
When writing inline shell for shell-expansion aliases (i.e. prefixed
with "!"), there are some caveats around argument parsing to be aware
of. This series of notes attempts to explain what is happening more
clearly.
---
Documentation/config/alias.txt | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 40851ef429..51fa876c91 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -27,3 +27,24 @@ it will be treated as a shell command. For example, defining
repository, which may not necessarily be the current directory.
* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
from the original current directory. See linkgit:git-rev-parse[1].
+* If the shell alias is the full path to a binary, it will be executed
+ directly with any arguments.
+* If the alias contains any whitespace or reserved characters, it will
+ be considered an inline script and run as an argument to `sh -c`.
+* When running as a script, Git appends "$@" to the alias shell
+ command when arguments are present. If there are no arguments,
+ `"$@"` will not be appended.
+** This may initially be confusing if your alias script references
+ argument variables, or is otherwise not expecting the presence of
+ `"$@"`. For example: `alias.echo = "!echo $1"` when run as `git
+ echo arg` will actually execute `sh -c "echo $1 $@" "echo $1"
+ "arg"` resulting in output `arg arg`. An alias `alias.for = "!for
+ i in 1 2 3; do echo $i; done"` will fail if any arguments are
+ specified to `git for` as the appended `"$@"` will create invalid
+ shell syntax.
+** A convenient way to deal with this is to write your script
+ operations in an inline function that is then called with any
+ arguments from the command-line. For example `alias.echo = "!e() {
+ echo $* ; }; e" will work as expected, with the function `e`
+ receiving any arugments from the command-line.
+** Setting `GIT_TRACE=1` can help debug the command being run.
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v2 3/3] run-command: show prepared command
2024-05-23 4:20 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-23 4:20 ` [PATCH v2 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
@ 2024-05-23 4:20 ` Ian Wienand
2024-05-23 4:27 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Eric Sunshine
2024-05-23 4:37 ` [PATCH v3 " Ian Wienand
3 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-23 4:20 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
This adds a trace point in prepare_cmd, so we can see the actual
command being run without having to resort to strace/code inspection.
e.g. "test = !echo" when run under GIT_TRACE will show:
$ GIT_TRACE=1 git test hello
10:58:56.877234 git.c:755 trace: exec: git-test hello
10:58:56.877382 run-command.c:657 trace: run_command: git-test hello
10:58:56.878655 run-command.c:657 trace: run_command: echo hello
10:58:56.878747 run-command.c:437 trace: prepare_cmd: /usr/bin/echo hello
hello
A "split" alias, e.g. test = "!echo $*" will show the shell wrapping
and appending of "$@".
$ GIT_TRACE=1 git test hello
11:00:45.959420 git.c:755 trace: exec: git-test hello
11:00:45.959737 run-command.c:657 trace: run_command: git-test hello
11:00:45.961424 run-command.c:657 trace: run_command: 'echo $*' hello
11:00:45.961528 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'echo $* "$@"' 'echo $*' hello
hello hello
For example, this can be very helpful when an alias is giving you an
unexpected syntax error that is very difficult figure out from only
the run_command trace point, e.g.
test = "!for i in 1 2 3; do echo $i; done"
will fail if there is an argument given, we can see why from the
output.
$ GIT_TRACE=1 test hello
11:02:39.813030 git.c:755 trace: exec: git-test hello
11:02:39.813233 run-command.c:657 trace: run_command: git-test hello
11:02:39.814384 run-command.c:657 trace: run_command: 'for i in 1 2 3; do echo $i; done' hello
11:02:39.814468 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'for i in 1 2 3; do echo $i; done "$@"' 'for i in 1 2 3; do echo $i; done' hello
for i in 1 2 3; do echo $i; done: -c: line 1: syntax error near unexpected token `"$@"'
for i in 1 2 3; do echo $i; done: -c: line 1: `for i in 1 2 3; do echo $i; done "$@"'
A test case is added.
---
run-command.c | 1 +
t/t0014-alias.sh | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/run-command.c b/run-command.c
index 1b821042b4..36b2b2f194 100644
--- a/run-command.c
+++ b/run-command.c
@@ -435,6 +435,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
}
}
+ trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
return 0;
}
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 95568342be..75c8763a6c 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -44,4 +44,11 @@ test_expect_success 'run-command formats empty args properly' '
test_cmp expect actual
'
+test_expect_success 'tracing a shell alias with arguments shows full prepared command' '
+ git config alias.echo "!echo \$*" &&
+ env GIT_TRACE=1 git echo argument 2>output &&
+ cp output /tmp/output &&
+ test_grep "^trace: prepare_cmd: /bin/sh -c '\''echo \$\* \"\$@\"" output
+'
+
test_done
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v2 1/3] Documentation: alias: rework notes into points
2024-05-23 4:20 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-23 4:20 ` [PATCH v2 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-23 4:20 ` [PATCH v2 3/3] run-command: show prepared command Ian Wienand
@ 2024-05-23 4:27 ` Eric Sunshine
2024-05-23 4:39 ` Ian Wienand
2024-05-23 4:37 ` [PATCH v3 " Ian Wienand
3 siblings, 1 reply; 43+ messages in thread
From: Eric Sunshine @ 2024-05-23 4:27 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
On Thu, May 23, 2024 at 12:22 AM Ian Wienand <iwienand@redhat.com> wrote:
> There are a number of caveats when using aliases. Rather than
> stuffing them all together in a paragraph, let's separate them out
> into individual points to make it clearer what's going on.
> ---
Missing sign-off.
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v3 1/3] Documentation: alias: rework notes into points
2024-05-23 4:20 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Ian Wienand
` (2 preceding siblings ...)
2024-05-23 4:27 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Eric Sunshine
@ 2024-05-23 4:37 ` Ian Wienand
2024-05-23 4:37 ` [PATCH v3 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
` (3 more replies)
3 siblings, 4 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-23 4:37 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
There are a number of caveats when using aliases. Rather than
stuffing them all together in a paragraph, let's separate them out
into individual points to make it clearer what's going on.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 01df96fab3..40851ef429 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -21,8 +21,9 @@ If the alias expansion is prefixed with an exclamation point,
it will be treated as a shell command. For example, defining
`alias.new = !gitk --all --not ORIG_HEAD`, the invocation
`git new` is equivalent to running the shell command
-`gitk --all --not ORIG_HEAD`. Note that shell commands will be
-executed from the top-level directory of a repository, which may
-not necessarily be the current directory.
-`GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
-from the original current directory. See linkgit:git-rev-parse[1].
+`gitk --all --not ORIG_HEAD`. Note:
++
+* Shell commands will be executed from the top-level directory of a
+ repository, which may not necessarily be the current directory.
+* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
+ from the original current directory. See linkgit:git-rev-parse[1].
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v3 2/3] Documentation: alias: add notes on shell expansion
2024-05-23 4:37 ` [PATCH v3 " Ian Wienand
@ 2024-05-23 4:37 ` Ian Wienand
2024-05-23 4:37 ` [PATCH v3 3/3] run-command: show prepared command Ian Wienand
` (2 subsequent siblings)
3 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-23 4:37 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
When writing inline shell for shell-expansion aliases (i.e. prefixed
with "!"), there are some caveats around argument parsing to be aware
of. This series of notes attempts to explain what is happening more
clearly.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 40851ef429..51fa876c91 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -27,3 +27,24 @@ it will be treated as a shell command. For example, defining
repository, which may not necessarily be the current directory.
* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
from the original current directory. See linkgit:git-rev-parse[1].
+* If the shell alias is the full path to a binary, it will be executed
+ directly with any arguments.
+* If the alias contains any whitespace or reserved characters, it will
+ be considered an inline script and run as an argument to `sh -c`.
+* When running as a script, Git appends "$@" to the alias shell
+ command when arguments are present. If there are no arguments,
+ `"$@"` will not be appended.
+** This may initially be confusing if your alias script references
+ argument variables, or is otherwise not expecting the presence of
+ `"$@"`. For example: `alias.echo = "!echo $1"` when run as `git
+ echo arg` will actually execute `sh -c "echo $1 $@" "echo $1"
+ "arg"` resulting in output `arg arg`. An alias `alias.for = "!for
+ i in 1 2 3; do echo $i; done"` will fail if any arguments are
+ specified to `git for` as the appended `"$@"` will create invalid
+ shell syntax.
+** A convenient way to deal with this is to write your script
+ operations in an inline function that is then called with any
+ arguments from the command-line. For example `alias.echo = "!e() {
+ echo $* ; }; e" will work as expected, with the function `e`
+ receiving any arugments from the command-line.
+** Setting `GIT_TRACE=1` can help debug the command being run.
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v3 3/3] run-command: show prepared command
2024-05-23 4:37 ` [PATCH v3 " Ian Wienand
2024-05-23 4:37 ` [PATCH v3 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
@ 2024-05-23 4:37 ` Ian Wienand
2024-05-23 15:29 ` Junio C Hamano
2024-05-23 15:14 ` [PATCH v3 1/3] Documentation: alias: rework notes into points Junio C Hamano
2024-05-24 7:32 ` [PATCH v4 " Ian Wienand
3 siblings, 1 reply; 43+ messages in thread
From: Ian Wienand @ 2024-05-23 4:37 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
This adds a trace point in prepare_cmd, so we can see the actual
command being run without having to resort to strace/code inspection.
e.g. "test = !echo" when run under GIT_TRACE will show:
$ GIT_TRACE=1 git test hello
10:58:56.877234 git.c:755 trace: exec: git-test hello
10:58:56.877382 run-command.c:657 trace: run_command: git-test hello
10:58:56.878655 run-command.c:657 trace: run_command: echo hello
10:58:56.878747 run-command.c:437 trace: prepare_cmd: /usr/bin/echo hello
hello
A "split" alias, e.g. test = "!echo $*" will show the shell wrapping
and appending of "$@".
$ GIT_TRACE=1 git test hello
11:00:45.959420 git.c:755 trace: exec: git-test hello
11:00:45.959737 run-command.c:657 trace: run_command: git-test hello
11:00:45.961424 run-command.c:657 trace: run_command: 'echo $*' hello
11:00:45.961528 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'echo $* "$@"' 'echo $*' hello
hello hello
For example, this can be very helpful when an alias is giving you an
unexpected syntax error that is very difficult figure out from only
the run_command trace point, e.g.
test = "!for i in 1 2 3; do echo $i; done"
will fail if there is an argument given, we can see why from the
output.
$ GIT_TRACE=1 test hello
11:02:39.813030 git.c:755 trace: exec: git-test hello
11:02:39.813233 run-command.c:657 trace: run_command: git-test hello
11:02:39.814384 run-command.c:657 trace: run_command: 'for i in 1 2 3; do echo $i; done' hello
11:02:39.814468 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'for i in 1 2 3; do echo $i; done "$@"' 'for i in 1 2 3; do echo $i; done' hello
for i in 1 2 3; do echo $i; done: -c: line 1: syntax error near unexpected token `"$@"'
for i in 1 2 3; do echo $i; done: -c: line 1: `for i in 1 2 3; do echo $i; done "$@"'
A test case is added.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
run-command.c | 1 +
t/t0014-alias.sh | 7 +++++++
2 files changed, 8 insertions(+)
diff --git a/run-command.c b/run-command.c
index 1b821042b4..36b2b2f194 100644
--- a/run-command.c
+++ b/run-command.c
@@ -435,6 +435,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
}
}
+ trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
return 0;
}
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 95568342be..75c8763a6c 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -44,4 +44,11 @@ test_expect_success 'run-command formats empty args properly' '
test_cmp expect actual
'
+test_expect_success 'tracing a shell alias with arguments shows full prepared command' '
+ git config alias.echo "!echo \$*" &&
+ env GIT_TRACE=1 git echo argument 2>output &&
+ cp output /tmp/output &&
+ test_grep "^trace: prepare_cmd: /bin/sh -c '\''echo \$\* \"\$@\"" output
+'
+
test_done
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v2 1/3] Documentation: alias: rework notes into points
2024-05-23 4:27 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Eric Sunshine
@ 2024-05-23 4:39 ` Ian Wienand
0 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-23 4:39 UTC (permalink / raw)
To: Eric Sunshine; +Cc: git
On Thu, May 23, 2024 at 12:27:17AM -0400, Eric Sunshine wrote:
> On Thu, May 23, 2024 at 12:22 AM Ian Wienand <iwienand@redhat.com> wrote:
> > There are a number of caveats when using aliases. Rather than
> > stuffing them all together in a paragraph, let's separate them out
> > into individual points to make it clearer what's going on.
> > ---
>
> Missing sign-off.
Sigh .. thanks sent v3
-i
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v3 1/3] Documentation: alias: rework notes into points
2024-05-23 4:37 ` [PATCH v3 " Ian Wienand
2024-05-23 4:37 ` [PATCH v3 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-23 4:37 ` [PATCH v3 3/3] run-command: show prepared command Ian Wienand
@ 2024-05-23 15:14 ` Junio C Hamano
2024-05-24 7:32 ` [PATCH v4 " Ian Wienand
3 siblings, 0 replies; 43+ messages in thread
From: Junio C Hamano @ 2024-05-23 15:14 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> There are a number of caveats when using aliases. Rather than
> stuffing them all together in a paragraph, let's separate them out
> into individual points to make it clearer what's going on.
Nicely explained.
> diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
> index 01df96fab3..40851ef429 100644
> --- a/Documentation/config/alias.txt
> +++ b/Documentation/config/alias.txt
> @@ -21,8 +21,9 @@ If the alias expansion is prefixed with an exclamation point,
> it will be treated as a shell command. For example, defining
> `alias.new = !gitk --all --not ORIG_HEAD`, the invocation
> `git new` is equivalent to running the shell command
> +`gitk --all --not ORIG_HEAD`. Note:
> ++
> +* Shell commands will be executed from the top-level directory of a
> + repository, which may not necessarily be the current directory.
> +* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
> + from the original current directory. See linkgit:git-rev-parse[1].
Looking for '[NOTE]' in the Documentation/ files finds sections
marked up like this (this one is from Documentation/git-blame.txt):
...
parser (which should be quite natural for most scripting languages).
+
[NOTE]
For people who do parsing: to make it more robust, just ignore any
lines between the first and last one ("<sha1>" and "filename" lines)
...
and its rendition looks like this:
https://git.github.io/htmldocs/git-blame.html#:~:text=most%20scripting%20languages).-,Note,-For%20people%20who
I am undecided if it gives a better presentation, especially when we
are giving bulletted list, so let's take the patch as-is and leave
it to interested folks to explore the use of [NOTE] _after_ this set
of patches lands as #leftoverbit follow-up topic.
Thanks.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v3 3/3] run-command: show prepared command
2024-05-23 4:37 ` [PATCH v3 3/3] run-command: show prepared command Ian Wienand
@ 2024-05-23 15:29 ` Junio C Hamano
2024-05-23 23:40 ` Junio C Hamano
2024-05-24 0:43 ` Ian Wienand
0 siblings, 2 replies; 43+ messages in thread
From: Junio C Hamano @ 2024-05-23 15:29 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> This adds a trace point in prepare_cmd, so we can see the actual
> command being run without having to resort to strace/code inspection.
>
> e.g. "test = !echo" when run under GIT_TRACE will show:
>
> $ GIT_TRACE=1 git test hello
> 10:58:56.877234 git.c:755 trace: exec: git-test hello
> 10:58:56.877382 run-command.c:657 trace: run_command: git-test hello
> 10:58:56.878655 run-command.c:657 trace: run_command: echo hello
> 10:58:56.878747 run-command.c:437 trace: prepare_cmd: /usr/bin/echo hello
> hello
Nice.
> A "split" alias, e.g. test = "!echo $*" will show the shell wrapping
> and appending of "$@".
>
> $ GIT_TRACE=1 git test hello
> 11:00:45.959420 git.c:755 trace: exec: git-test hello
> 11:00:45.959737 run-command.c:657 trace: run_command: git-test hello
> 11:00:45.961424 run-command.c:657 trace: run_command: 'echo $*' hello
> 11:00:45.961528 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'echo $* "$@"' 'echo $*' hello
> hello hello
Nice again.
But ...
> For example, this can be very helpful when an alias is giving you an
> unexpected syntax error that is very difficult figure out from only
> the run_command trace point, e.g.
>
> test = "!for i in 1 2 3; do echo $i; done"
>
> will fail if there is an argument given, we can see why from the
> output.
... if the reader truly understands "the alias gives the command and
its leading arguments, to which the invocation can supply even more
arguments", the reader wouldn't be writing such a command line to
begin with, no?
So I find the example a bit suboptimal. Hopefully additional
explanation in patch 2/3 stressed on that point well enough with
much more stress than it talks about the implementation detail of
using "sh -c" and "$@", so that readers who read it would not even
dream of writing such an alias in the first place.
> diff --git a/run-command.c b/run-command.c
> index 1b821042b4..36b2b2f194 100644
> --- a/run-command.c
> +++ b/run-command.c
> @@ -435,6 +435,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
> }
> }
>
> + trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
> return 0;
> }
Nice addition that is obviously correct.
> diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
> index 95568342be..75c8763a6c 100755
> --- a/t/t0014-alias.sh
> +++ b/t/t0014-alias.sh
> @@ -44,4 +44,11 @@ test_expect_success 'run-command formats empty args properly' '
> test_cmp expect actual
> '
>
> +test_expect_success 'tracing a shell alias with arguments shows full prepared command' '
> + git config alias.echo "!echo \$*" &&
> + env GIT_TRACE=1 git echo argument 2>output &&
> + cp output /tmp/output &&
> + test_grep "^trace: prepare_cmd: /bin/sh -c '\''echo \$\* \"\$@\"" output
> +'
This is probably too specific search string, I suspect, given that
runcommand.c:prepare_shell_cmd() uses SHELL_PATH or "sh" so if your
SHELL_PATH is anything but /bin/sh (or if you are unlucky enough to
be running this test on Windows), the pattern would not match.
You'd want to loosen it a bit, perhaps with "/bin/sh" -> ".*", as
the rest of the output are expected to stay constant.
By the way, common to this step and also to the previous step,
you'd want to use
echo "$@"
instead of
echo $*
to encourage better variable reference hygiene. It makes difference
when any arguments given to "e" has whitespace, i.e.
$ sh -c 'echo $*' - a 'b c'
a b c
$ sh -c 'echo "$@"' - a 'b c'
a b c
Thanks.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v3 3/3] run-command: show prepared command
2024-05-23 15:29 ` Junio C Hamano
@ 2024-05-23 23:40 ` Junio C Hamano
2024-05-24 6:09 ` Junio C Hamano
2024-05-24 0:43 ` Ian Wienand
1 sibling, 1 reply; 43+ messages in thread
From: Junio C Hamano @ 2024-05-23 23:40 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Junio C Hamano <gitster@pobox.com> writes:
>> + git config alias.echo "!echo \$*" &&
>> + env GIT_TRACE=1 git echo argument 2>output &&
>> + cp output /tmp/output &&
>> + test_grep "^trace: prepare_cmd: /bin/sh -c '\''echo \$\* \"\$@\"" output
>> +'
>
> This is probably too specific search string, ...
And of course, 'seen' no longer passes the test with this topic
merged when run with SHELL_PATH=/bin/dash for obvious reasons.
I've added the following band-aid to help 'seen' pass CI, but this
has to be fixed in the main patch by squashing it in.
Thanks.
--- >8 ---
Subject: [PATCH] fixup! run-command: show prepared command
Do not assume everybody sets SHELL_PATH to /bin/sh
---
t/t0014-alias.sh | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 75c8763a6c..0d42d2b454 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -48,7 +48,8 @@ test_expect_success 'tracing a shell alias with arguments shows full prepared co
git config alias.echo "!echo \$*" &&
env GIT_TRACE=1 git echo argument 2>output &&
cp output /tmp/output &&
- test_grep "^trace: prepare_cmd: /bin/sh -c '\''echo \$\* \"\$@\"" output
+ # ".*" can be "sh", or SHELL_PATH (not always "/bin/sh")
+ test_grep "^trace: prepare_cmd: .* -c '\''echo \$\* \"\$@\"" output
'
test_done
--
2.45.1-246-gb9cfe4845c
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v3 3/3] run-command: show prepared command
2024-05-23 15:29 ` Junio C Hamano
2024-05-23 23:40 ` Junio C Hamano
@ 2024-05-24 0:43 ` Ian Wienand
2024-05-24 17:50 ` Junio C Hamano
1 sibling, 1 reply; 43+ messages in thread
From: Ian Wienand @ 2024-05-24 0:43 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
On Thu, May 23, 2024 at 08:29:21AM -0700, Junio C Hamano wrote:
> > For example, this can be very helpful when an alias is giving you an
> > unexpected syntax error that is very difficult figure out from only
> > the run_command trace point, e.g.
> >
> > test = "!for i in 1 2 3; do echo $i; done"
> >
> > will fail if there is an argument given, we can see why from the
> > output.
>
> ... if the reader truly understands "the alias gives the command and
> its leading arguments, to which the invocation can supply even more
> arguments", the reader wouldn't be writing such a command line to
> begin with, no?
>
> So I find the example a bit suboptimal. Hopefully additional
> explanation in patch 2/3 stressed on that point well enough with
> much more stress than it talks about the implementation detail of
> using "sh -c" and "$@", so that readers who read it would not even
> dream of writing such an alias in the first place.
Right; I was seeing this in a more convoluted way via our tool but
essentially the same issue. I was just looking for the simplest thing
that also gave the syntax error output, which I thought was something
people might search for (the "unexpected "$@" stuff).
Should I just leave as is?
> > +test_expect_success 'tracing a shell alias with arguments shows full prepared command' '
> > + git config alias.echo "!echo \$*" &&
> > + env GIT_TRACE=1 git echo argument 2>output &&
> > + cp output /tmp/output &&
> > + test_grep "^trace: prepare_cmd: /bin/sh -c '\''echo \$\* \"\$@\"" output
> > +'
>
> This is probably too specific search string, I suspect, given that
> runcommand.c:prepare_shell_cmd() uses SHELL_PATH or "sh" so if your
> SHELL_PATH is anything but /bin/sh (or if you are unlucky enough to
> be running this test on Windows), the pattern would not match.
> You'd want to loosen it a bit, perhaps with "/bin/sh" -> ".*", as
> the rest of the output are expected to stay constant.
OK, should this perhaps just look for '^trace: prepare_cmd.*'?
My initial thinking was to enforce seeing the "$@" appended, but
perhaps that is implementation details that don't really need to be
covered; the interesting thing is we show the person tracing the full
command as constructed, so this is useful just to ensure the
tracepoint remains in place?
-i
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v3 3/3] run-command: show prepared command
2024-05-23 23:40 ` Junio C Hamano
@ 2024-05-24 6:09 ` Junio C Hamano
2024-05-24 7:18 ` Ian Wienand
0 siblings, 1 reply; 43+ messages in thread
From: Junio C Hamano @ 2024-05-24 6:09 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Junio C Hamano <gitster@pobox.com> writes:
> Subject: [PATCH] fixup! run-command: show prepared command
>
> Do not assume everybody sets SHELL_PATH to /bin/sh
> ---
> t/t0014-alias.sh | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
> index 75c8763a6c..0d42d2b454 100755
> --- a/t/t0014-alias.sh
> +++ b/t/t0014-alias.sh
> @@ -48,7 +48,8 @@ test_expect_success 'tracing a shell alias with arguments shows full prepared co
> git config alias.echo "!echo \$*" &&
> env GIT_TRACE=1 git echo argument 2>output &&
> cp output /tmp/output &&
> - test_grep "^trace: prepare_cmd: /bin/sh -c '\''echo \$\* \"\$@\"" output
> + # ".*" can be "sh", or SHELL_PATH (not always "/bin/sh")
> + test_grep "^trace: prepare_cmd: .* -c '\''echo \$\* \"\$@\"" output
> '
So with this applied on top of the topic, 'seen' does pass with
SHELL_PATH set to say /bin/dash, but this still fails CI jobs on
Windows.
A sample run that failed (you'd probably need to be logged in as a
github user there):
https://github.com/git/git/actions/runs/9216303673/job/25360383492#step:5:237
With the lack of "trace: prepare_cmd:" in the "output" file, on the
platform, perhaps the code is failing to find an executable for
"echo" on the $PATH and taking the early return codepath that sets
errno to ENOENT and returns -1? Or it is that even prepare_cmd()
is not called? I do not do Windows, so I'll stop here.
Oh by the way, I just noticed that there is an apparent leftover
debugging statement that accesses outside the test playpen. When
you reroll the patch, please make sure that "cp output /tmp/output"
is removed.
Thanks.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v3 3/3] run-command: show prepared command
2024-05-24 6:09 ` Junio C Hamano
@ 2024-05-24 7:18 ` Ian Wienand
2024-05-24 15:33 ` Junio C Hamano
0 siblings, 1 reply; 43+ messages in thread
From: Ian Wienand @ 2024-05-24 7:18 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
On Thu, May 23, 2024 at 11:09:01PM -0700, Junio C Hamano wrote:
> So with this applied on top of the topic, 'seen' does pass with
> SHELL_PATH set to say /bin/dash, but this still fails CI jobs on
> Windows.
Sigh, it looks like windows uses prepare_shell_cmd() instead. I think
it's still probably valid to dump the full command there for the same
reasons, which I'll add in v4.
I've reduced to the check to just look for the trace prefix. This is
enough to ensure we don't regress by removing it; the exact details of
what comes out really isn't the main point -- it's that the log is
there at all to help you debug what is being run.
> Oh by the way, I just noticed that there is an apparent leftover
> debugging statement that accesses outside the test playpen. When
> you reroll the patch, please make sure that "cp output /tmp/output"
> is removed.
Done
-i
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v4 1/3] Documentation: alias: rework notes into points
2024-05-23 4:37 ` [PATCH v3 " Ian Wienand
` (2 preceding siblings ...)
2024-05-23 15:14 ` [PATCH v3 1/3] Documentation: alias: rework notes into points Junio C Hamano
@ 2024-05-24 7:32 ` Ian Wienand
2024-05-24 7:32 ` [PATCH v4 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
` (2 more replies)
3 siblings, 3 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-24 7:32 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
There are a number of caveats when using aliases. Rather than
stuffing them all together in a paragraph, let's separate them out
into individual points to make it clearer what's going on.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 01df96fab3..40851ef429 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -21,8 +21,9 @@ If the alias expansion is prefixed with an exclamation point,
it will be treated as a shell command. For example, defining
`alias.new = !gitk --all --not ORIG_HEAD`, the invocation
`git new` is equivalent to running the shell command
-`gitk --all --not ORIG_HEAD`. Note that shell commands will be
-executed from the top-level directory of a repository, which may
-not necessarily be the current directory.
-`GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
-from the original current directory. See linkgit:git-rev-parse[1].
+`gitk --all --not ORIG_HEAD`. Note:
++
+* Shell commands will be executed from the top-level directory of a
+ repository, which may not necessarily be the current directory.
+* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
+ from the original current directory. See linkgit:git-rev-parse[1].
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v4 2/3] Documentation: alias: add notes on shell expansion
2024-05-24 7:32 ` [PATCH v4 " Ian Wienand
@ 2024-05-24 7:32 ` Ian Wienand
2024-05-24 7:32 ` [PATCH v4 3/3] run-command: show prepared command Ian Wienand
2024-05-25 1:20 ` [PATCH v5 1/3] Documentation: alias: rework notes into points Ian Wienand
2 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-24 7:32 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
When writing inline shell for shell-expansion aliases (i.e. prefixed
with "!"), there are some caveats around argument parsing to be aware
of. This series of notes attempts to explain what is happening more
clearly.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 21 +++++++++++++++++++++
1 file changed, 21 insertions(+)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 40851ef429..51fa876c91 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -27,3 +27,24 @@ it will be treated as a shell command. For example, defining
repository, which may not necessarily be the current directory.
* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
from the original current directory. See linkgit:git-rev-parse[1].
+* If the shell alias is the full path to a binary, it will be executed
+ directly with any arguments.
+* If the alias contains any whitespace or reserved characters, it will
+ be considered an inline script and run as an argument to `sh -c`.
+* When running as a script, Git appends "$@" to the alias shell
+ command when arguments are present. If there are no arguments,
+ `"$@"` will not be appended.
+** This may initially be confusing if your alias script references
+ argument variables, or is otherwise not expecting the presence of
+ `"$@"`. For example: `alias.echo = "!echo $1"` when run as `git
+ echo arg` will actually execute `sh -c "echo $1 $@" "echo $1"
+ "arg"` resulting in output `arg arg`. An alias `alias.for = "!for
+ i in 1 2 3; do echo $i; done"` will fail if any arguments are
+ specified to `git for` as the appended `"$@"` will create invalid
+ shell syntax.
+** A convenient way to deal with this is to write your script
+ operations in an inline function that is then called with any
+ arguments from the command-line. For example `alias.echo = "!e() {
+ echo $* ; }; e" will work as expected, with the function `e`
+ receiving any arugments from the command-line.
+** Setting `GIT_TRACE=1` can help debug the command being run.
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v4 3/3] run-command: show prepared command
2024-05-24 7:32 ` [PATCH v4 " Ian Wienand
2024-05-24 7:32 ` [PATCH v4 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
@ 2024-05-24 7:32 ` Ian Wienand
2024-05-24 19:16 ` Junio C Hamano
2024-05-25 1:20 ` [PATCH v5 1/3] Documentation: alias: rework notes into points Ian Wienand
2 siblings, 1 reply; 43+ messages in thread
From: Ian Wienand @ 2024-05-24 7:32 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
This adds a trace point in prepare_cmd (for windows,
prepare_shell_cmd), so we can see the actual command being run without
having to resort to strace/code inspection.
e.g. "test = !echo" when run under GIT_TRACE will show:
$ GIT_TRACE=1 git test hello
10:58:56.877234 git.c:755 trace: exec: git-test hello
10:58:56.877382 run-command.c:657 trace: run_command: git-test hello
10:58:56.878655 run-command.c:657 trace: run_command: echo hello
10:58:56.878747 run-command.c:437 trace: prepare_cmd: /usr/bin/echo hello
hello
A "split" alias, e.g. test = "!echo $*" will show the shell wrapping
and appending of "$@".
$ GIT_TRACE=1 git test hello
11:00:45.959420 git.c:755 trace: exec: git-test hello
11:00:45.959737 run-command.c:657 trace: run_command: git-test hello
11:00:45.961424 run-command.c:657 trace: run_command: 'echo $*' hello
11:00:45.961528 run-command.c:437 trace: prepare_cmd: /bin/sh -c 'echo $* "$@"' 'echo $*' hello
hello hello
Prior changes have made the documentation around the internals of the
alias command execution clearer, but I have still found this detailed
view of the aliased command being run helpful for debugging purposes.
A test case is added. This simply looks for the trace point output;
the details change depending on platform -- the important thing is
that we have a verbose log point, not so much the details of what
happens on each platform.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
run-command.c | 2 ++
t/t0014-alias.sh | 6 ++++++
2 files changed, 8 insertions(+)
diff --git a/run-command.c b/run-command.c
index 1b821042b4..13e35fb76e 100644
--- a/run-command.c
+++ b/run-command.c
@@ -296,6 +296,7 @@ static const char **prepare_shell_cmd(struct strvec *out, const char **argv)
}
strvec_pushv(out, argv);
+ trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
return out->v;
}
@@ -435,6 +436,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
}
}
+ trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
return 0;
}
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 95568342be..b7affbe93a 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -44,4 +44,10 @@ test_expect_success 'run-command formats empty args properly' '
test_cmp expect actual
'
+test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' '
+ git config alias.echo "!echo \$*" &&
+ env GIT_TRACE=1 git echo argument 2>output &&
+ test_grep "^trace: prepare_cmd:.*" output
+'
+
test_done
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v3 3/3] run-command: show prepared command
2024-05-24 7:18 ` Ian Wienand
@ 2024-05-24 15:33 ` Junio C Hamano
0 siblings, 0 replies; 43+ messages in thread
From: Junio C Hamano @ 2024-05-24 15:33 UTC (permalink / raw)
To: Ian Wienand; +Cc: git, Johannes Sixt, Johannes Schindelin
Ian Wienand <iwienand@redhat.com> writes:
> On Thu, May 23, 2024 at 11:09:01PM -0700, Junio C Hamano wrote:
>> So with this applied on top of the topic, 'seen' does pass with
>> SHELL_PATH set to say /bin/dash, but this still fails CI jobs on
>> Windows.
>
> Sigh, it looks like windows uses prepare_shell_cmd() instead. I think
> it's still probably valid to dump the full command there for the same
> reasons, which I'll add in v4.
Ahh, it's the large conditional compilation in start_command().
On non-Windows side, we just do
if (prepare_cmd(&argv, cmd) < 0) {
failed_errno = errno;
...
goto end_of_spawn;
}
... pipe(), fork(), and exec() ...
but on Windows side, we have
if (cmd->git_cmd)
cmd->args.v = prepare_git_cmd(&nargv, sargv);
else if (cmd->use_shell)
cmd->args.v = prepare_shell_cmd(&nargv, sargv);
cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v,
(char**) cmd->env.v,
cmd->dir, fhin, fhout, fherr);
And the thing is, prepare_cmd() already has
if (cmd->git_cmd) {
prepare_git_cmd(out, cmd->args.v);
} else if (cmd->use_shell) {
prepare_shell_cmd(out, cmd->args.v);
} else {
strvec_pushv(out, cmd->args.v);
}
I am wondering (and not suggesting to do this as a part of this
series, but to decide if we should leave a left-over-bits note here)
if the Windows side can be updated to also use prepare_cmd(). Do
folks a lot more familiar with Windows than I (cc'ed j6t and dscho)
have comments on this?
Anyway.
If you unconditionally add the printf's to both prepare_cmd() and
prepare_shell_cmd(), you'll see two printf output on a non-Windows
platform if we are spawning a shell cmd.
It appears that the best place to "show the prepared command" is
*NOT* in any of these prepare_* helper functions, but after these
two callers in start_command() receives the prepared command from
these helpers, namely:
diff --git c/run-command.c w/run-command.c
index 1b821042b4..9d0fa6afe4 100644
--- c/run-command.c
+++ w/run-command.c
@@ -745,6 +745,7 @@ int start_command(struct child_process *cmd)
error_errno("cannot run %s", cmd->args.v[0]);
goto end_of_spawn;
}
+ /* Here show argv in the trace */
if (pipe(notify_pipe))
notify_pipe[0] = notify_pipe[1] = -1;
@@ -912,6 +913,7 @@ int start_command(struct child_process *cmd)
cmd->args.v = prepare_git_cmd(&nargv, sargv);
else if (cmd->use_shell)
cmd->args.v = prepare_shell_cmd(&nargv, sargv);
+ /* Here show cmd->args.v in the trace */
cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v,
(char**) cmd->env.v,
We would not have to do so and instead have trace-printf(s) only in
prepare_cmd() if/when the Windows side is updated to stop switching
between the prepare_git/prepare_shell and instead let prepare_cmd()
do the switching, (which may require an update to prepare_cmd() if
needed). But until that happens, logging at the caller seems to be
the best approach among equally bad ones.
Thanks.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v3 3/3] run-command: show prepared command
2024-05-24 0:43 ` Ian Wienand
@ 2024-05-24 17:50 ` Junio C Hamano
2024-05-25 1:13 ` Ian Wienand
0 siblings, 1 reply; 43+ messages in thread
From: Junio C Hamano @ 2024-05-24 17:50 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> On Thu, May 23, 2024 at 08:29:21AM -0700, Junio C Hamano wrote:
>> ... if the reader truly understands "the alias gives the command and
>> its leading arguments, to which the invocation can supply even more
>> arguments", the reader wouldn't be writing such a command line to
>> begin with, no?
>>
>> So I find the example a bit suboptimal. Hopefully additional
>> explanation in patch 2/3 stressed on that point well enough with
>> much more stress than it talks about the implementation detail of
>> using "sh -c" and "$@", so that readers who read it would not even
>> dream of writing such an alias in the first place.
>
> Right; I was seeing this in a more convoluted way via our tool but
> essentially the same issue. I was just looking for the simplest thing
> that also gave the syntax error output, which I thought was something
> people might search for (the "unexpected "$@" stuff).
>
> Should I just leave as is?
If I found as-is would be good enough, I wouldn't have been
commenting on this.
Even in this third iteration, I still didn't see the added
documentation talk about the principle behind the design, i.e. what
you write after the "git your-alias" are appended to the command
line to be used as additional arguments.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v4 3/3] run-command: show prepared command
2024-05-24 7:32 ` [PATCH v4 3/3] run-command: show prepared command Ian Wienand
@ 2024-05-24 19:16 ` Junio C Hamano
2024-05-24 19:58 ` Junio C Hamano
2024-05-25 1:14 ` Ian Wienand
0 siblings, 2 replies; 43+ messages in thread
From: Junio C Hamano @ 2024-05-24 19:16 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> +test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' '
> + git config alias.echo "!echo \$*" &&
> + env GIT_TRACE=1 git echo argument 2>output &&
> + test_grep "^trace: prepare_cmd:.*" output
> +'
If you run
$ cd t && sh ./t0014-alias.sh -d && cat trash*.t0014-alias/output
you'll see two "prepare_cmd" logged, because you added one to
prepare_shell_cmd() and another to prepare_cmd(). If you were
tracing something that uses prepare_git_cmd() on Windows, you would
not see any trace output, on the other hand, I would suspect (I do
not do Windows so this is from code inspection only).
Perhaps squashing something like this is sufficient?
run-command.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git c/run-command.c w/run-command.c
index 13e35fb76e..7eb68a541d 100644
--- c/run-command.c
+++ w/run-command.c
@@ -296,7 +296,6 @@ static const char **prepare_shell_cmd(struct strvec *out, const char **argv)
}
strvec_pushv(out, argv);
- trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
return out->v;
}
@@ -436,7 +435,6 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
}
}
- trace_argv_printf(&out->v[1], "trace: prepare_cmd:");
return 0;
}
@@ -747,6 +745,7 @@ int start_command(struct child_process *cmd)
error_errno("cannot run %s", cmd->args.v[0]);
goto end_of_spawn;
}
+ trace_argv_printf(&argv.v[1], "trace: prepare_cmd:");
if (pipe(notify_pipe))
notify_pipe[0] = notify_pipe[1] = -1;
@@ -914,6 +913,7 @@ int start_command(struct child_process *cmd)
cmd->args.v = prepare_git_cmd(&nargv, sargv);
else if (cmd->use_shell)
cmd->args.v = prepare_shell_cmd(&nargv, sargv);
+ trace_argv_printf(&cmd->args.v[1], "trace: prepare_cmd:");
cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v,
(char**) cmd->env.v,
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v4 3/3] run-command: show prepared command
2024-05-24 19:16 ` Junio C Hamano
@ 2024-05-24 19:58 ` Junio C Hamano
2024-05-25 1:14 ` Ian Wienand
1 sibling, 0 replies; 43+ messages in thread
From: Junio C Hamano @ 2024-05-24 19:58 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Junio C Hamano <gitster@pobox.com> writes:
>> + test_grep "^trace: prepare_cmd:.*" output
>> +'
>
> If you run
>
> $ cd t && sh ./t0014-alias.sh -d && cat trash*.t0014-alias/output
>
> you'll see two "prepare_cmd" logged, because you added one to
> prepare_shell_cmd() and another to prepare_cmd().
To avoid similar issues, perhaps the test can try to see a bit more
into the output? We already know that we cannot expect the output
to be identical everywhere, but we can do something along the lines
of the attached patch, or is the expected pattern below a bit too
specific?
t/t0014-alias.sh | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)
diff --git c/t/t0014-alias.sh w/t/t0014-alias.sh
index b7affbe93a..a3c133be96 100755
--- c/t/t0014-alias.sh
+++ w/t/t0014-alias.sh
@@ -45,9 +45,17 @@ test_expect_success 'run-command formats empty args properly' '
'
test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' '
+ cat >expect <<-EOF &&
+ trace: exec: git-echo argument
+ trace: run_command: git-echo argument
+ trace: run_command: ${SQ}echo \$*${SQ} argument
+ trace: prepare_cmd: SHELL -c ${SQ}echo \$* "\$@"${SQ} ${SQ}echo \$*${SQ} argument
+ EOF
git config alias.echo "!echo \$*" &&
env GIT_TRACE=1 git echo argument 2>output &&
- test_grep "^trace: prepare_cmd:.*" output
+ # redact platform differences
+ sed -e "s/^\(trace: prepare_cmd:\) .* -c /\1 SHELL -c /" output >actual &&
+ test_cmp expect actual
'
test_done
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v3 3/3] run-command: show prepared command
2024-05-24 17:50 ` Junio C Hamano
@ 2024-05-25 1:13 ` Ian Wienand
0 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-25 1:13 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
On Fri, May 24, 2024 at 10:50:33AM -0700, Junio C Hamano wrote:
> > Should I just leave as is?
>
> If I found as-is would be good enough, I wouldn't have been
> commenting on this.
I think this "for ... done" example from me is perhaps too specific
and weird, and not being helpful. I'll trim all this out as I think
the point can be made clearer.
> Even in this third iteration, I still didn't see the added
> documentation talk about the principle behind the design, i.e. what
> you write after the "git your-alias" are appended to the command
> line to be used as additional arguments.
I'll send an update that makes it clearer in a separate point that for
simple arguments, the added "$@" just passes through arguments as
additional positional arguments.
-i
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v4 3/3] run-command: show prepared command
2024-05-24 19:16 ` Junio C Hamano
2024-05-24 19:58 ` Junio C Hamano
@ 2024-05-25 1:14 ` Ian Wienand
1 sibling, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-25 1:14 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
On Fri, May 24, 2024 at 12:16:34PM -0700, Junio C Hamano wrote:
> Ian Wienand <iwienand@redhat.com> writes:
>
> > +test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' '
> > + git config alias.echo "!echo \$*" &&
> > + env GIT_TRACE=1 git echo argument 2>output &&
> > + test_grep "^trace: prepare_cmd:.*" output
> > +'
>
> If you run
>
> $ cd t && sh ./t0014-alias.sh -d && cat trash*.t0014-alias/output
>
> you'll see two "prepare_cmd" logged, because you added one to
> prepare_shell_cmd() and another to prepare_cmd().
Sorry that's a bit embarrasing that I missed that. Reading this more
carefully, I agree that the points you've identified in start_command
are probably the best place. I've updated to this.
-i
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v5 1/3] Documentation: alias: rework notes into points
2024-05-24 7:32 ` [PATCH v4 " Ian Wienand
2024-05-24 7:32 ` [PATCH v4 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-24 7:32 ` [PATCH v4 3/3] run-command: show prepared command Ian Wienand
@ 2024-05-25 1:20 ` Ian Wienand
2024-05-25 1:20 ` [PATCH v5 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
` (2 more replies)
2 siblings, 3 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-25 1:20 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
There are a number of caveats when using aliases. Rather than
stuffing them all together in a paragraph, let's separate them out
into individual points to make it clearer what's going on.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 01df96fab3..40851ef429 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -21,8 +21,9 @@ If the alias expansion is prefixed with an exclamation point,
it will be treated as a shell command. For example, defining
`alias.new = !gitk --all --not ORIG_HEAD`, the invocation
`git new` is equivalent to running the shell command
-`gitk --all --not ORIG_HEAD`. Note that shell commands will be
-executed from the top-level directory of a repository, which may
-not necessarily be the current directory.
-`GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
-from the original current directory. See linkgit:git-rev-parse[1].
+`gitk --all --not ORIG_HEAD`. Note:
++
+* Shell commands will be executed from the top-level directory of a
+ repository, which may not necessarily be the current directory.
+* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
+ from the original current directory. See linkgit:git-rev-parse[1].
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v5 2/3] Documentation: alias: add notes on shell expansion
2024-05-25 1:20 ` [PATCH v5 1/3] Documentation: alias: rework notes into points Ian Wienand
@ 2024-05-25 1:20 ` Ian Wienand
2024-05-25 1:20 ` [PATCH v5 3/3] run-command: show prepared command Ian Wienand
2024-05-25 23:44 ` [PATCH v6 1/3] Documentation: alias: rework notes into points Ian Wienand
2 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-25 1:20 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
When writing inline shell for shell-expansion aliases (i.e. prefixed
with "!"), there are some caveats around argument parsing to be aware
of. This series of notes attempts to explain what is happening more
clearly.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 40851ef429..f32b86cde3 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -27,3 +27,31 @@ it will be treated as a shell command. For example, defining
repository, which may not necessarily be the current directory.
* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
from the original current directory. See linkgit:git-rev-parse[1].
+* If the shell alias is the full path to a binary, it will be executed
+ directly with any arguments as positional arguments.
+* If the alias contains any white-space or reserved characters, it
+ will be considered an inline script and run as an argument to `sh
+ -c`.
+* When running as a script, if arguments are provided to the alias
+ call, Git makes them available to the process by appending "$@" to
+ the alias shell command. This is not appended if arguments are not
+ provided.
+** For "simple" commands, such as calling a single binary
+ (e.g. `alias.myapp = !myapp --myflag1`) this will result in any
+ arguments becoming additional regular positional arguments to the
+ called binary, appended after any arguments specified in the aliased
+ command.
+** Care should be taken if your alias script has multiple commands
+ (e.g. in a pipeline), references argument variables, or is
+ otherwise not expecting the presence of the appended `"$@"`. For
+ example: `alias.echo = "!echo $1"` when run as `git echo arg` will
+ actually execute `sh -c "echo $1 $@" "echo $1" "arg"` resulting in
+ output `arg arg`. When writing such aliases, you should ensure
+ that the appended "$@" when arguments are present does not cause
+ syntax errors or unintended side-effects.
+** A convenient way to deal with this is to write your script
+ operations in an inline function that is then called with any
+ arguments from the command-line. For example `alias.cmd = "!c() {
+ cmd $1 | cmd $2 ; }; c" will allow you to work with separate
+ arguments.
+** Setting `GIT_TRACE=1` can help debug the command being run.
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v5 3/3] run-command: show prepared command
2024-05-25 1:20 ` [PATCH v5 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-25 1:20 ` [PATCH v5 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
@ 2024-05-25 1:20 ` Ian Wienand
2024-05-25 5:44 ` Junio C Hamano
2024-05-25 6:06 ` Junio C Hamano
2024-05-25 23:44 ` [PATCH v6 1/3] Documentation: alias: rework notes into points Ian Wienand
2 siblings, 2 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-25 1:20 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
This adds a trace point in start_command so we can see the full
command invocation without having to resort to strace/code inspection.
For example:
$ GIT_TRACE=1 git test foo
git.c:755 trace: exec: git-test foo
run-command.c:657 trace: run_command: git-test foo
run-command.c:657 trace: run_command: 'echo $*' foo
run-command.c:749 trace: start_command: /bin/sh -c 'echo $* "$@"' 'echo $*' foo
Prior changes have made the documentation around the internals of the
alias command execution clearer, but I have still found this detailed
view of the aliased command being run helpful for debugging purposes.
A test case is added. This simply looks for the trace point output;
the details change depending on platform -- the important thing is
that we have a verbose log point, not so much the details of what
happens on each platform.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
run-command.c | 3 +++
t/t0014-alias.sh | 6 ++++++
2 files changed, 9 insertions(+)
diff --git a/run-command.c b/run-command.c
index 1b821042b4..9892c4421c 100644
--- a/run-command.c
+++ b/run-command.c
@@ -746,6 +746,8 @@ int start_command(struct child_process *cmd)
goto end_of_spawn;
}
+ trace_argv_printf(&argv.v[1], "trace: start_command:");
+
if (pipe(notify_pipe))
notify_pipe[0] = notify_pipe[1] = -1;
@@ -913,6 +915,7 @@ int start_command(struct child_process *cmd)
else if (cmd->use_shell)
cmd->args.v = prepare_shell_cmd(&nargv, sargv);
+ trace_argv_printf(&cmd->args.v[1], "trace: start_command:");
cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v,
(char**) cmd->env.v,
cmd->dir, fhin, fhout, fherr);
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 95568342be..db747950c7 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -44,4 +44,10 @@ test_expect_success 'run-command formats empty args properly' '
test_cmp expect actual
'
+test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' '
+ git config alias.echo "!echo \$*" &&
+ env GIT_TRACE=1 git echo argument 2>output &&
+ test_grep "^trace: start_command:.*" output
+'
+
test_done
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v5 3/3] run-command: show prepared command
2024-05-25 1:20 ` [PATCH v5 3/3] run-command: show prepared command Ian Wienand
@ 2024-05-25 5:44 ` Junio C Hamano
2024-05-25 6:06 ` Junio C Hamano
1 sibling, 0 replies; 43+ messages in thread
From: Junio C Hamano @ 2024-05-25 5:44 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> diff --git a/run-command.c b/run-command.c
> index 1b821042b4..9892c4421c 100644
> --- a/run-command.c
> +++ b/run-command.c
> @@ -746,6 +746,8 @@ int start_command(struct child_process *cmd)
> goto end_of_spawn;
> }
>
> + trace_argv_printf(&argv.v[1], "trace: start_command:");
> +
> if (pipe(notify_pipe))
> notify_pipe[0] = notify_pipe[1] = -1;
This side is OK ...
> @@ -913,6 +915,7 @@ int start_command(struct child_process *cmd)
> else if (cmd->use_shell)
> cmd->args.v = prepare_shell_cmd(&nargv, sargv);
>
> + trace_argv_printf(&cmd->args.v[1], "trace: start_command:");
> cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v,
> (char**) cmd->env.v,
> cmd->dir, fhin, fhout, fherr);
... but this side should pass "cmd->args.v" (i.e., the entire array,
without omitting the zeroth element) to be consistent with the other
side. I made the same mistake in my "how about doing it this way"
draft, by the way.
It is because prepare_cmd() does this:
static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
{
...
/*
* Add SHELL_PATH so in the event exec fails with ENOEXEC we can
* attempt to interpret the command with 'sh'.
*/
strvec_push(out, SHELL_PATH);
if (cmd->git_cmd) {
prepare_git_cmd(out, cmd->args.v);
} else if (cmd->use_shell) {
prepare_shell_cmd(out, cmd->args.v);
...
So, the other side (i.e. non Windows, that used the result from
prepare_cmd()) skips the argv.v[0] element (which is the SHELL_PATH
pushed by prepare_cmd()), but because a bare use of
prepare_shell_cmd() done on the Windows side does not have that
excess element at the beginning.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v5 3/3] run-command: show prepared command
2024-05-25 1:20 ` [PATCH v5 3/3] run-command: show prepared command Ian Wienand
2024-05-25 5:44 ` Junio C Hamano
@ 2024-05-25 6:06 ` Junio C Hamano
2024-05-25 23:49 ` Ian Wienand
1 sibling, 1 reply; 43+ messages in thread
From: Junio C Hamano @ 2024-05-25 6:06 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> +test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' '
> + git config alias.echo "!echo \$*" &&
> + env GIT_TRACE=1 git echo argument 2>output &&
> + test_grep "^trace: start_command:.*" output
> +'
This will succeed even if you have two or more instances of this
log, which will not help you avoid the mistake we saw in an earlier
iteration.
How about making sure that the prepared command looks reasonable
enough and we have only one, by doing something like this?
test_expect_success 'tracing a shell alias with arg shows trace of prepared command' '
cat >expect <<-EOF &&
trace: prepare_cmd: SHELL -c ${SQ}echo \$* "\$@"${SQ} ${SQ}echo \$*${SQ} arg
EOF
git config alias.echo "!echo \$*" &&
env GIT_TRACE=1 git echo arg 2>output &&
# redact platform differences
sed -n -e "s/^\(trace: prepare_cmd:\) .* -c /\1 SHELL -c /p" output >actual &&
test_cmp expect actual
'
A sample run of CI job with the above change (and others I sent on
the list) can be seen at:
https://github.com/git/git/actions/runs/9233329671
You can fetch 'seen' and can find commit 05ebf54569, which is the
tip of your topic including a few "fixup" commits I made on top.
$ git log --decorate --oneline master..05ebf54569
05ebf54569 (iw/trace-argv-on-alias) fixup! run-command: show prepared command
11056975bb fixup! run-command: show prepared command
770dda8f2b run-command: show prepared command
242b9d4d63 Documentation: alias: add notes on shell expansion
e4331ad0d4 Documentation: alias: rework notes into points
It hasn't finished running yet, but all the windows tests have
passed, so I'll happily go to bed now ;-)
Thanks.
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v6 1/3] Documentation: alias: rework notes into points
2024-05-25 1:20 ` [PATCH v5 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-25 1:20 ` [PATCH v5 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-25 1:20 ` [PATCH v5 3/3] run-command: show prepared command Ian Wienand
@ 2024-05-25 23:44 ` Ian Wienand
2024-05-25 23:44 ` [PATCH v6 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
` (2 more replies)
2 siblings, 3 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-25 23:44 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
There are a number of caveats when using aliases. Rather than
stuffing them all together in a paragraph, let's separate them out
into individual points to make it clearer what's going on.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 01df96fab3..40851ef429 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -21,8 +21,9 @@ If the alias expansion is prefixed with an exclamation point,
it will be treated as a shell command. For example, defining
`alias.new = !gitk --all --not ORIG_HEAD`, the invocation
`git new` is equivalent to running the shell command
-`gitk --all --not ORIG_HEAD`. Note that shell commands will be
-executed from the top-level directory of a repository, which may
-not necessarily be the current directory.
-`GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
-from the original current directory. See linkgit:git-rev-parse[1].
+`gitk --all --not ORIG_HEAD`. Note:
++
+* Shell commands will be executed from the top-level directory of a
+ repository, which may not necessarily be the current directory.
+* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
+ from the original current directory. See linkgit:git-rev-parse[1].
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 2/3] Documentation: alias: add notes on shell expansion
2024-05-25 23:44 ` [PATCH v6 1/3] Documentation: alias: rework notes into points Ian Wienand
@ 2024-05-25 23:44 ` Ian Wienand
2024-05-26 23:26 ` Junio C Hamano
2024-05-25 23:44 ` [PATCH v6 3/3] run-command: show prepared command Ian Wienand
2024-05-27 0:30 ` [PATCH v7 1/3] Documentation: alias: rework notes into points Ian Wienand
2 siblings, 1 reply; 43+ messages in thread
From: Ian Wienand @ 2024-05-25 23:44 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
When writing inline shell for shell-expansion aliases (i.e. prefixed
with "!"), there are some caveats around argument parsing to be aware
of. This series of notes attempts to explain what is happening more
clearly.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 40851ef429..f32b86cde3 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -27,3 +27,31 @@ it will be treated as a shell command. For example, defining
repository, which may not necessarily be the current directory.
* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
from the original current directory. See linkgit:git-rev-parse[1].
+* If the shell alias is the full path to a binary, it will be executed
+ directly with any arguments as positional arguments.
+* If the alias contains any white-space or reserved characters, it
+ will be considered an inline script and run as an argument to `sh
+ -c`.
+* When running as a script, if arguments are provided to the alias
+ call, Git makes them available to the process by appending "$@" to
+ the alias shell command. This is not appended if arguments are not
+ provided.
+** For "simple" commands, such as calling a single binary
+ (e.g. `alias.myapp = !myapp --myflag1`) this will result in any
+ arguments becoming additional regular positional arguments to the
+ called binary, appended after any arguments specified in the aliased
+ command.
+** Care should be taken if your alias script has multiple commands
+ (e.g. in a pipeline), references argument variables, or is
+ otherwise not expecting the presence of the appended `"$@"`. For
+ example: `alias.echo = "!echo $1"` when run as `git echo arg` will
+ actually execute `sh -c "echo $1 $@" "echo $1" "arg"` resulting in
+ output `arg arg`. When writing such aliases, you should ensure
+ that the appended "$@" when arguments are present does not cause
+ syntax errors or unintended side-effects.
+** A convenient way to deal with this is to write your script
+ operations in an inline function that is then called with any
+ arguments from the command-line. For example `alias.cmd = "!c() {
+ cmd $1 | cmd $2 ; }; c" will allow you to work with separate
+ arguments.
+** Setting `GIT_TRACE=1` can help debug the command being run.
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v6 3/3] run-command: show prepared command
2024-05-25 23:44 ` [PATCH v6 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-25 23:44 ` [PATCH v6 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
@ 2024-05-25 23:44 ` Ian Wienand
2024-05-26 16:20 ` Junio C Hamano
2024-05-27 0:30 ` [PATCH v7 1/3] Documentation: alias: rework notes into points Ian Wienand
2 siblings, 1 reply; 43+ messages in thread
From: Ian Wienand @ 2024-05-25 23:44 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
This adds a trace point in start_command so we can see the full
command invocation without having to resort to strace/code inspection.
For example:
$ GIT_TRACE=1 git test foo
git.c:755 trace: exec: git-test foo
run-command.c:657 trace: run_command: git-test foo
run-command.c:657 trace: run_command: 'echo $*' foo
run-command.c:749 trace: start_command: /bin/sh -c 'echo $* "$@"' 'echo $*' foo
Prior changes have made the documentation around the internals of the
alias command execution clearer, but I have still found this detailed
view of the aliased command being run helpful for debugging purposes.
A test case is added. This simply looks for the trace point output;
the details change depending on platform -- the important thing is
that we have a verbose log point, not so much the details of what
happens on each platform.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
run-command.c | 3 +++
t/t0014-alias.sh | 12 ++++++++++++
2 files changed, 15 insertions(+)
diff --git a/run-command.c b/run-command.c
index 1b821042b4..31b20123d8 100644
--- a/run-command.c
+++ b/run-command.c
@@ -746,6 +746,8 @@ int start_command(struct child_process *cmd)
goto end_of_spawn;
}
+ trace_argv_printf(&argv.v[1], "trace: start_command:");
+
if (pipe(notify_pipe))
notify_pipe[0] = notify_pipe[1] = -1;
@@ -913,6 +915,7 @@ int start_command(struct child_process *cmd)
else if (cmd->use_shell)
cmd->args.v = prepare_shell_cmd(&nargv, sargv);
+ trace_argv_printf(cmd->args.v, "trace: start_command:");
cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v,
(char**) cmd->env.v,
cmd->dir, fhin, fhout, fherr);
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 95568342be..634f6d78ef 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -44,4 +44,16 @@ test_expect_success 'run-command formats empty args properly' '
test_cmp expect actual
'
+test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' '
+ cat >expect <<-EOF &&
+ trace: start_command: SHELL -c ${SQ}echo \$* "\$@"${SQ} ${SQ}echo \$*${SQ} arg
+ EOF
+ git config alias.echo "!echo \$*" &&
+ env GIT_TRACE=1 git echo arg 2>output &&
+ # redact platform differences
+ cat output &&
+ sed -n -e "s/^\(trace: start_command:\) .* -c /\1 SHELL -c /p" output >actual &&
+ test_cmp expect actual
+'
+
test_done
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v5 3/3] run-command: show prepared command
2024-05-25 6:06 ` Junio C Hamano
@ 2024-05-25 23:49 ` Ian Wienand
0 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-25 23:49 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
On Fri, May 24, 2024 at 11:06:14PM -0700, Junio C Hamano wrote:
> A sample run of CI job with the above change (and others I sent on
> the list) can be seen at:
>
> https://github.com/git/git/actions/runs/9233329671
Thanks for this; I had in my head that the pipeline wouldn't run in a
personal fork, but after I enabled it, it does ... I don't know who's
ultimately paying for it but I'll take it :)
https://github.com/ianw/git/actions/runs/9238552513
is for the v6 version; I'll make sure to run it through that from now
on!
-i
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 3/3] run-command: show prepared command
2024-05-25 23:44 ` [PATCH v6 3/3] run-command: show prepared command Ian Wienand
@ 2024-05-26 16:20 ` Junio C Hamano
0 siblings, 0 replies; 43+ messages in thread
From: Junio C Hamano @ 2024-05-26 16:20 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> This adds a trace point in start_command so we can see the full
> command invocation without having to resort to strace/code inspection.
> For example:
>
> $ GIT_TRACE=1 git test foo
> git.c:755 trace: exec: git-test foo
> run-command.c:657 trace: run_command: git-test foo
> run-command.c:657 trace: run_command: 'echo $*' foo
> run-command.c:749 trace: start_command: /bin/sh -c 'echo $* "$@"' 'echo $*' foo
>
> Prior changes have made the documentation around the internals of the
> alias command execution clearer, but I have still found this detailed
> view of the aliased command being run helpful for debugging purposes.
>
> A test case is added. This simply looks for the trace point output;
> the details change depending on platform -- the important thing is
> that we have a verbose log point, not so much the details of what
> happens on each platform.
>
> Signed-off-by: Ian Wienand <iwienand@redhat.com>
> ---
> run-command.c | 3 +++
> t/t0014-alias.sh | 12 ++++++++++++
> 2 files changed, 15 insertions(+)
>
> diff --git a/run-command.c b/run-command.c
> index 1b821042b4..31b20123d8 100644
> --- a/run-command.c
> +++ b/run-command.c
> @@ -746,6 +746,8 @@ int start_command(struct child_process *cmd)
> goto end_of_spawn;
> }
>
> + trace_argv_printf(&argv.v[1], "trace: start_command:");
> +
> if (pipe(notify_pipe))
> notify_pipe[0] = notify_pipe[1] = -1;
>
> @@ -913,6 +915,7 @@ int start_command(struct child_process *cmd)
> else if (cmd->use_shell)
> cmd->args.v = prepare_shell_cmd(&nargv, sargv);
>
> + trace_argv_printf(cmd->args.v, "trace: start_command:");
> cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v,
> (char**) cmd->env.v,
> cmd->dir, fhin, fhout, fherr);
> diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
> index 95568342be..634f6d78ef 100755
> --- a/t/t0014-alias.sh
> +++ b/t/t0014-alias.sh
> @@ -44,4 +44,16 @@ test_expect_success 'run-command formats empty args properly' '
> test_cmp expect actual
> '
>
> +test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' '
> + cat >expect <<-EOF &&
> + trace: start_command: SHELL -c ${SQ}echo \$* "\$@"${SQ} ${SQ}echo \$*${SQ} arg
> + EOF
> + git config alias.echo "!echo \$*" &&
> + env GIT_TRACE=1 git echo arg 2>output &&
> + # redact platform differences
> + cat output &&
That "cat" is not about "redact"ing platform differences. Leftover
debugging? I'll remove it while queuing.
> + sed -n -e "s/^\(trace: start_command:\) .* -c /\1 SHELL -c /p" output >actual &&
> + test_cmp expect actual
> +'
Other than that, this step looks good to me.
Thanks.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 2/3] Documentation: alias: add notes on shell expansion
2024-05-25 23:44 ` [PATCH v6 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
@ 2024-05-26 23:26 ` Junio C Hamano
2024-05-27 0:22 ` Ian Wienand
0 siblings, 1 reply; 43+ messages in thread
From: Junio C Hamano @ 2024-05-26 23:26 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> +* If the shell alias is the full path to a binary, it will be executed
> + directly with any arguments as positional arguments.
> +* If the alias contains any white-space or reserved characters, it
> + will be considered an inline script and run as an argument to `sh
> + -c`.
> +* When running as a script, if arguments are provided to the alias
> + call, Git makes them available to the process by appending "$@" to
> + the alias shell command. This is not appended if arguments are not
> + provided.
These are not technically wrong per-se.
> +** For "simple" commands, such as calling a single binary
> + (e.g. `alias.myapp = !myapp --myflag1`) this will result in any
> + arguments becoming additional regular positional arguments to the
> + called binary, appended after any arguments specified in the aliased
> + command.
But the single-command script still receives the arguments in
argv[], so what the alias command has to do is the same. The
earlier ones that are "not technically wrong" are merely
implemenation detail.
In a single command case, e.g., "[alias] single = !one", you may
write
#!/bin/sh
echo "$1" | grep "$2"
in 'one' script, and "git single 1 2" will be turned into
start_command: one 1 2
i.e. one receives two arguments in argv[]. It is an implementation
detail that we can bypass "sh" or "-c" or "$@"
If you write exactly the same thing like
$ git -c 'alias=single=!one ' single 1 2
you'll instead see
start_command: /bin/sh -c 'one' "$@" "one " 1 2
because the trailing SP in the alias disables the optimization to
bypass a more generic 'sh -c ... "$@"' construction. What gets run
is an equivalent of the reader saying
$ /bin/sh -c 'one "$@"' "one " 1 2
bypassing git from the command line.
What the script (one) has to write does not change at all either
case.
As I keep saying over multiple iterations, the above three bullet
points stress too much on the minute implementation detail while
failing to tell readers that the end-user alias receives the rest of
the command line as arguments.
> +** Care should be taken if your alias script has multiple commands
> + (e.g. in a pipeline), references argument variables, or is
"argument variables" -> "arguments".
> + otherwise not expecting the presence of the appended `"$@"`.
"otherwise not expecting" is SIMPLY BUGGY but the readers may not
understand it unless you tell them that the arguments are fed to
their aliased command by appending them.
When you look at the implementation detail of "sh -c '... $@' -
$ARGS" as something to fight against, readers would lose sight to
see the crux of the problem they are trying to solve. I think it is
a wrong way to frame the issue. The problem readers are solving when
coming up with their own alias is this:
How would one write a single-liner that can take arguments
appended at the end?
I think giving that to the readers upfront, i.e. "when you write an
alias, you are forming a single-liner that takes arguments appended
at the end", would go a long way without having them lose sight in
the implementation details of "sometimes args directly come in
argv[], sometimes your alias is wrapped in "sh -c" and "$@" is used.
They do the same thing to feed the arguments to your script.
Going back that 'one' example, if 'echo "$1" | grep "$2"' was what
you wanted to run, how would you write a single-liner that does
echo "$1" | grep "$2"
and can take its arguments at the end? You do *not* want to see
your invocation of the alias
$ git single 1 2
turn into
$ echo "$1" | grep "$2" 1 2
of course, and remember, "$@" is merely an implementation detail
that the end-users do not need to see.
Of course the simplest one-liner, if you had the "one" script
already stored in the file, is to say
$ one 1 2
i.e. "[alias] single = !one". But calling that a "single-liner" is
cheating.
You can do one of two easy things.
$ sh -c 'echo "$1" | grep "$2"' - 1 2
$ e(){ echo "$1" | grep "$2"; };e 1 2
The earlier string (before "1 2" is appended) of either of these
gives you "a single-liner that takes arguments at the end" that does
the "echo the first one, pipe it to grep that looks for the second
one", which you would make the body of the alias. If the reader
understands the earlier example that stores it in a file, the former
is more mechanical and straight-forward rewrite of it. The latter
may be a bit more convoluted, but says the same thing in the same
number of letters.
HTH.
^ permalink raw reply [flat|nested] 43+ messages in thread
* Re: [PATCH v6 2/3] Documentation: alias: add notes on shell expansion
2024-05-26 23:26 ` Junio C Hamano
@ 2024-05-27 0:22 ` Ian Wienand
0 siblings, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-27 0:22 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git
On Sun, May 26, 2024 at 04:26:51PM -0700, Junio C Hamano wrote:
> As I keep saying over multiple iterations, the above three bullet
> points stress too much on the minute implementation detail while
> failing to tell readers that the end-user alias receives the rest of
> the command line as arguments.
OK, I can agree that perhaps I've been a bit to fixated on the
addition of "$@" and the mechanics of this. I will propose again with
this trimmed. What I didn't have to help me at the time was the full
command in GIT_TRACE, which I think is probably a more appropriate way
to communicate these details of what's actually hitting the exec
calls.
> Of course the simplest one-liner, if you had the "one" script
> already stored in the file, is to say
So the reason I fell into this, and I wonder how much this plays out
for others too, is that shipping these workflow bits as stand-alone
scripts would mean no !shell tricks required, it's all very logical
and I would never have looked at any of this :) However, when all you
have is a hammer ... since a git config .inc file was needed for other
things, it has been overloaded into essentially being mini
package-manger that avoids having to install additional dependencies;
one-liner shell script at a time :)
> You can do one of two easy things.
>
> $ sh -c 'echo "$1" | grep "$2"' - 1 2
Ok, I think "sh -c" in ! aliases is a bit confusing, personally. You
end up two shells nested deep, and you really have to explain what's
going on with $0; it's very easy to miss.
> $ e(){ echo "$1" | grep "$2"; };e 1 2
This method, which is used elsewhere in the docs as well, I think
makes the most sense. So I've left that in as the example.
-i
^ permalink raw reply [flat|nested] 43+ messages in thread
* [PATCH v7 1/3] Documentation: alias: rework notes into points
2024-05-25 23:44 ` [PATCH v6 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-25 23:44 ` [PATCH v6 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-25 23:44 ` [PATCH v6 3/3] run-command: show prepared command Ian Wienand
@ 2024-05-27 0:30 ` Ian Wienand
2024-05-27 0:30 ` [PATCH v7 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-27 0:30 ` [PATCH v7 3/3] run-command: show prepared command Ian Wienand
2 siblings, 2 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-27 0:30 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
There are a number of caveats when using aliases. Rather than
stuffing them all together in a paragraph, let's separate them out
into individual points to make it clearer what's going on.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 01df96fab3..40851ef429 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -21,8 +21,9 @@ If the alias expansion is prefixed with an exclamation point,
it will be treated as a shell command. For example, defining
`alias.new = !gitk --all --not ORIG_HEAD`, the invocation
`git new` is equivalent to running the shell command
-`gitk --all --not ORIG_HEAD`. Note that shell commands will be
-executed from the top-level directory of a repository, which may
-not necessarily be the current directory.
-`GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
-from the original current directory. See linkgit:git-rev-parse[1].
+`gitk --all --not ORIG_HEAD`. Note:
++
+* Shell commands will be executed from the top-level directory of a
+ repository, which may not necessarily be the current directory.
+* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
+ from the original current directory. See linkgit:git-rev-parse[1].
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v7 2/3] Documentation: alias: add notes on shell expansion
2024-05-27 0:30 ` [PATCH v7 1/3] Documentation: alias: rework notes into points Ian Wienand
@ 2024-05-27 0:30 ` Ian Wienand
2024-05-27 17:48 ` Junio C Hamano
2024-05-27 0:30 ` [PATCH v7 3/3] run-command: show prepared command Ian Wienand
1 sibling, 1 reply; 43+ messages in thread
From: Ian Wienand @ 2024-05-27 0:30 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
When writing inline shell for shell-expansion aliases (i.e. prefixed
with "!"), there are some caveats around argument parsing to be aware
of. This series of notes attempts to explain what is happening more
clearly.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
Documentation/config/alias.txt | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
index 40851ef429..75f9f5e26f 100644
--- a/Documentation/config/alias.txt
+++ b/Documentation/config/alias.txt
@@ -27,3 +27,17 @@ it will be treated as a shell command. For example, defining
repository, which may not necessarily be the current directory.
* `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
from the original current directory. See linkgit:git-rev-parse[1].
+* Shell command aliases always receive any extra arguments provided to
+ the Git command-line as positional arguments.
+** Care should be taken if your shell alias is a "one-liner" script
+ with multiple commands (e.g. in a pipeline), references multiple
+ arguments, or is otherwise not able to handle positional arguments
+ appended via `"$@"`. For example: `alias.cmd = "!echo $1 | grep
+ $2"` called as `git cmd 1 2` will be executed as 'echo $1 | grep $2
+ 1 2', which is not what you want.
+** A convenient way to deal with this is to write your script
+ operations in an inline function that is then called with any
+ arguments from the command-line. For example `alias.cmd = "!c() {
+ echo $1 | grep $2 ; }; c" will correctly execute the prior example.
+** Setting `GIT_TRACE=1` can help you debug the command being run for
+ your alias.
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* [PATCH v7 3/3] run-command: show prepared command
2024-05-27 0:30 ` [PATCH v7 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-27 0:30 ` [PATCH v7 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
@ 2024-05-27 0:30 ` Ian Wienand
1 sibling, 0 replies; 43+ messages in thread
From: Ian Wienand @ 2024-05-27 0:30 UTC (permalink / raw)
To: git; +Cc: Ian Wienand
This adds a trace point in start_command so we can see the full
command invocation without having to resort to strace/code inspection.
For example:
$ GIT_TRACE=1 git test foo
git.c:755 trace: exec: git-test foo
run-command.c:657 trace: run_command: git-test foo
run-command.c:657 trace: run_command: 'echo $*' foo
run-command.c:749 trace: start_command: /bin/sh -c 'echo $* "$@"' 'echo $*' foo
Prior changes have made the documentation around the internals of the
alias command execution clearer, but I have still found this detailed
view of the aliased command being run helpful for debugging purposes.
A test case is added to ensure the full command output is present in
the execution flow.
Signed-off-by: Ian Wienand <iwienand@redhat.com>
---
run-command.c | 3 +++
t/t0014-alias.sh | 11 +++++++++++
2 files changed, 14 insertions(+)
diff --git a/run-command.c b/run-command.c
index 1b821042b4..31b20123d8 100644
--- a/run-command.c
+++ b/run-command.c
@@ -746,6 +746,8 @@ int start_command(struct child_process *cmd)
goto end_of_spawn;
}
+ trace_argv_printf(&argv.v[1], "trace: start_command:");
+
if (pipe(notify_pipe))
notify_pipe[0] = notify_pipe[1] = -1;
@@ -913,6 +915,7 @@ int start_command(struct child_process *cmd)
else if (cmd->use_shell)
cmd->args.v = prepare_shell_cmd(&nargv, sargv);
+ trace_argv_printf(cmd->args.v, "trace: start_command:");
cmd->pid = mingw_spawnvpe(cmd->args.v[0], cmd->args.v,
(char**) cmd->env.v,
cmd->dir, fhin, fhout, fherr);
diff --git a/t/t0014-alias.sh b/t/t0014-alias.sh
index 95568342be..854d59ec58 100755
--- a/t/t0014-alias.sh
+++ b/t/t0014-alias.sh
@@ -44,4 +44,15 @@ test_expect_success 'run-command formats empty args properly' '
test_cmp expect actual
'
+test_expect_success 'tracing a shell alias with arguments shows trace of prepared command' '
+ cat >expect <<-EOF &&
+ trace: start_command: SHELL -c ${SQ}echo \$* "\$@"${SQ} ${SQ}echo \$*${SQ} arg
+ EOF
+ git config alias.echo "!echo \$*" &&
+ env GIT_TRACE=1 git echo arg 2>output &&
+ # redact platform differences
+ sed -n -e "s/^\(trace: start_command:\) .* -c /\1 SHELL -c /p" output >actual &&
+ test_cmp expect actual
+'
+
test_done
--
2.45.1
^ permalink raw reply related [flat|nested] 43+ messages in thread
* Re: [PATCH v7 2/3] Documentation: alias: add notes on shell expansion
2024-05-27 0:30 ` [PATCH v7 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
@ 2024-05-27 17:48 ` Junio C Hamano
0 siblings, 0 replies; 43+ messages in thread
From: Junio C Hamano @ 2024-05-27 17:48 UTC (permalink / raw)
To: Ian Wienand; +Cc: git
Ian Wienand <iwienand@redhat.com> writes:
> When writing inline shell for shell-expansion aliases (i.e. prefixed
> with "!"), there are some caveats around argument parsing to be aware
> of. This series of notes attempts to explain what is happening more
> clearly.
>
> Signed-off-by: Ian Wienand <iwienand@redhat.com>
> ---
> Documentation/config/alias.txt | 14 ++++++++++++++
> 1 file changed, 14 insertions(+)
This one has become much shorter but very much to the point.
Let's further do "appended via `"$@"`" -> "added at the end". The
example given in that bullet point would be easier to understand
that way.
Will queue.
> diff --git a/Documentation/config/alias.txt b/Documentation/config/alias.txt
> index 40851ef429..75f9f5e26f 100644
> --- a/Documentation/config/alias.txt
> +++ b/Documentation/config/alias.txt
> @@ -27,3 +27,17 @@ it will be treated as a shell command. For example, defining
> repository, which may not necessarily be the current directory.
> * `GIT_PREFIX` is set as returned by running `git rev-parse --show-prefix`
> from the original current directory. See linkgit:git-rev-parse[1].
> +* Shell command aliases always receive any extra arguments provided to
> + the Git command-line as positional arguments.
> +** Care should be taken if your shell alias is a "one-liner" script
> + with multiple commands (e.g. in a pipeline), references multiple
> + arguments, or is otherwise not able to handle positional arguments
> + appended via `"$@"`. For example: `alias.cmd = "!echo $1 | grep
> + $2"` called as `git cmd 1 2` will be executed as 'echo $1 | grep $2
> + 1 2', which is not what you want.
> +** A convenient way to deal with this is to write your script
> + operations in an inline function that is then called with any
> + arguments from the command-line. For example `alias.cmd = "!c() {
> + echo $1 | grep $2 ; }; c" will correctly execute the prior example.
> +** Setting `GIT_TRACE=1` can help you debug the command being run for
> + your alias.
^ permalink raw reply [flat|nested] 43+ messages in thread
end of thread, other threads:[~2024-05-27 17:48 UTC | newest]
Thread overview: 43+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-05-22 2:41 [PATCH] alias: document caveats and add trace of prepared command Ian Wienand
2024-05-22 3:29 ` Eric Sunshine
2024-05-22 16:07 ` Junio C Hamano
2024-05-23 0:38 ` Ian Wienand
2024-05-23 4:20 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-23 4:20 ` [PATCH v2 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-23 4:20 ` [PATCH v2 3/3] run-command: show prepared command Ian Wienand
2024-05-23 4:27 ` [PATCH v2 1/3] Documentation: alias: rework notes into points Eric Sunshine
2024-05-23 4:39 ` Ian Wienand
2024-05-23 4:37 ` [PATCH v3 " Ian Wienand
2024-05-23 4:37 ` [PATCH v3 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-23 4:37 ` [PATCH v3 3/3] run-command: show prepared command Ian Wienand
2024-05-23 15:29 ` Junio C Hamano
2024-05-23 23:40 ` Junio C Hamano
2024-05-24 6:09 ` Junio C Hamano
2024-05-24 7:18 ` Ian Wienand
2024-05-24 15:33 ` Junio C Hamano
2024-05-24 0:43 ` Ian Wienand
2024-05-24 17:50 ` Junio C Hamano
2024-05-25 1:13 ` Ian Wienand
2024-05-23 15:14 ` [PATCH v3 1/3] Documentation: alias: rework notes into points Junio C Hamano
2024-05-24 7:32 ` [PATCH v4 " Ian Wienand
2024-05-24 7:32 ` [PATCH v4 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-24 7:32 ` [PATCH v4 3/3] run-command: show prepared command Ian Wienand
2024-05-24 19:16 ` Junio C Hamano
2024-05-24 19:58 ` Junio C Hamano
2024-05-25 1:14 ` Ian Wienand
2024-05-25 1:20 ` [PATCH v5 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-25 1:20 ` [PATCH v5 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-25 1:20 ` [PATCH v5 3/3] run-command: show prepared command Ian Wienand
2024-05-25 5:44 ` Junio C Hamano
2024-05-25 6:06 ` Junio C Hamano
2024-05-25 23:49 ` Ian Wienand
2024-05-25 23:44 ` [PATCH v6 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-25 23:44 ` [PATCH v6 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-26 23:26 ` Junio C Hamano
2024-05-27 0:22 ` Ian Wienand
2024-05-25 23:44 ` [PATCH v6 3/3] run-command: show prepared command Ian Wienand
2024-05-26 16:20 ` Junio C Hamano
2024-05-27 0:30 ` [PATCH v7 1/3] Documentation: alias: rework notes into points Ian Wienand
2024-05-27 0:30 ` [PATCH v7 2/3] Documentation: alias: add notes on shell expansion Ian Wienand
2024-05-27 17:48 ` Junio C Hamano
2024-05-27 0:30 ` [PATCH v7 3/3] run-command: show prepared command Ian Wienand
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).