* [PATCHv3 0/4] Push options in C Git
@ 2016-07-07 1:12 Stefan Beller
2016-07-07 1:12 ` [PATCH 1/4] push options: {pre,post}-receive hook learns about push options Stefan Beller
` (3 more replies)
0 siblings, 4 replies; 35+ messages in thread
From: Stefan Beller @ 2016-07-07 1:12 UTC (permalink / raw)
To: gitster; +Cc: git, e, peff, dwwang, dennis, Stefan Beller
This is not marked for RFC any more, as I do not recall any open points
left for discussion. This addresses the only reply from Eric Wong on patch 3:
diff to v2:
diff --git a/send-pack.c b/send-pack.c
index e328276..c943560 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -536,7 +536,8 @@ int send_pack(struct send_pack_args *args,
for_each_string_list_item(item, args->push_options)
packet_buf_write(&sb, "%s", item->string);
- write_or_die(out, sb.buf, sb.len);
+
+ write_or_die(out, sb.buf, sb.len);
packet_flush(out);
strbuf_release(&sb);
}
diff --git a/transport.c b/transport.c
index 598bd1f..0298be1 100644
--- a/transport.c
+++ b/transport.c
@@ -641,7 +641,6 @@ struct transport *transport_get(struct remote *remote, const char *url)
struct transport *ret = xcalloc(1, sizeof(*ret));
ret->progress = isatty(2);
- ret->push_options = NULL;
if (!remote)
die("No remote provided to transport_get()");
Cover letter v2:
================
Allow a user to pass information along a push to the pre/post-receive hook
on the remote.
Jeff writes on v1:
> Whereas in Dennis's patches, it was about specific information directly
> related to the act of pushing.
This allows to transmit arbitrary information as the backends of $VENDOR
may have different options available related to the direct act of pushing.
Thanks,
Stefan
Cover letter v1:
================
Allow a user to pass information along a push to the pre/post-receive hook
on the remote.
When using a remote that is more than just a plain Git host (e.g. Gerrit,
Git{hub/lab}, etc) this may become more obvious: The (server backend specific)
push options can instruct the server to:
* open a pull request
* send out emails asking for review
* (un)trigger continuous integration
* set priority for continuous integration (i.e. bots pushing may ask to be
treated with lower priority compared to humans)
* ...
Most of these actions can be done on the client side as well,
but in these remote-centric workflows it is easier to do that on the remote,
which is why we need to transport the information there.
More concrete examples:
* When you want a change in Gerrit to be submitted to refs/heads/master, you
push instead to a magic branch refs/for/master and Gerrit will create a change
for you (similar to a pull request). Instead we could imagine that you push
to a magical refs/heads/master with a push option "create-change".
* When pushing to Gerrit you can already attach some of this information by
adding a '%' followed by the parameter to the ref, i.e. when interacting with
Gerrit it is possible to do things like[1]:
git push origin HEAD:refs/for/master%draft%topic=example%cc=jon.doe@example.org
This is not appealing to our users as it looks like hacks upon hacks to make
it work. It would read better if it was spelled as:
git push origin HEAD:refs/for/master \
--push-option draft \
--push-option topic=example \
--push-option cc=jon.doe@example.org
(with a short form that is even easier to type,
but this is is more intuitive already)
This is a patch series to Git core, which is developed at the same time
as a change is proposed to JGit by Dan Wang, see [2].
This code is also available at [3].
Thanks,
Stefan
[1] Not all Gerrit '%' options are documented, so here is a link to source code instead :(
https://gerrit.googlesource.com/gerrit/+/refs/heads/master/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java#1141
[2] https://git.eclipse.org/r/#/c/74570/
[3] https://github.com/stefanbeller/git/tree/pushoptions
Stefan Beller (4):
push options: {pre,post}-receive hook learns about push options
receive-pack: implement advertising and receiving push options
push: accept push options
add a test for push options
Documentation/config.txt | 7 +-
Documentation/git-push.txt | 8 ++-
Documentation/githooks.txt | 4 ++
Documentation/technical/pack-protocol.txt | 10 +--
Documentation/technical/protocol-capabilities.txt | 8 +++
builtin/push.c | 16 ++++-
builtin/receive-pack.c | 85 +++++++++++++++++++----
send-pack.c | 29 ++++++++
send-pack.h | 3 +
t/t5544-push-options.sh | 85 +++++++++++++++++++++++
transport.c | 2 +
transport.h | 7 ++
12 files changed, 242 insertions(+), 22 deletions(-)
create mode 100755 t/t5544-push-options.sh
--
2.9.0.141.gdd65b60
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 1/4] push options: {pre,post}-receive hook learns about push options
2016-07-07 1:12 [PATCHv3 0/4] Push options in C Git Stefan Beller
@ 2016-07-07 1:12 ` Stefan Beller
2016-07-07 20:20 ` Junio C Hamano
2016-07-07 1:12 ` [PATCH 2/4] receive-pack: implement advertising and receiving " Stefan Beller
` (2 subsequent siblings)
3 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-07 1:12 UTC (permalink / raw)
To: gitster; +Cc: git, e, peff, dwwang, dennis, Stefan Beller
The environment variable GIT_PUSH_OPTION_COUNT is set to the number of
push options sent, and GIT_PUSH_OPTION_{0,1,..} is set to the transmitted
option.
The code is not executed as the push options are set to NULL, nor is the
new capability advertised.
There was some discussion back and forth how to present these push options
to the user as there are some ways to do it:
Keep all options in one environment variable
============================================
+ easiest way to implement in Git
- This would make things hard to parse correctly in the hook.
Put the options in files instead,
filenames are in GIT_PUSH_OPTION_FILES
======================================
+ After a discussion about environment variables and shells, we may not
want to put user data into an environment variable (see [1] for example).
+ We could transmit binaries, i.e. we're not bound to C strings as
we are when using environment variables to the user.
+ Maybe easier to parse than constructing environment variable names
GIT_PUSH_OPTION_{0,1,..} yourself
- cleanup of the temporary files is hard to do reliably
- we have race conditions with multiple clients pushing, hence we'd need
to use mkstemp. That's not too bad, but still.
Use environment variables, but restrict to key/value pairs
==========================================================
(When the user pushes a push option `foo=bar`, we'd
GIT_PUSH_OPTION_foo=bar)
+ very easy to parse for a simple model of push options
- it's not sufficient for more elaborate models, e.g.
it doesn't allow doubles (e.g. cc=reviewer@email)
Present the options in different environment variables
======================================================
(This is implemented)
* harder to parse as a user, but we have a sample hook for that.
- doesn't allow binary files
+ allows the same option twice, i.e. is not restrictive about
options, except for binary files.
+ doesn't clutter a remote directory with (possibly stale)
temporary files
As we first want to focus on getting simple strings to work
reliably, we go with the last option for now. If we want to
do transmission of binaries later, we can just attach a
'side-channel', e.g. "any push option that contains a '\0' is
put into a file instead of the environment variable and we'd
have new GIT_PUSH_OPTION_FILES, GIT_PUSH_OPTION_FILENAME_{0,1,..}
environment variables".
We limit the push options for now
* to not exceed an arbitrary count, and
* to not exceed an arbitrary size.
This serves two purposes:
* DoS protection (i.e. one connection can add no more than 32kB
now)
* We need to figure out how to handle large (>64kB). Jeff wrote:
> Yes, but people are also happy when they can use a flexible and
> standardized tool to do a thing. I'd be more frustrated when I found out
> that Git's data-pushing protocol has arbitrary limitations (like, say, I
> can't push a data item larger than a single 64K pkt-line), which would
> easily just work with something like HTTP POSTs.
So to keep a way open in the future to deal with large pay loads,
the size is restricted for now.
[1] 'Shellshock' https://lwn.net/Articles/614218/
Signed-off-by: Stefan Beller <sbeller@google.com>
---
Documentation/githooks.txt | 6 +++++
builtin/receive-pack.c | 48 +++++++++++++++++++++++++++----------
templates/hooks--pre-receive.sample | 22 +++++++++++++++++
3 files changed, 63 insertions(+), 13 deletions(-)
create mode 100644 templates/hooks--pre-receive.sample
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index d82e912..c875cde 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -247,6 +247,9 @@ Both standard output and standard error output are forwarded to
'git send-pack' on the other end, so you can simply `echo` messages
for the user.
+The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
+and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
+
[[update]]
update
~~~~~~
@@ -322,6 +325,9 @@ a sample script `post-receive-email` provided in the `contrib/hooks`
directory in Git distribution, which implements sending commit
emails.
+The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
+and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
+
[[post-update]]
post-update
~~~~~~~~~~~
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 15c323a..edbf81e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -550,8 +550,16 @@ static void prepare_push_cert_sha1(struct child_process *proc)
}
}
+struct receive_hook_feed_state {
+ struct command *cmd;
+ int skip_broken;
+ struct strbuf buf;
+ const struct string_list *push_options;
+};
+
typedef int (*feed_fn)(void *, const char **, size_t *);
-static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_state)
+static int run_and_feed_hook(const char *hook_name, feed_fn feed,
+ struct receive_hook_feed_state *feed_state)
{
struct child_process proc = CHILD_PROCESS_INIT;
struct async muxer;
@@ -567,6 +575,15 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
+ if (feed_state && feed_state->push_options) {
+ int i;
+ for (i = 0; i < feed_state->push_options->nr; i++)
+ argv_array_pushf(&proc.env_array,
+ "GIT_PUSH_OPTION_%d=%s", i,
+ feed_state->push_options->items[i].string);
+ argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
+ feed_state->push_options->nr);
+ }
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
@@ -606,12 +623,6 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed, void *feed_sta
return finish_command(&proc);
}
-struct receive_hook_feed_state {
- struct command *cmd;
- int skip_broken;
- struct strbuf buf;
-};
-
static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
{
struct receive_hook_feed_state *state = state_;
@@ -634,8 +645,10 @@ static int feed_receive_hook(void *state_, const char **bufp, size_t *sizep)
return 0;
}
-static int run_receive_hook(struct command *commands, const char *hook_name,
- int skip_broken)
+static int run_receive_hook(struct command *commands,
+ const char *hook_name,
+ int skip_broken,
+ const struct string_list *push_options)
{
struct receive_hook_feed_state state;
int status;
@@ -646,6 +659,7 @@ static int run_receive_hook(struct command *commands, const char *hook_name,
if (feed_receive_hook(&state, NULL, NULL))
return 0;
state.cmd = commands;
+ state.push_options = push_options;
status = run_and_feed_hook(hook_name, feed_receive_hook, &state);
strbuf_release(&state.buf);
return status;
@@ -1316,7 +1330,8 @@ cleanup:
static void execute_commands(struct command *commands,
const char *unpacker_error,
- struct shallow_info *si)
+ struct shallow_info *si,
+ const struct string_list *push_options)
{
struct command *cmd;
unsigned char sha1[20];
@@ -1335,7 +1350,7 @@ static void execute_commands(struct command *commands,
reject_updates_to_hidden(commands);
- if (run_receive_hook(commands, "pre-receive", 0)) {
+ if (run_receive_hook(commands, "pre-receive", 0, push_options)) {
for (cmd = commands; cmd; cmd = cmd->next) {
if (!cmd->error_string)
cmd->error_string = "pre-receive hook declined";
@@ -1756,6 +1771,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
if ((commands = read_head_info(&shallow)) != NULL) {
const char *unpack_status = NULL;
+ struct string_list *push_options = NULL;
prepare_shallow_info(&si, &shallow);
if (!si.nr_ours && !si.nr_theirs)
@@ -1764,13 +1780,19 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
unpack_status = unpack_with_sideband(&si);
update_shallow_info(commands, &si, &ref);
}
- execute_commands(commands, unpack_status, &si);
+ execute_commands(commands, unpack_status, &si,
+ push_options);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
if (report_status)
report(commands, unpack_status);
- run_receive_hook(commands, "post-receive", 1);
+ run_receive_hook(commands, "post-receive", 1,
+ push_options);
run_update_post_hook(commands);
+ if (push_options) {
+ string_list_clear(push_options, 0);
+ free(push_options);
+ }
if (auto_gc) {
const char *argv_gc_auto[] = {
"gc", "--auto", "--quiet", NULL,
diff --git a/templates/hooks--pre-receive.sample b/templates/hooks--pre-receive.sample
new file mode 100644
index 0000000..e4d3edc
--- /dev/null
+++ b/templates/hooks--pre-receive.sample
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# An example hook script to make use of push options.
+# The example simply echoes all push options that start with 'echoback='
+# and rejects all pushes when the "reject" push option is used.
+#
+# To enable this hook, rename this file to "pre-receive".
+
+if test -n "$GIT_PUSH_OPTION_COUNT"; then
+ i=0
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do
+ eval "value=\$GIT_PUSH_OPTION_$i"
+ case "$value" in
+ echoback=*)
+ echo "echo from the pre-receive-hook ${value#*=}" >&2
+ ;;
+ reject)
+ exit 1
+ esac
+ i=$((i + 1))
+ done
+fi
--
2.9.0.141.gd59d3e9.dirty
^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 1/4] push options: {pre,post}-receive hook learns about push options
2016-07-07 1:12 ` [PATCH 1/4] push options: {pre,post}-receive hook learns about push options Stefan Beller
@ 2016-07-07 20:20 ` Junio C Hamano
2016-07-07 21:50 ` Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2016-07-07 20:20 UTC (permalink / raw)
To: Stefan Beller; +Cc: git, e, peff, dwwang, dennis
Stefan Beller <sbeller@google.com> writes:
> As we first want to focus on getting simple strings to work
> reliably, we go with the last option for now.
OK.
> diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
> index d82e912..c875cde 100644
> --- a/Documentation/githooks.txt
> +++ b/Documentation/githooks.txt
> @@ -247,6 +247,9 @@ Both standard output and standard error output are forwarded to
> 'git send-pack' on the other end, so you can simply `echo` messages
> for the user.
>
> +The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
> +and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
> +
> [[update]]
> update
> ~~~~~~
> @@ -322,6 +325,9 @@ a sample script `post-receive-email` provided in the `contrib/hooks`
> directory in Git distribution, which implements sending commit
> emails.
>
> +The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
> +and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
> +
The mention of "push options" in these two entries sounded a bit too
abrupt, at least to me. Perhaps add a short phrase before the
sentence, e.g.
When 'git push --push-option=...' is used, the number of push
options are avaiable ...
or
The number of push options given on the command line of "git
push --push-option=..." can be read from the environment
variable GIT_PUSH_OPTION_COUNT, and the options themselves are
found in GIT_PUSH_OPTION_0, GIT_PUSH_OPTION_1,...
We can read any of the above variants in two ways to describe what
should happen when "git push" is run without "--push-option=...".
Is GIT_PUSH_OPTION_COUNT unset, or set to 0? Either way, it may be
better to be a bit more explicit.
The hook driver code does not explicitly clear these environment
variables; it is safe to assume that these won't seep in from the
environment, I think, but I am not sure.
> @@ -1756,6 +1771,7 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
>
> if ((commands = read_head_info(&shallow)) != NULL) {
> const char *unpack_status = NULL;
> + struct string_list *push_options = NULL;
>
> prepare_shallow_info(&si, &shallow);
> if (!si.nr_ours && !si.nr_theirs)
> @@ -1764,13 +1780,19 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
> unpack_status = unpack_with_sideband(&si);
> update_shallow_info(commands, &si, &ref);
> }
> - execute_commands(commands, unpack_status, &si);
> + execute_commands(commands, unpack_status, &si,
> + push_options);
> if (pack_lockfile)
> unlink_or_warn(pack_lockfile);
> if (report_status)
> report(commands, unpack_status);
> - run_receive_hook(commands, "post-receive", 1);
> + run_receive_hook(commands, "post-receive", 1,
> + push_options);
> run_update_post_hook(commands);
> + if (push_options) {
> + string_list_clear(push_options, 0);
> + free(push_options);
> + }
> if (auto_gc) {
> const char *argv_gc_auto[] = {
> "gc", "--auto", "--quiet", NULL,
> diff --git a/templates/hooks--pre-receive.sample b/templates/hooks--pre-receive.sample
> new file mode 100644
> index 0000000..e4d3edc
> --- /dev/null
> +++ b/templates/hooks--pre-receive.sample
> @@ -0,0 +1,22 @@
> +#!/bin/sh
> +#
> +# An example hook script to make use of push options.
> +# The example simply echoes all push options that start with 'echoback='
> +# and rejects all pushes when the "reject" push option is used.
> +#
> +# To enable this hook, rename this file to "pre-receive".
> +
> +if test -n "$GIT_PUSH_OPTION_COUNT"; then
> + i=0
> + while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do
Style:
if test -n "$GIT_PUSH_OPTION_COUNT"
then
...
while test ...
do
...
> + eval "value=\$GIT_PUSH_OPTION_$i"
> + case "$value" in
> + echoback=*)
> + echo "echo from the pre-receive-hook ${value#*=}" >&2
> + ;;
> + reject)
> + exit 1
> + esac
> + i=$((i + 1))
> + done
> +fi
What is suboptimal about the structure of the series is that we
won't bisect down to any of the potential bugs in the above code
even if we ever see any bug in the future. It also does not hint
where push_options is expected to be read in the code in the
subsequent patches in the series. If I were doing this series, I
would probably have done 2/4 first without plumbing it through
(i.e. it is sent and accumulated in a string list at the receiver,
and then cleared and freed without being used), and then added the
processing (i.e. this step) as the second patch.
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 1/4] push options: {pre,post}-receive hook learns about push options
2016-07-07 20:20 ` Junio C Hamano
@ 2016-07-07 21:50 ` Stefan Beller
2016-07-07 21:53 ` Junio C Hamano
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-07 21:50 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
On Thu, Jul 7, 2016 at 1:20 PM, Junio C Hamano <gitster@pobox.com> wrote:
>
> What is suboptimal about the structure of the series is that we
> won't bisect down to any of the potential bugs in the above code
> even if we ever see any bug in the future.
> It also does not hint
> where push_options is expected to be read in the code in the
> subsequent patches in the series. If I were doing this series, I
> would probably have done 2/4 first without plumbing it through
> (i.e. it is sent and accumulated in a string list at the receiver,
> and then cleared and freed without being used), and then added the
> processing (i.e. this step) as the second patch.
But your first patch (2/4) would not yet advertise the capability?
Or advertise and then just ignoring it?
That shadows other bugs that would not properly bisect, I'd imagine?
It is better for documentation purposes in this patch though. It makes
the other patch harder as "it allows transmitting push options, but
in that patch nothing of value is done with them."
So I'll see if I can reorder easily.
Thanks,
Stefan
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 1/4] push options: {pre,post}-receive hook learns about push options
2016-07-07 21:50 ` Stefan Beller
@ 2016-07-07 21:53 ` Junio C Hamano
0 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2016-07-07 21:53 UTC (permalink / raw)
To: Stefan Beller; +Cc: git, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
Stefan Beller <sbeller@google.com> writes:
> But your first patch (2/4) would not yet advertise the capability?
> Or advertise and then just ignoring it?
As I wrote...
>> If I were doing this series, I
>> would probably have done 2/4 first without plumbing it through
>> (i.e. it is sent and accumulated in a string list at the receiver,
>> and then cleared and freed without being used), and then added the
>> processing (i.e. this step) as the second patch.
... I would imagine it would advertise, allow the other side to send,
receive and collect, and then discard (properly) without using.
> It is better for documentation purposes in this patch though. It makes
> the other patch harder as "it allows transmitting push options, but
> in that patch nothing of value is done with them."
>
> So I'll see if I can reorder easily.
It does not matter too much. Let's not spend too much time on the
ordering.
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-07 1:12 [PATCHv3 0/4] Push options in C Git Stefan Beller
2016-07-07 1:12 ` [PATCH 1/4] push options: {pre,post}-receive hook learns about push options Stefan Beller
@ 2016-07-07 1:12 ` Stefan Beller
2016-07-07 20:37 ` Junio C Hamano
2016-07-07 1:12 ` [PATCH 3/4] push: accept " Stefan Beller
2016-07-07 1:12 ` [PATCH 4/4] add a test for " Stefan Beller
3 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-07 1:12 UTC (permalink / raw)
To: gitster; +Cc: git, e, peff, dwwang, dennis, Stefan Beller
The pre/post receive hook may be interested in more information from the
user. This information can be transmitted when both client and server
support the "push-options" capability, which when used is a phase directly
after update commands ended by a flush pkt.
Similar to the atomic option, the server capability can be disabled via
the `receive.advertisePushOptions` config variable. While documenting
this, fix a nit in the `receive.advertiseAtomic` wording.
Signed-off-by: Stefan Beller <sbeller@google.com>
---
Documentation/config.txt | 7 ++-
Documentation/technical/pack-protocol.txt | 10 ++--
Documentation/technical/protocol-capabilities.txt | 8 +++
builtin/receive-pack.c | 59 +++++++++++++++++++++++
4 files changed, 79 insertions(+), 5 deletions(-)
diff --git a/Documentation/config.txt b/Documentation/config.txt
index e208af1..df1b314 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2410,7 +2410,12 @@ rebase.instructionFormat
receive.advertiseAtomic::
By default, git-receive-pack will advertise the atomic push
- capability to its clients. If you don't want to this capability
+ capability to its clients. If you don't want this capability
+ to be advertised, set this variable to false.
+
+receive.advertisePushOptions::
+ By default, git-receive-pack will advertise the push options capability
+ to its clients. If you don't want this capability
to be advertised, set this variable to false.
receive.autogc::
diff --git a/Documentation/technical/pack-protocol.txt b/Documentation/technical/pack-protocol.txt
index 8b36343..7a2ed30 100644
--- a/Documentation/technical/pack-protocol.txt
+++ b/Documentation/technical/pack-protocol.txt
@@ -454,7 +454,8 @@ The reference discovery phase is done nearly the same way as it is in the
fetching protocol. Each reference obj-id and name on the server is sent
in packet-line format to the client, followed by a flush-pkt. The only
real difference is that the capability listing is different - the only
-possible values are 'report-status', 'delete-refs' and 'ofs-delta'.
+possible values are 'report-status', 'delete-refs', 'ofs-delta' and
+'push-options'.
Reference Update Request and Packfile Transfer
----------------------------------------------
@@ -465,9 +466,10 @@ that it wants to update, it sends a line listing the obj-id currently on
the server, the obj-id the client would like to update it to and the name
of the reference.
-This list is followed by a flush-pkt and then the packfile that should
-contain all the objects that the server will need to complete the new
-references.
+This list is followed by a flush-pkt. Then the push options are transmitted
+one per packet followed by another flush-pkt. After that the packfile that
+should contain all the objects that the server will need to complete the new
+references will be sent.
----
update-request = *shallow ( command-list | push-cert ) [packfile]
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index eaab6b4..b71eda9 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -253,6 +253,14 @@ atomic pushes. If the pushing client requests this capability, the server
will update the refs in one atomic transaction. Either all refs are
updated or none.
+push-options
+------------
+
+If the server sends the 'push-options' capability it is capable to accept
+push options after the update commands have been sent. If the pushing client
+requests this capability, the server will pass the options to the pre and post
+receive hooks that process this push request.
+
allow-tip-sha1-in-want
----------------------
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index edbf81e..e71041a 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -44,10 +44,12 @@ static struct strbuf fsck_msg_types = STRBUF_INIT;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int advertise_atomic_push = 1;
+static int advertise_push_options = 1;
static int unpack_limit = 100;
static int report_status;
static int use_sideband;
static int use_atomic;
+static int use_push_options;
static int quiet;
static int prefer_ofs_delta = 1;
static int auto_update_server_info;
@@ -193,6 +195,11 @@ static int receive_pack_config(const char *var, const char *value, void *cb)
return 0;
}
+ if (strcmp(var, "receive.advertisepushoptions") == 0) {
+ advertise_push_options = git_config_bool(var, value);
+ return 0;
+ }
+
return git_default_config(var, value, cb);
}
@@ -207,6 +214,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
"report-status delete-refs side-band-64k quiet");
if (advertise_atomic_push)
strbuf_addstr(&cap, " atomic");
+ if (advertise_push_options)
+ strbuf_addstr(&cap, " push-options");
if (prefer_ofs_delta)
strbuf_addstr(&cap, " ofs-delta");
if (push_cert_nonce)
@@ -1454,6 +1463,9 @@ static struct command *read_head_info(struct sha1_array *shallow)
if (advertise_atomic_push
&& parse_feature_request(feature_list, "atomic"))
use_atomic = 1;
+ if (advertise_push_options
+ && parse_feature_request(feature_list, "push-options"))
+ use_push_options = 1;
}
if (!strcmp(line, "push-cert")) {
@@ -1486,6 +1498,50 @@ static struct command *read_head_info(struct sha1_array *shallow)
return commands;
}
+static struct string_list *read_push_options()
+{
+ int i;
+ struct string_list *ret = xmalloc(sizeof(*ret));
+ string_list_init(ret, 1);
+
+ /* NEEDSWORK: expose the limitations to be configurable. */
+ int max_options = 32;
+
+ /*
+ * NEEDSWORK: expose the limitations to be configurable;
+ * Once the limit can be lifted, include a way for payloads
+ * larger than one pkt, e.g allow a payload of up to
+ * LARGE_PACKET_MAX - 1 only, and reserve the last byte
+ * to indicate whether the next pkt continues with this
+ * push option.
+ */
+ int max_size = 1024;
+
+ for (i = 0; i < max_options; i++) {
+ char *line;
+ int len;
+
+ line = packet_read_line(0, &len);
+
+ if (!line)
+ break;
+
+ if (len > max_size)
+ die("protocol error: server configuration allows push "
+ "options of size up to %d bytes", max_size);
+
+ len = strcspn(line, "\n");
+ line[len] = '\0';
+
+ string_list_append(ret, line);
+ }
+ if (i == max_options)
+ die("protocol error: server configuration only allows up "
+ "to %d push options", max_options);
+
+ return ret;
+}
+
static const char *parse_pack_header(struct pack_header *hdr)
{
switch (read_pack_header(0, hdr)) {
@@ -1773,6 +1829,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
const char *unpack_status = NULL;
struct string_list *push_options = NULL;
+ if (use_push_options)
+ push_options = read_push_options();
+
prepare_shallow_info(&si, &shallow);
if (!si.nr_ours && !si.nr_theirs)
shallow_update = 0;
--
2.9.0.141.gd59d3e9.dirty
^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-07 1:12 ` [PATCH 2/4] receive-pack: implement advertising and receiving " Stefan Beller
@ 2016-07-07 20:37 ` Junio C Hamano
2016-07-07 21:41 ` Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2016-07-07 20:37 UTC (permalink / raw)
To: Stefan Beller; +Cc: git, e, peff, dwwang, dennis
Stefan Beller <sbeller@google.com> writes:
> While documenting
> this, fix a nit in the `receive.advertiseAtomic` wording.
>
> receive.advertiseAtomic::
> By default, git-receive-pack will advertise the atomic push
> - capability to its clients. If you don't want to this capability
> + capability to its clients. If you don't want this capability
> + to be advertised, set this variable to false.
> +
> +receive.advertisePushOptions::
> + By default, git-receive-pack will advertise the push options capability
> + to its clients. If you don't want this capability
> to be advertised, set this variable to false.
I think we correcting the nit by avoiding passive voice, i.e.
If you don't want to advertise this capability, set this
variable to false.
would make it easier to read.
> in packet-line format to the client, followed by a flush-pkt. The only
> real difference is that the capability listing is different - the only
> -possible values are 'report-status', 'delete-refs' and 'ofs-delta'.
> +possible values are 'report-status', 'delete-refs', 'ofs-delta' and
> +'push-options'.
OK.
> +push-options
> +------------
> +
> +If the server sends the 'push-options' capability it is capable to accept
Two nits:
- A comma would make it easier to read.
- "capable" goes with "of <gerund>", while "able" goes with "to <infinitive>".
i.e. "... capability, it is capable of accepting..."
> +push options after the update commands have been sent. If the pushing client
> +requests this capability, the server will pass the options to the pre and post
> +receive hooks that process this push request.
Missing dashes, i.e. "pre- and post-receive hooks"?
> @@ -207,6 +214,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
> "report-status delete-refs side-band-64k quiet");
> if (advertise_atomic_push)
> strbuf_addstr(&cap, " atomic");
> + if (advertise_push_options)
> + strbuf_addstr(&cap, " push-options");
> if (prefer_ofs_delta)
> strbuf_addstr(&cap, " ofs-delta");
> if (push_cert_nonce)
Hmph, was there a good reason to add it in the middle (contrast to
the previous addition to the "only possible values are..."
enumeration)?
> +static struct string_list *read_push_options()
static struct string_list *read_push_options(void)
> +{
> + int i;
> + struct string_list *ret = xmalloc(sizeof(*ret));
> + string_list_init(ret, 1);
> +
> + /* NEEDSWORK: expose the limitations to be configurable. */
> + int max_options = 32;
> +
> + /*
> + * NEEDSWORK: expose the limitations to be configurable;
> + * Once the limit can be lifted, include a way for payloads
> + * larger than one pkt, e.g allow a payload of up to
> + * LARGE_PACKET_MAX - 1 only, and reserve the last byte
> + * to indicate whether the next pkt continues with this
> + * push option.
> + */
> + int max_size = 1024;
Good NEEDSWORK comments; perhaps also hint that the configuration
must not come from the repository level configuration file (i.e.
Peff's "scoped configuration" from jk/upload-pack-hook topic)?
> + for (i = 0; i < max_options; i++) {
> + char *line;
> + int len;
> +
> + line = packet_read_line(0, &len);
> +
> + if (!line)
> + break;
> +
> + if (len > max_size)
> + die("protocol error: server configuration allows push "
> + "options of size up to %d bytes", max_size);
> +
> + len = strcspn(line, "\n");
> + line[len] = '\0';
> +
> + string_list_append(ret, line);
> + }
> + if (i == max_options)
> + die("protocol error: server configuration only allows up "
> + "to %d push options", max_options);
When not going over ssh://, does the user sees these messages?
More importantly, if we plan to make this configurable and not make
the limit a hardwired constant of the wire protocol, it may be
better to advertise push-options capability with the limit, e.g.
"push-options=32" (or even "push-options=1024/32"), so that the
client side can count and abort early?
I wondered how well the extra flush works with the extra framing
smart-http does to wrap the wire protocol; as I do not see any
change to the http side, I'd assume that there is no issue.
> +
> + return ret;
> +}
> +
> static const char *parse_pack_header(struct pack_header *hdr)
> {
> switch (read_pack_header(0, hdr)) {
> @@ -1773,6 +1829,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
> const char *unpack_status = NULL;
> struct string_list *push_options = NULL;
>
> + if (use_push_options)
> + push_options = read_push_options();
> +
> prepare_shallow_info(&si, &shallow);
> if (!si.nr_ours && !si.nr_theirs)
> shallow_update = 0;
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-07 20:37 ` Junio C Hamano
@ 2016-07-07 21:41 ` Stefan Beller
2016-07-07 21:56 ` Jeff King
2016-07-07 22:06 ` Junio C Hamano
0 siblings, 2 replies; 35+ messages in thread
From: Stefan Beller @ 2016-07-07 21:41 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
On Thu, Jul 7, 2016 at 1:37 PM, Junio C Hamano <gitster@pobox.com> wrote:
>
>> @@ -207,6 +214,8 @@ static void show_ref(const char *path, const unsigned char *sha1)
>> "report-status delete-refs side-band-64k quiet");
>> if (advertise_atomic_push)
>> strbuf_addstr(&cap, " atomic");
>> + if (advertise_push_options)
>> + strbuf_addstr(&cap, " push-options");
>> if (prefer_ofs_delta)
>> strbuf_addstr(&cap, " ofs-delta");
>> if (push_cert_nonce)
>
> Hmph, was there a good reason to add it in the middle (contrast to
> the previous addition to the "only possible values are..."
> enumeration)?
No, there is no good objective reason. I added it just after the atomic
flag as that is what I implemented.
Is there a reason for a particular order of capabilities? I always considered
it a set of strings, i.e. any order is valid and there is no preference in
which way to put it.
>
>> +static struct string_list *read_push_options()
>
> static struct string_list *read_push_options(void)
>
>> +{
>> + int i;
>> + struct string_list *ret = xmalloc(sizeof(*ret));
>> + string_list_init(ret, 1);
>> +
>> + /* NEEDSWORK: expose the limitations to be configurable. */
>> + int max_options = 32;
>> +
>> + /*
>> + * NEEDSWORK: expose the limitations to be configurable;
>> + * Once the limit can be lifted, include a way for payloads
>> + * larger than one pkt, e.g allow a payload of up to
>> + * LARGE_PACKET_MAX - 1 only, and reserve the last byte
>> + * to indicate whether the next pkt continues with this
>> + * push option.
>> + */
>> + int max_size = 1024;
>
> Good NEEDSWORK comments; perhaps also hint that the configuration
> must not come from the repository level configuration file (i.e.
> Peff's "scoped configuration" from jk/upload-pack-hook topic)?
Ok, I reviewed that series. It is unclear to me how the attack would
actually look like in that case.
In 20b20a22f8f Jeff writes:
> Because we promise that
> upload-pack is safe to run in an untrusted repository, we
> cannot execute arbitrary code or commands found in the
> repository (neither in hooks/, nor in the config).
I agree on this for all content that can be modified by the user
(e.g. files in the work tree such as .gitmodules), but the .git/config
file cannot be changed remotely. So I wonder how an attack would
look like for a hosting provider or anyone else?
We still rely on a sane system and trust /etc/gitconfig
so we do trust the host/admin but not the user?
>
>> + for (i = 0; i < max_options; i++) {
>> + char *line;
>> + int len;
>> +
>> + line = packet_read_line(0, &len);
>> +
>> + if (!line)
>> + break;
>> +
>> + if (len > max_size)
>> + die("protocol error: server configuration allows push "
>> + "options of size up to %d bytes", max_size);
>> +
>> + len = strcspn(line, "\n");
>> + line[len] = '\0';
>> +
>> + string_list_append(ret, line);
>> + }
>> + if (i == max_options)
>> + die("protocol error: server configuration only allows up "
>> + "to %d push options", max_options);
>
> When not going over ssh://, does the user sees these messages?
>
> More importantly, if we plan to make this configurable and not make
> the limit a hardwired constant of the wire protocol, it may be
> better to advertise push-options capability with the limit, e.g.
> "push-options=32" (or even "push-options=1024/32"), so that the
> client side can count and abort early?
Yeah we may want to start out with a strict format here indicating
the parameters used for evaluating the size.
So what do these numbers mean?
I assume (and hence I should document that,) that the first (1024)
is the maximum number of bytes per push option. The second
number (32) is the number of push options (not the number of pkts,
as one push option may take more than one pkt if the first number is
larger than 65k, see the NEEDSWORK comment.)
Do we really need 2 numbers, or could we just have one number
describing the maximum total size in bytes before the remote rejects
the connection?
>
> I wondered how well the extra flush works with the extra framing
> smart-http does to wrap the wire protocol; as I do not see any
> change to the http side, I'd assume that there is no issue.
That's a dangerous assumption of yours, as I did not test the
https side, yet.
>
>> +
>> + return ret;
>> +}
>> +
>> static const char *parse_pack_header(struct pack_header *hdr)
>> {
>> switch (read_pack_header(0, hdr)) {
>> @@ -1773,6 +1829,9 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
>> const char *unpack_status = NULL;
>> struct string_list *push_options = NULL;
>>
>> + if (use_push_options)
>> + push_options = read_push_options();
>> +
>> prepare_shallow_info(&si, &shallow);
>> if (!si.nr_ours && !si.nr_theirs)
>> shallow_update = 0;
>
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-07 21:41 ` Stefan Beller
@ 2016-07-07 21:56 ` Jeff King
2016-07-07 22:06 ` Stefan Beller
2016-07-07 22:06 ` Junio C Hamano
1 sibling, 1 reply; 35+ messages in thread
From: Jeff King @ 2016-07-07 21:56 UTC (permalink / raw)
To: Stefan Beller
Cc: Junio C Hamano, git, Eric Wong, Dan Wang, Dennis Kaarsemaker
On Thu, Jul 07, 2016 at 02:41:37PM -0700, Stefan Beller wrote:
> >> + /* NEEDSWORK: expose the limitations to be configurable. */
> >> + int max_options = 32;
> >> +
> >> + /*
> >> + * NEEDSWORK: expose the limitations to be configurable;
> >> + * Once the limit can be lifted, include a way for payloads
> >> + * larger than one pkt, e.g allow a payload of up to
> >> + * LARGE_PACKET_MAX - 1 only, and reserve the last byte
> >> + * to indicate whether the next pkt continues with this
> >> + * push option.
> >> + */
> >> + int max_size = 1024;
> >
> > Good NEEDSWORK comments; perhaps also hint that the configuration
> > must not come from the repository level configuration file (i.e.
> > Peff's "scoped configuration" from jk/upload-pack-hook topic)?
>
> Ok, I reviewed that series. It is unclear to me how the attack would
> actually look like in that case.
>
> In 20b20a22f8f Jeff writes:
> > Because we promise that
> > upload-pack is safe to run in an untrusted repository, we
> > cannot execute arbitrary code or commands found in the
> > repository (neither in hooks/, nor in the config).
>
> I agree on this for all content that can be modified by the user
> (e.g. files in the work tree such as .gitmodules), but the .git/config
> file cannot be changed remotely. So I wonder how an attack would
> look like for a hosting provider or anyone else?
> We still rely on a sane system and trust /etc/gitconfig
> so we do trust the host/admin but not the user?
The problem is for hosting sites which serve repositories via git-daemon
from untrusted users who have real shell accounts (e.g., you set up
git-daemon to run as the "daemon" user serving repositories out of
people's home directories; you don't want users to escalate their shell
access into running arbitrary code as "daemon").
But I don't think that case applies here. That is about running
upload-pack on an untrusted repository, but your changes here are part
of receive-pack. In such a scenario, users should be pushing as
themselves via ssh. And if they are not (e.g., the admin set up
push-over-smart-http centrally), they are already screwed, as a
malicious user could just set up a pre-receive hook.
IOW, we promise only that upload-pack is safe to run an untrusted repo,
but not receive-pack.
-Peff
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-07 21:56 ` Jeff King
@ 2016-07-07 22:06 ` Stefan Beller
2016-07-07 22:09 ` Jeff King
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-07 22:06 UTC (permalink / raw)
To: Jeff King; +Cc: Junio C Hamano, git, Eric Wong, Dan Wang, Dennis Kaarsemaker
On Thu, Jul 7, 2016 at 2:56 PM, Jeff King <peff@peff.net> wrote:
> On Thu, Jul 07, 2016 at 02:41:37PM -0700, Stefan Beller wrote:
>
>> >> + /* NEEDSWORK: expose the limitations to be configurable. */
>> >> + int max_options = 32;
>> >> +
>> >> + /*
>> >> + * NEEDSWORK: expose the limitations to be configurable;
>> >> + * Once the limit can be lifted, include a way for payloads
>> >> + * larger than one pkt, e.g allow a payload of up to
>> >> + * LARGE_PACKET_MAX - 1 only, and reserve the last byte
>> >> + * to indicate whether the next pkt continues with this
>> >> + * push option.
>> >> + */
>> >> + int max_size = 1024;
>> >
>> > Good NEEDSWORK comments; perhaps also hint that the configuration
>> > must not come from the repository level configuration file (i.e.
>> > Peff's "scoped configuration" from jk/upload-pack-hook topic)?
>>
>> Ok, I reviewed that series. It is unclear to me how the attack would
>> actually look like in that case.
>>
>> In 20b20a22f8f Jeff writes:
>> > Because we promise that
>> > upload-pack is safe to run in an untrusted repository, we
>> > cannot execute arbitrary code or commands found in the
>> > repository (neither in hooks/, nor in the config).
>>
>> I agree on this for all content that can be modified by the user
>> (e.g. files in the work tree such as .gitmodules), but the .git/config
>> file cannot be changed remotely. So I wonder how an attack would
>> look like for a hosting provider or anyone else?
>> We still rely on a sane system and trust /etc/gitconfig
>> so we do trust the host/admin but not the user?
>
> The problem is for hosting sites which serve repositories via git-daemon
> from untrusted users who have real shell accounts (e.g., you set up
> git-daemon to run as the "daemon" user serving repositories out of
> people's home directories; you don't want users to escalate their shell
> access into running arbitrary code as "daemon").
I think you would want to lock down the
hosting site as much as possible and not put untrusted users home
directories on there? So it is hard for me to imagine you'd go for such a setup
in practice.
>
> But I don't think that case applies here. That is about running
> upload-pack on an untrusted repository, but your changes here are part
> of receive-pack. In such a scenario, users should be pushing as
> themselves via ssh. And if they are not (e.g., the admin set up
> push-over-smart-http centrally), they are already screwed, as a
> malicious user could just set up a pre-receive hook.
I hear that as: "The pre-receive hook itself can do much more
damage than an oversized push option payload".
OK.
>
> IOW, we promise only that upload-pack is safe to run an untrusted repo,
> but not receive-pack.
>
> -Peff
Thanks,
Stefan
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-07 22:06 ` Stefan Beller
@ 2016-07-07 22:09 ` Jeff King
0 siblings, 0 replies; 35+ messages in thread
From: Jeff King @ 2016-07-07 22:09 UTC (permalink / raw)
To: Stefan Beller
Cc: Junio C Hamano, git, Eric Wong, Dan Wang, Dennis Kaarsemaker
On Thu, Jul 07, 2016 at 03:06:31PM -0700, Stefan Beller wrote:
> > The problem is for hosting sites which serve repositories via git-daemon
> > from untrusted users who have real shell accounts (e.g., you set up
> > git-daemon to run as the "daemon" user serving repositories out of
> > people's home directories; you don't want users to escalate their shell
> > access into running arbitrary code as "daemon").
>
> I think you would want to lock down the
> hosting site as much as possible and not put untrusted users home
> directories on there? So it is hard for me to imagine you'd go for such a setup
> in practice.
Sure, I think that's a good way to run a hosting site, too. But it
doesn't mean people don't have other needs. kernel.org was run as I
mentioned above for many years.
Another related case: you have a multi-user server where Alice might run
"git fetch server:~bob/repo.git". Alice does not want to run arbitrary
code based on what is in Bob's repo.git. Even if they are in the same
company, it is a poor security practice.
> > But I don't think that case applies here. That is about running
> > upload-pack on an untrusted repository, but your changes here are part
> > of receive-pack. In such a scenario, users should be pushing as
> > themselves via ssh. And if they are not (e.g., the admin set up
> > push-over-smart-http centrally), they are already screwed, as a
> > malicious user could just set up a pre-receive hook.
>
> I hear that as: "The pre-receive hook itself can do much more
> damage than an oversized push option payload".
Exactly. Or more to the point: we promise nothing here except for
upload-pack, so changes to receive-pack do not have to worry about this
issue at all.
-Peff
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-07 21:41 ` Stefan Beller
2016-07-07 21:56 ` Jeff King
@ 2016-07-07 22:06 ` Junio C Hamano
2016-07-08 17:58 ` Jonathan Nieder
1 sibling, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2016-07-07 22:06 UTC (permalink / raw)
To: Stefan Beller; +Cc: git, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
Stefan Beller <sbeller@google.com> writes:
> No, there is no good objective reason. I added it just after the atomic
> flag as that is what I implemented.
>
> Is there a reason for a particular order of capabilities? I always considered
> it a set of strings, i.e. any order is valid and there is no preference in
> which way to put it.
That is correct, but "there is no inherent order or grouping" does
not lead to "hence it is OK to put a new thing at a random place in
the middle."
If a reviewer sees a new thing at some specific point in a
collection, when the collection is understood not to have any
specific order or grouping, it makes the reviewer doubt the
belief--there must be a reason why this was placed not at the end;
otherwise a new thing wouldn't be placed randomly at the middle.
If you place a new thing at the end, it still leaves ambiguity; it
may be there because there is no inherent order or grouping, or the
new one is sorts later than the one that used to be at the last or
somehow related to it.
> I agree on this for all content that can be modified by the user
> (e.g. files in the work tree such as .gitmodules), but the .git/config
> file cannot be changed remotely. So I wonder how an attack would
> look like for a hosting provider or anyone else?
> We still rely on a sane system and trust /etc/gitconfig
> so we do trust the host/admin but not the user?
It is not "we" do trust; it is "we let host/admin trust itself while
making sure that they can protect themselves from their users".
>> More importantly, if we plan to make this configurable and not make
>> the limit a hardwired constant of the wire protocol, it may be
>> better to advertise push-options capability with the limit, e.g.
>> "push-options=32" (or even "push-options=1024/32"), so that the
>> client side can count and abort early?
>
> Yeah we may want to start out with a strict format here indicating
> the parameters used for evaluating the size.
> So what do these numbers mean?
>
> I assume (and hence I should document that,) that the first (1024)
> is the maximum number of bytes per push option. The second
> number (32) is the number of push options (not the number of pkts,
> as one push option may take more than one pkt if the first number is
> larger than 65k, see the NEEDSWORK comment.)
>
> Do we really need 2 numbers, or could we just have one number
> describing the maximum total size in bytes before the remote rejects
> the connection?
That's for you to decide. push-options=32 is probably OK but it
will prevent a well-behaving "git push" from catching a request that
will be rejected, if you are basing the receiver side decision on
the other number.
The suggestion was primarily my reaction after seeing the two new
calls to die() on the receiver side, whose message I was not sure
will be given to the user who is pushing, i.e.
> When not going over ssh://, does the user see these messages?
Your "git push" will be collecting the options in a string-list
while parsing the options, so it would be able to check immediately
upon seeing the advertised capability if it will trigger this
protocol error even before making the request, which would be a good
thing to do, and I am reasonably sure we can give a better error
message if we do that on the local side without having the receiver
to tell you (for one thing, we can i18n the local side error
message more easily).
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-07 22:06 ` Junio C Hamano
@ 2016-07-08 17:58 ` Jonathan Nieder
2016-07-08 18:39 ` Junio C Hamano
0 siblings, 1 reply; 35+ messages in thread
From: Jonathan Nieder @ 2016-07-08 17:58 UTC (permalink / raw)
To: Junio C Hamano
Cc: Stefan Beller, git, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
Hi,
Junio C Hamano wrote:
> Stefan Beller <sbeller@google.com> writes:
>>> More importantly, if we plan to make this configurable and not make
>>> the limit a hardwired constant of the wire protocol, it may be
>>> better to advertise push-options capability with the limit, e.g.
>>> "push-options=32" (or even "push-options=1024/32"), so that the
>>> client side can count and abort early?
Sorry to butt into the conversation late, but: I am not yet convinced.
Is the idea that if the push options were very large, this would save
the client from the cost of sending them? But this comes with a
downside: the server doesn't get to send an error message about where
the maximum number of push options can come from (e.g., with a link to
a page where the limit can be adjusted, or with an explanation of when
clients tend to run into this problem and what they should do
instead).
So I'd like to propose an alternative. What if the client tells the
server the number of push options early on (and possibly also a cap on
the length of those push options)? That way, the client doesn't have
to waste bit sending the push options but the server gets an
opportunity to send a helpful error message on sideband 3.
server> HEAD\0push-options ...
client> ... commands ...
client> push-options 2
client> my-first-option
client> my-second-option
Thanks,
Jonathan
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 17:58 ` Jonathan Nieder
@ 2016-07-08 18:39 ` Junio C Hamano
2016-07-08 18:57 ` Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2016-07-08 18:39 UTC (permalink / raw)
To: Jonathan Nieder
Cc: Stefan Beller, git, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
Jonathan Nieder <jrnieder@gmail.com> writes:
> Hi,
>
> Junio C Hamano wrote:
>> Stefan Beller <sbeller@google.com> writes:
>
>>>> More importantly, if we plan to make this configurable and not make
>>>> the limit a hardwired constant of the wire protocol, it may be
>>>> better to advertise push-options capability with the limit, e.g.
>>>> "push-options=32" (or even "push-options=1024/32"), so that the
>>>> client side can count and abort early?
>
> Sorry to butt into the conversation late, but: I am not yet convinced.
>
> Is the idea that if the push options were very large, this would save
> the client from the cost of sending them?
Not really. I have no strong opinion on the benefit of limiting
number/size. Stefan limited the number/size at the receiving end
and made receiving end die with its message. I was merely trying to
tweak the arrangement so that the sending end can complain with its
own message, possibly in its own language, especially because it was
unclear to me if the die() message on the receiving end would always
go back to the sending end correctly.
> But this comes with a
> downside: the server doesn't get to send an error message about
> where
> the maximum number of push options can come from (e.g., with a link to
> a page where the limit can be adjusted, or with an explanation of when
> clients tend to run into this problem and what they should do
> instead).
Hmm, interesting point. That would be better told by the receiving
end, as the way to configure it (if offered) would be different from
installation to installation.
> So I'd like to propose an alternative. What if the client tells the
> server the number of push options early on (and possibly also a cap on
> the length of those push options)? That way, the client doesn't have
> to waste bit sending the push options but the server gets an
> opportunity to send a helpful error message on sideband 3.
>
> server> HEAD\0push-options ...
> client> ... commands ...
> client> push-options 2
> client> my-first-option
> client> my-second-option
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 18:39 ` Junio C Hamano
@ 2016-07-08 18:57 ` Stefan Beller
2016-07-08 21:46 ` Jeff King
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-08 18:57 UTC (permalink / raw)
To: Junio C Hamano
Cc: Jonathan Nieder, git, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
On Fri, Jul 8, 2016 at 11:39 AM, Junio C Hamano <gitster@pobox.com> wrote:
> Jonathan Nieder <jrnieder@gmail.com> writes:
>
>> Hi,
>>
>> Junio C Hamano wrote:
>>> Stefan Beller <sbeller@google.com> writes:
>>
>>>>> More importantly, if we plan to make this configurable and not make
>>>>> the limit a hardwired constant of the wire protocol, it may be
>>>>> better to advertise push-options capability with the limit, e.g.
>>>>> "push-options=32" (or even "push-options=1024/32"), so that the
>>>>> client side can count and abort early?
>>
>> Sorry to butt into the conversation late, but: I am not yet convinced.
>>
>> Is the idea that if the push options were very large, this would save
>> the client from the cost of sending them?
>
> Not really. I have no strong opinion on the benefit of limiting
> number/size. Stefan limited the number/size at the receiving end
> and made receiving end die with its message.
Jeff claimed we'd need some sort of DoS protection for this feature,
so I considered just die-ing enough for an initial implementation.
> I was merely trying to
> tweak the arrangement so that the sending end can complain with its
> own message, possibly in its own language, especially because it was
> unclear to me if the die() message on the receiving end would always
> go back to the sending end correctly.
I'm currently implementing Jonathans suggestion as it seems to be a reasonable
trade off (client hasn't sent a lot of data when it is decided it
doesn't go through,
the server can complain with a reasonable error message, only downside: no
i18n localisation support on the client side as the server will
currently report the
error in English).
That method will make heavy use of rp_error that uses the side band for
communicating the actual error message.
>
>> But this comes with a
>> downside: the server doesn't get to send an error message about
>> where
>> the maximum number of push options can come from (e.g., with a link to
>> a page where the limit can be adjusted, or with an explanation of when
>> clients tend to run into this problem and what they should do
>> instead).
>
> Hmm, interesting point. That would be better told by the receiving
> end, as the way to configure it (if offered) would be different from
> installation to installation.
>
>> So I'd like to propose an alternative. What if the client tells the
>> server the number of push options early on (and possibly also a cap on
>> the length of those push options)? That way, the client doesn't have
>> to waste bit sending the push options but the server gets an
>> opportunity to send a helpful error message on sideband 3.
>>
>> server> HEAD\0push-options ...
>> client> ... commands ...
>> client> push-options 2
>> client> my-first-option
>> client> my-second-option
>
Another (slightly offtopic) observation:
If in the future we'll need to transmit push options >64k, instead of
splitting the push option to multiple packets, we could invent "large
packets". The current upper bound for packets is artificailly low, such
that the server is able to interleave sideband information with the actual
data in a fetch and have the client display the progress in a timely manner.
When pushing to the server we'd not need to have progress information
(the server doesn't care, and the client knows the size it is pushing).
As of today a packet consists of 4 bytes (hex characters) to indicate
the length and then the payload follows. So instead we could transmit
"v" (that is not a hex character) followed by a variable length integer for
the length and then the payload which has no upper bound.
In the release notes for 2.3 you wrote:
> * "git push" and "git fetch" did not communicate an overlong refname
> correctly. Now it uses 64kB sideband to accommodate longer ones.
That could also make use of these "large packets" instead.
Thanks,
Stefan
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 18:57 ` Stefan Beller
@ 2016-07-08 21:46 ` Jeff King
2016-07-08 22:17 ` Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Jeff King @ 2016-07-08 21:46 UTC (permalink / raw)
To: Stefan Beller
Cc: Junio C Hamano, Jonathan Nieder, git, Eric Wong, Dan Wang,
Dennis Kaarsemaker
On Fri, Jul 08, 2016 at 11:57:20AM -0700, Stefan Beller wrote:
> >> Sorry to butt into the conversation late, but: I am not yet convinced.
> >>
> >> Is the idea that if the push options were very large, this would save
> >> the client from the cost of sending them?
> >
> > Not really. I have no strong opinion on the benefit of limiting
> > number/size. Stefan limited the number/size at the receiving end
> > and made receiving end die with its message.
>
> Jeff claimed we'd need some sort of DoS protection for this feature,
> so I considered just die-ing enough for an initial implementation.
I do not think we need to worry too much about niceties for these
limits. The point is to protect servers from malicious nonsense, like
somebody sending gigabytes of push options, or trying to overflow a
buffer in a hook with a large value. If people are seeing these in
routine use, then the limits are set too low, and this should happen
roughly as often as a BUG assertion, and IMHO should be treated roughly
the same: don't bother with translation, and don't worry about
optimizing wasted bandwidth for this case. It won't happen enough to
matter.
-Peff
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 21:46 ` Jeff King
@ 2016-07-08 22:17 ` Stefan Beller
2016-07-08 22:21 ` Jeff King
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-08 22:17 UTC (permalink / raw)
To: Jeff King
Cc: Junio C Hamano, Jonathan Nieder, git, Eric Wong, Dan Wang,
Dennis Kaarsemaker
On Fri, Jul 8, 2016 at 2:46 PM, Jeff King <peff@peff.net> wrote:
> On Fri, Jul 08, 2016 at 11:57:20AM -0700, Stefan Beller wrote:
>
>> >> Sorry to butt into the conversation late, but: I am not yet convinced.
>> >>
>> >> Is the idea that if the push options were very large, this would save
>> >> the client from the cost of sending them?
>> >
>> > Not really. I have no strong opinion on the benefit of limiting
>> > number/size. Stefan limited the number/size at the receiving end
>> > and made receiving end die with its message.
>>
>> Jeff claimed we'd need some sort of DoS protection for this feature,
>> so I considered just die-ing enough for an initial implementation.
>
> I do not think we need to worry too much about niceties for these
> limits. The point is to protect servers from malicious nonsense, like
> somebody sending gigabytes of push options, or trying to overflow a
> buffer in a hook with a large value.
Agreed. This would speak for keeping the implementation as is.
>If people are seeing these in
> routine use, then the limits are set too low, and this should happen
> roughly as often as a BUG assertion, and IMHO should be treated roughly
> the same: don't bother with translation, and don't worry about
> optimizing wasted bandwidth for this case. It won't happen enough to
> matter.
Well the wasted band width is part of the server protection, no?
This would favor the idea Jonathan came up with:
server: I advertise push options
client: ok I want to use push options
client: I'll send you 1000 push options with upper bound of 1000M
server: It's a bit too much, eh?
* server quits
So this case only occurs for the (malicious?) corner case, where I
do not bother a translation.
But having the size announcement not in
the capability advertisement, but in the actual push options phase makes
sense to me as we do not want to clutter the capabilities with data that can
come later. We would only waste a little bit of band width, (the
initial ls-remote
and command list of the client).
Speaking of this, I can craft a malicious client that sends the
following command list
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
refs/heads/loooooooooooooooooong-ref
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
refs/heads/loooooooooooooooooong-ref
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
refs/heads/loooooooooooooooooong-ref
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
refs/heads/loooooooooooooooooong-ref
0000000000000000000000000000000000000000
0000000000000000000000000000000000000000
refs/heads/loooooooooooooooooong-ref
<repeat the above a few times>
0000
IIUC in the receive-pack code we would queue that up and the error checking
(two times null sha1? update of the same ref more than once?), is
done just after we send out the flush packet, i.e. when all commands
are received.
This would also result in sending gigabytes of junk as well as a
memory issue on the server
side?
The new push options design is actually neat in the way that the
client exactly says what it wants
and the server can reject early, but not cluttering the capability
advertisement.
Thanks,
Stefan
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 22:17 ` Stefan Beller
@ 2016-07-08 22:21 ` Jeff King
2016-07-08 22:29 ` Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Jeff King @ 2016-07-08 22:21 UTC (permalink / raw)
To: Stefan Beller
Cc: Junio C Hamano, Jonathan Nieder, git, Eric Wong, Dan Wang,
Dennis Kaarsemaker
On Fri, Jul 08, 2016 at 03:17:13PM -0700, Stefan Beller wrote:
> >If people are seeing these in
> > routine use, then the limits are set too low, and this should happen
> > roughly as often as a BUG assertion, and IMHO should be treated roughly
> > the same: don't bother with translation, and don't worry about
> > optimizing wasted bandwidth for this case. It won't happen enough to
> > matter.
>
> Well the wasted band width is part of the server protection, no?
Not if you stop receiving as soon as you hit the limits. Then of course
they can send up to the limit each time, but that is not a DoS. That is
things working as advertised.
> This would favor the idea Jonathan came up with:
>
> server: I advertise push options
> client: ok I want to use push options
> client: I'll send you 1000 push options with upper bound of 1000M
> server: It's a bit too much, eh?
> * server quits
>
> So this case only occurs for the (malicious?) corner case, where I
> do not bother a translation.
In the malicious case, the client says "I'll send you 10 push option
with an upper bound of 1024K", and then sends gigabytes anyway. Either
way the server has to react to what is sent, not what is promised.
-Peff
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 22:21 ` Jeff King
@ 2016-07-08 22:29 ` Stefan Beller
2016-07-08 22:35 ` Jeff King
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-08 22:29 UTC (permalink / raw)
To: Jeff King
Cc: Junio C Hamano, Jonathan Nieder, git, Eric Wong, Dan Wang,
Dennis Kaarsemaker
On Fri, Jul 8, 2016 at 3:21 PM, Jeff King <peff@peff.net> wrote:
> On Fri, Jul 08, 2016 at 03:17:13PM -0700, Stefan Beller wrote:
>
>> >If people are seeing these in
>> > routine use, then the limits are set too low, and this should happen
>> > roughly as often as a BUG assertion, and IMHO should be treated roughly
>> > the same: don't bother with translation, and don't worry about
>> > optimizing wasted bandwidth for this case. It won't happen enough to
>> > matter.
>>
>> Well the wasted band width is part of the server protection, no?
>
> Not if you stop receiving as soon as you hit the limits. Then of course
> they can send up to the limit each time, but that is not a DoS. That is
> things working as advertised.
>
>> This would favor the idea Jonathan came up with:
>>
>> server: I advertise push options
>> client: ok I want to use push options
>> client: I'll send you 1000 push options with upper bound of 1000M
>> server: It's a bit too much, eh?
>> * server quits
>>
>> So this case only occurs for the (malicious?) corner case, where I
>> do not bother a translation.
>
> In the malicious case, the client says "I'll send you 10 push option
> with an upper bound of 1024K", and then sends gigabytes anyway. Either
> way the server has to react to what is sent, not what is promised.
Well that is what the initial patch did via:
+ for (i = 0; i < max_options; i++) {
+ char *line;
+ int len;
+
+ line = packet_read_line(0, &len);
+
+ if (!line)
+ break;
+
+ if (len > max_size)
+ die("protocol error: server configuration allows push "
+ "options of size up to %d bytes", max_size);
+
+ len = strcspn(line, "\n");
+ line[len] = '\0';
+
+ string_list_append(ret, line);
+ }
+ if (i == max_options)
+ die("protocol error: server configuration only allows up "
+ "to %d push options", max_options);
I assume the die is an effective way to "stop receiving".
Thinking further about what you said, I think the initial selections of
max_size and max_options is sufficient and we only see those bounds in
the malicious case.
This discussion rather makes me wonder if we want to stick to the initial design
as it was easy and not overcomplicating things as we assume the abort case
doesn't occur often.
>
> -Peff
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 22:29 ` Stefan Beller
@ 2016-07-08 22:35 ` Jeff King
2016-07-08 22:43 ` Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Jeff King @ 2016-07-08 22:35 UTC (permalink / raw)
To: Stefan Beller
Cc: Junio C Hamano, Jonathan Nieder, git, Eric Wong, Dan Wang,
Dennis Kaarsemaker
On Fri, Jul 08, 2016 at 03:29:09PM -0700, Stefan Beller wrote:
> > In the malicious case, the client says "I'll send you 10 push option
> > with an upper bound of 1024K", and then sends gigabytes anyway. Either
> > way the server has to react to what is sent, not what is promised.
>
> Well that is what the initial patch did via:
>
> + for (i = 0; i < max_options; i++) {
> + char *line;
> + int len;
> +
> + line = packet_read_line(0, &len);
> +
> + if (!line)
> + break;
> +
> + if (len > max_size)
> + die("protocol error: server configuration allows push "
> + "options of size up to %d bytes", max_size);
> +
> + len = strcspn(line, "\n");
> + line[len] = '\0';
> +
> + string_list_append(ret, line);
> + }
> + if (i == max_options)
> + die("protocol error: server configuration only allows up "
> + "to %d push options", max_options);
>
> I assume the die is an effective way to "stop receiving".
>
> Thinking further about what you said, I think the initial selections of
> max_size and max_options is sufficient and we only see those bounds in
> the malicious case.
>
> This discussion rather makes me wonder if we want to stick to the initial design
> as it was easy and not overcomplicating things as we assume the abort case
> doesn't occur often.
Yes. I haven't been following the intermediate discussion and patches,
but I don't see anything wrong with the general design above. I think
you do need to use rp_error() to get the die message to the client for
non-ssh cases, though (that is a problem with other protocol-error
messages, too; I wonder if we should install a custom die handler, or
convert them all to some kind of rp_die()).
-Peff
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 22:35 ` Jeff King
@ 2016-07-08 22:43 ` Stefan Beller
2016-07-08 22:46 ` Jeff King
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-08 22:43 UTC (permalink / raw)
To: Jeff King
Cc: Junio C Hamano, Jonathan Nieder, git, Eric Wong, Dan Wang,
Dennis Kaarsemaker
On Fri, Jul 8, 2016 at 3:35 PM, Jeff King <peff@peff.net> wrote:
>
> Yes. I haven't been following the intermediate discussion and patches,
> but I don't see anything wrong with the general design above. I think
> you do need to use rp_error() to get the die message to the client for
> non-ssh cases, though (that is a problem with other protocol-error
> messages, too; I wonder if we should install a custom die handler, or
> convert them all to some kind of rp_die()).
Some of the rp_error messages do not want to die(), but most seem to
be ok when the rp_error would die.
I'll look into that.
>
> -Peff
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 22:43 ` Stefan Beller
@ 2016-07-08 22:46 ` Jeff King
2016-07-08 22:51 ` Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Jeff King @ 2016-07-08 22:46 UTC (permalink / raw)
To: Stefan Beller
Cc: Junio C Hamano, Jonathan Nieder, git, Eric Wong, Dan Wang,
Dennis Kaarsemaker
On Fri, Jul 08, 2016 at 03:43:27PM -0700, Stefan Beller wrote:
> On Fri, Jul 8, 2016 at 3:35 PM, Jeff King <peff@peff.net> wrote:
> >
> > Yes. I haven't been following the intermediate discussion and patches,
> > but I don't see anything wrong with the general design above. I think
> > you do need to use rp_error() to get the die message to the client for
> > non-ssh cases, though (that is a problem with other protocol-error
> > messages, too; I wonder if we should install a custom die handler, or
> > convert them all to some kind of rp_die()).
>
> Some of the rp_error messages do not want to die(), but most seem to
> be ok when the rp_error would die.
Sorry, I meant converting die() into:
rp_error(...);
die(...);
possibly via an rp_die() helper. The existing rp_error() cases would
remain untouched.
Installing a die handler could make that work automatically, though I
suspect it would lead to corner cases where we break protocol (e.g., if
we die() in the middle of writing out a packet).
-Peff
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 2/4] receive-pack: implement advertising and receiving push options
2016-07-08 22:46 ` Jeff King
@ 2016-07-08 22:51 ` Stefan Beller
0 siblings, 0 replies; 35+ messages in thread
From: Stefan Beller @ 2016-07-08 22:51 UTC (permalink / raw)
To: Jeff King
Cc: Junio C Hamano, Jonathan Nieder, git, Eric Wong, Dan Wang,
Dennis Kaarsemaker
On Fri, Jul 8, 2016 at 3:46 PM, Jeff King <peff@peff.net> wrote:
> Sorry, I meant converting die() into:
>
> rp_error(...);
> die(...);
>
> possibly via an rp_die() helper. The existing rp_error() cases would
> remain untouched.
Oh I see!
That makes a lot of sense.
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 3/4] push: accept push options
2016-07-07 1:12 [PATCHv3 0/4] Push options in C Git Stefan Beller
2016-07-07 1:12 ` [PATCH 1/4] push options: {pre,post}-receive hook learns about push options Stefan Beller
2016-07-07 1:12 ` [PATCH 2/4] receive-pack: implement advertising and receiving " Stefan Beller
@ 2016-07-07 1:12 ` Stefan Beller
2016-07-07 20:52 ` Junio C Hamano
2016-07-07 1:12 ` [PATCH 4/4] add a test for " Stefan Beller
3 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-07 1:12 UTC (permalink / raw)
To: gitster; +Cc: git, e, peff, dwwang, dennis, Stefan Beller
This implements everything that is required on the client side to make use
of push options from the porcelain push command.
Signed-off-by: Stefan Beller <sbeller@google.com>
---
Documentation/git-push.txt | 8 +++++++-
builtin/push.c | 16 +++++++++++++---
send-pack.c | 30 ++++++++++++++++++++++++++++++
send-pack.h | 3 +++
transport.c | 1 +
transport.h | 7 +++++++
6 files changed, 61 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 19f46b6..b0b1273 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -11,7 +11,7 @@ SYNOPSIS
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
- [-u | --set-upstream]
+ [-u | --set-upstream] [--push-option=<string>]
[--[no-]signed|--sign=(true|false|if-asked)]
[--force-with-lease[=<refname>[:<expect>]]]
[--no-verify] [<repository> [<refspec>...]]
@@ -156,6 +156,12 @@ already exists on the remote side.
Either all refs are updated, or on error, no refs are updated.
If the server does not support atomic pushes the push will fail.
+-L::
+--push-option::
+ Transmit the given string to the server, which passes them to
+ the pre-receive as well as the post-receive hook. Only C strings
+ containing no new lines are allowed.
+
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
Path to the 'git-receive-pack' program on the remote
diff --git a/builtin/push.c b/builtin/push.c
index 4e9e4db..1b5d205 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -353,7 +353,8 @@ static int push_with_options(struct transport *transport, int flags)
return 1;
}
-static int do_push(const char *repo, int flags)
+static int do_push(const char *repo, int flags,
+ const struct string_list *push_options)
{
int i, errs;
struct remote *remote = pushremote_get(repo);
@@ -376,6 +377,9 @@ static int do_push(const char *repo, int flags)
if (remote->mirror)
flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+ if (push_options->nr)
+ flags |= TRANSPORT_PUSH_OPTIONS;
+
if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
if (!strcmp(*refspec, "refs/tags/*"))
return error(_("--all and --tags are incompatible"));
@@ -406,13 +410,16 @@ static int do_push(const char *repo, int flags)
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
} else {
struct transport *transport =
transport_get(remote, NULL);
-
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
@@ -500,6 +507,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int push_cert = -1;
int rc;
const char *repo = NULL; /* default repository */
+ static struct string_list push_options = STRING_LIST_INIT_DUP;
+
struct option options[] = {
OPT__VERBOSITY(&verbosity),
OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")),
@@ -533,6 +542,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
PARSE_OPT_OPTARG, option_parse_push_signed },
OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
+ OPT_STRING_LIST('o', "push-option", &push_options, N_("server-specific"), N_("option to transmit")),
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@ -563,7 +573,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
- rc = do_push(repo, flags);
+ rc = do_push(repo, flags, &push_options);
if (rc == -1)
usage_with_options(push_usage, options);
else
diff --git a/send-pack.c b/send-pack.c
index 299d303..c943560 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -260,6 +260,7 @@ static int generate_push_cert(struct strbuf *req_buf,
const char *push_cert_nonce)
{
const struct ref *ref;
+ struct string_list_item *item;
char *signing_key = xstrdup(get_signing_key());
const char *cp, *np;
struct strbuf cert = STRBUF_INIT;
@@ -278,6 +279,12 @@ static int generate_push_cert(struct strbuf *req_buf,
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
strbuf_addstr(&cert, "\n");
+ if (args->push_options) {
+ for_each_string_list_item(item, args->push_options)
+ strbuf_addf(&cert, "push-option %s\n", item->string);
+ strbuf_addstr(&cert, "\n");
+ }
+
for (ref = remote_refs; ref; ref = ref->next) {
if (check_to_send_update(ref, args) < 0)
continue;
@@ -370,6 +377,8 @@ int send_pack(struct send_pack_args *args,
int agent_supported = 0;
int use_atomic = 0;
int atomic_supported = 0;
+ int use_push_options = 0;
+ int push_options_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
@@ -392,6 +401,8 @@ int send_pack(struct send_pack_args *args,
args->use_thin_pack = 0;
if (server_supports("atomic"))
atomic_supported = 1;
+ if (server_supports("push-options"))
+ push_options_supported = 1;
if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
int len;
@@ -418,6 +429,11 @@ int send_pack(struct send_pack_args *args,
use_atomic = atomic_supported && args->atomic;
+ if (args->push_options && !push_options_supported)
+ die(_("the receiving end does not support push options"));
+
+ use_push_options = push_options_supported && args->push_options;
+
if (status_report)
strbuf_addstr(&cap_buf, " report-status");
if (use_sideband)
@@ -426,6 +442,8 @@ int send_pack(struct send_pack_args *args,
strbuf_addstr(&cap_buf, " quiet");
if (use_atomic)
strbuf_addstr(&cap_buf, " atomic");
+ if (use_push_options)
+ strbuf_addstr(&cap_buf, " push-options");
if (agent_supported)
strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
@@ -512,6 +530,18 @@ int send_pack(struct send_pack_args *args,
strbuf_release(&req_buf);
strbuf_release(&cap_buf);
+ if (use_push_options) {
+ struct string_list_item *item;
+ struct strbuf sb = STRBUF_INIT;
+
+ for_each_string_list_item(item, args->push_options)
+ packet_buf_write(&sb, "%s", item->string);
+
+ write_or_die(out, sb.buf, sb.len);
+ packet_flush(out);
+ strbuf_release(&sb);
+ }
+
if (use_sideband && cmds_sent) {
memset(&demux, 0, sizeof(demux));
demux.proc = sideband_demux;
diff --git a/send-pack.h b/send-pack.h
index 57f222a..67fc40f 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -1,6 +1,8 @@
#ifndef SEND_PACK_H
#define SEND_PACK_H
+#include "string-list.h"
+
/* Possible values for push_cert field in send_pack_args. */
#define SEND_PACK_PUSH_CERT_NEVER 0
#define SEND_PACK_PUSH_CERT_IF_ASKED 1
@@ -21,6 +23,7 @@ struct send_pack_args {
push_cert:2,
stateless_rpc:1,
atomic:1;
+ const struct string_list *push_options;
};
struct option;
diff --git a/transport.c b/transport.c
index 095e61f..0298be1 100644
--- a/transport.c
+++ b/transport.c
@@ -510,6 +510,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
+ args.push_options = transport->push_options;
args.url = transport->url;
if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
diff --git a/transport.h b/transport.h
index c681408..6fe3485 100644
--- a/transport.h
+++ b/transport.h
@@ -48,6 +48,12 @@ struct transport {
*/
unsigned cloning : 1;
+ /*
+ * These strings will be passed to the {pre, post}-receive hook,
+ * on the remote side, if both sides support the push options capability.
+ */
+ const struct string_list *push_options;
+
/**
* Returns 0 if successful, positive if the option is not
* recognized or is inapplicable, and negative if the option
@@ -134,6 +140,7 @@ struct transport {
#define TRANSPORT_PUSH_CERT_ALWAYS 2048
#define TRANSPORT_PUSH_CERT_IF_ASKED 4096
#define TRANSPORT_PUSH_ATOMIC 8192
+#define TRANSPORT_PUSH_OPTIONS 16384
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
--
2.9.0.141.gd59d3e9.dirty
^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 3/4] push: accept push options
2016-07-07 1:12 ` [PATCH 3/4] push: accept " Stefan Beller
@ 2016-07-07 20:52 ` Junio C Hamano
2016-07-08 22:59 ` Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2016-07-07 20:52 UTC (permalink / raw)
To: Stefan Beller; +Cc: git, e, peff, dwwang, dennis
Stefan Beller <sbeller@google.com> writes:
> +-L::
> +--push-option::
> + Transmit the given string to the server, which passes them to
> + the pre-receive as well as the post-receive hook. Only C strings
> + containing no new lines are allowed.
This is to affect what happens at the remote end, so I would have
understood "-R". I also would have understood "-P" as a short-hand
for "--push-option". What is the justification of "-L"?
What does "C strings" mean? Did you mean to say "A sequence of
bytes excluding NUL is passed verbatim"?
I do not think I saw anything in the code I reviewed so far that
requires "no LF" limitation.
... Ahh, OK, you want to make sure that push-options are
one-per-line in the push certificate. While I do not think it is
absolutely necessary, starting with a possibly tighter than
necessary limitation is much better than starting loose and having
to tighten it later.
> } else {
> struct transport *transport =
> transport_get(remote, NULL);
> -
> + if (flags & TRANSPORT_PUSH_OPTIONS)
> + transport->push_options = push_options;
The result would be easier to read without the removal of a blank
that separates decl/defn and stmt here.
> @@ -533,6 +542,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
> 0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
> PARSE_OPT_OPTARG, option_parse_push_signed },
> OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
> + OPT_STRING_LIST('o', "push-option", &push_options, N_("server-specific"), N_("option to transmit")),
Here it seems to expect "-o". If we really want a short option,
"-o" would probably be OK, as I do not think "git push" wants to
have "send the output to this file" option.
> diff --git a/send-pack.c b/send-pack.c
> index 299d303..c943560 100644
> --- a/send-pack.c
> +++ b/send-pack.c
> @@ -260,6 +260,7 @@ static int generate_push_cert(struct strbuf *req_buf,
> const char *push_cert_nonce)
> {
> const struct ref *ref;
> + struct string_list_item *item;
> char *signing_key = xstrdup(get_signing_key());
> const char *cp, *np;
> struct strbuf cert = STRBUF_INIT;
> @@ -278,6 +279,12 @@ static int generate_push_cert(struct strbuf *req_buf,
> strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
> strbuf_addstr(&cert, "\n");
>
> + if (args->push_options) {
> + for_each_string_list_item(item, args->push_options)
> + strbuf_addf(&cert, "push-option %s\n", item->string);
> + strbuf_addstr(&cert, "\n");
Why the extra blank?
I would actually have expected to see
certificate version ...
pusher ...
<datestamp>
pushee ... # optional
nonce ... # optional
push-option ... # optional
push-option ... # optional
<old> <new> <name>
...
by adding this between the two lines in the pre-context of this
hunk, i.e.
if (push_cert_nonce[0])
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
if (args->push_options)
for_each_string_list_item(item, args->push_options)
strbuf_addf(&cert, "push-option %s\n", item->string);
strbuf_addstr(&cert, "\n");
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 3/4] push: accept push options
2016-07-07 20:52 ` Junio C Hamano
@ 2016-07-08 22:59 ` Stefan Beller
2016-07-11 18:42 ` Junio C Hamano
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-08 22:59 UTC (permalink / raw)
To: Junio C Hamano; +Cc: git, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
On Thu, Jul 7, 2016 at 1:52 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Stefan Beller <sbeller@google.com> writes:
>
>> +-L::
>> +--push-option::
>> + Transmit the given string to the server, which passes them to
>> + the pre-receive as well as the post-receive hook. Only C strings
>> + containing no new lines are allowed.
>
> This is to affect what happens at the remote end, so I would have
> understood "-R". I also would have understood "-P" as a short-hand
> for "--push-option". What is the justification of "-L"?
It was made up. The actual code took -o for option. Changed that.
>
> What does "C strings" mean? Did you mean to say "A sequence of
> bytes excluding NUL is passed verbatim"?
>
> I do not think I saw anything in the code I reviewed so far that
> requires "no LF" limitation.
It is enforced server side, but an additional
client side enforcement may be better indeed.
The rationale for no enforcement on the client side is an easier way
forward if we allow it on the server as the client would "just work"
and it's up to the server to complain.
That makes me wonder if we want to document that, i.e.:
-o::
--push-option::
Transmit the given argument to the server, which passes them to
the pre-receive as well as the post-receive hook. As it is up to the
server to react on these push options, it may reject push options
that contain new line or NUL characters. .
>
> ... Ahh, OK, you want to make sure that push-options are
> one-per-line in the push certificate. While I do not think it is
> absolutely necessary, starting with a possibly tighter than
> necessary limitation is much better than starting loose and having
> to tighten it later.
This is not what I had in mind, but rather the pain of dealing with multi line
environment variables.
>> transport_get(remote, NULL);
>> -
>> + if (flags & TRANSPORT_PUSH_OPTIONS)
>> + transport->push_options = push_options;
>
> The result would be easier to read without the removal of a blank
> that separates decl/defn and stmt here.
ok
>> + OPT_STRING_LIST('o', "push-option", &push_options, N_("server-specific"), N_("option to transmit")),
>
> Here it seems to expect "-o". If we really want a short option,
> "-o" would probably be OK, as I do not think "git push" wants to
> have "send the output to this file" option.
>
Ok, will update the documentation.
>
> by adding this between the two lines in the pre-context of this
> hunk, i.e.
>
> if (push_cert_nonce[0])
> strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
> if (args->push_options)
> for_each_string_list_item(item, args->push_options)
> strbuf_addf(&cert, "push-option %s\n", item->string);
> strbuf_addstr(&cert, "\n");
>
makes sense.
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 3/4] push: accept push options
2016-07-08 22:59 ` Stefan Beller
@ 2016-07-11 18:42 ` Junio C Hamano
0 siblings, 0 replies; 35+ messages in thread
From: Junio C Hamano @ 2016-07-11 18:42 UTC (permalink / raw)
To: Stefan Beller; +Cc: git, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
Stefan Beller <sbeller@google.com> writes:
> This is not what I had in mind, but rather the pain of dealing with multi line
> environment variables.
Hmph, is that painful?
$ cmd "$VAR"
inside double quotes would do multi-line just fine, I think.
But I think it is OK (and probably preferrable) to limit the options
to be a collection of one-liners.
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 4/4] add a test for push options
2016-07-07 1:12 [PATCHv3 0/4] Push options in C Git Stefan Beller
` (2 preceding siblings ...)
2016-07-07 1:12 ` [PATCH 3/4] push: accept " Stefan Beller
@ 2016-07-07 1:12 ` Stefan Beller
2016-07-07 19:51 ` Junio C Hamano
3 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-07 1:12 UTC (permalink / raw)
To: gitster; +Cc: git, e, peff, dwwang, dennis, Stefan Beller
The functions `mk_repo_pair` as well as `test_refs` are borrowed from
t5543-atomic-push, with additional hooks installed.
Signed-off-by: Stefan Beller <sbeller@google.com>
---
t/t5544-push-options.sh | 101 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 101 insertions(+)
create mode 100755 t/t5544-push-options.sh
diff --git a/t/t5544-push-options.sh b/t/t5544-push-options.sh
new file mode 100755
index 0000000..8dd3c8e
--- /dev/null
+++ b/t/t5544-push-options.sh
@@ -0,0 +1,101 @@
+#!/bin/sh
+
+test_description='pushing to a repository using push options'
+
+. ./test-lib.sh
+
+mk_repo_pair () {
+ rm -rf workbench upstream &&
+ test_create_repo upstream &&
+ test_create_repo workbench &&
+ (
+ cd upstream &&
+ git config receive.denyCurrentBranch warn &&
+ mkdir -p .git/hooks &&
+ cat >.git/hooks/pre-receive <<-'EOF' &&
+ #!/bin/sh
+ if test -n "$GIT_PUSH_OPTION_COUNT"; then
+ i=0
+ >hooks/pre-receive.push_options
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do
+ eval "value=\$GIT_PUSH_OPTION_$i"
+ echo $value >>hooks/pre-receive.push_options
+ i=$((i + 1))
+ done
+ fi
+ EOF
+ chmod u+x .git/hooks/pre-receive
+
+ cat >.git/hooks/post-receive <<-'EOF' &&
+ #!/bin/sh
+ if test -n "$GIT_PUSH_OPTION_COUNT"; then
+ i=0
+ >hooks/post-receive.push_options
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do
+ eval "value=\$GIT_PUSH_OPTION_$i"
+ echo $value >>hooks/post-receive.push_options
+ i=$((i + 1))
+ done
+ fi
+ EOF
+ chmod u+x .git/hooks/post-receive
+ ) &&
+ (
+ cd workbench &&
+ git remote add up ../upstream
+ )
+}
+
+# Compare the ref ($1) in upstream with a ref value from workbench ($2)
+# i.e. test_refs second HEAD@{2}
+test_refs () {
+ test $# = 2 &&
+ git -C upstream rev-parse --verify "$1" >expect &&
+ git -C workbench rev-parse --verify "$2" >actual &&
+ test_cmp expect actual
+}
+
+test_expect_success 'one push option works for a single branch' '
+ mk_repo_pair &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ git push --push-option=asdf up master
+ ) &&
+ test_refs master master &&
+ echo "asdf" >expect &&
+ test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+ test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_expect_success 'push option denied by remote' '
+ mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions false &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ test_must_fail git push --push-option=asdf up master
+ ) &&
+ test_refs master HEAD@{1}
+'
+
+test_expect_success 'two push options work' '
+ mk_repo_pair &&
+ (
+ cd workbench &&
+ test_commit one &&
+ git push --mirror up &&
+ test_commit two &&
+ git push --push-option=asdf --push-option="more structured text" up master
+ ) &&
+ test_refs master master &&
+ printf "asdf\nmore structured text\n" >expect &&
+ test_cmp expect upstream/.git/hooks/pre-receive.push_options &&
+ test_cmp expect upstream/.git/hooks/post-receive.push_options
+'
+
+test_done
--
2.9.0.141.gd59d3e9.dirty
^ permalink raw reply related [flat|nested] 35+ messages in thread
* Re: [PATCH 4/4] add a test for push options
2016-07-07 1:12 ` [PATCH 4/4] add a test for " Stefan Beller
@ 2016-07-07 19:51 ` Junio C Hamano
2016-07-07 20:01 ` Junio C Hamano
0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2016-07-07 19:51 UTC (permalink / raw)
To: Stefan Beller; +Cc: git, e, peff, dwwang, dennis
Stefan Beller <sbeller@google.com> writes:
> The functions `mk_repo_pair` as well as `test_refs` are borrowed from
> t5543-atomic-push, with additional hooks installed.
>
> Signed-off-by: Stefan Beller <sbeller@google.com>
> ---
> t/t5544-push-options.sh | 101 ++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 101 insertions(+)
> create mode 100755 t/t5544-push-options.sh
FYI:
Applying: add a test for push options
Test number t5544 already taken
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 4/4] add a test for push options
2016-07-07 19:51 ` Junio C Hamano
@ 2016-07-07 20:01 ` Junio C Hamano
2016-07-07 21:51 ` Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Junio C Hamano @ 2016-07-07 20:01 UTC (permalink / raw)
To: Stefan Beller
Cc: Git Mailing List, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
On Thu, Jul 7, 2016 at 12:51 PM, Junio C Hamano <gitster@pobox.com> wrote:
> Stefan Beller <sbeller@google.com> writes:
>
>> The functions `mk_repo_pair` as well as `test_refs` are borrowed from
>> t5543-atomic-push, with additional hooks installed.
>>
>> Signed-off-by: Stefan Beller <sbeller@google.com>
>> ---
>> t/t5544-push-options.sh | 101 ++++++++++++++++++++++++++++++++++++++++++++++++
>> 1 file changed, 101 insertions(+)
>> create mode 100755 t/t5544-push-options.sh
>
> FYI:
>
> Applying: add a test for push options
> Test number t5544 already taken
>
I'll just move it over to t5545; no need to resend.
^ permalink raw reply [flat|nested] 35+ messages in thread
* Re: [PATCH 4/4] add a test for push options
2016-07-07 20:01 ` Junio C Hamano
@ 2016-07-07 21:51 ` Stefan Beller
0 siblings, 0 replies; 35+ messages in thread
From: Stefan Beller @ 2016-07-07 21:51 UTC (permalink / raw)
To: Junio C Hamano
Cc: Git Mailing List, Eric Wong, Jeff King, Dan Wang, Dennis Kaarsemaker
On Thu, Jul 7, 2016 at 1:01 PM, Junio C Hamano <gitster@pobox.com> wrote:
> On Thu, Jul 7, 2016 at 12:51 PM, Junio C Hamano <gitster@pobox.com> wrote:
>> Stefan Beller <sbeller@google.com> writes:
>>
>>> The functions `mk_repo_pair` as well as `test_refs` are borrowed from
>>> t5543-atomic-push, with additional hooks installed.
>>>
>>> Signed-off-by: Stefan Beller <sbeller@google.com>
>>> ---
>>> t/t5544-push-options.sh | 101 ++++++++++++++++++++++++++++++++++++++++++++++++
>>> 1 file changed, 101 insertions(+)
>>> create mode 100755 t/t5544-push-options.sh
>>
>> FYI:
>>
>> Applying: add a test for push options
>> Test number t5544 already taken
>>
>
> I'll just move it over to t5545; no need to resend.
I'd resend because of the the first three patches anyway,
so I can include a renamed version of tests, too.
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCHv7 0/4] Push options
@ 2016-07-14 21:49 Stefan Beller
2016-07-14 21:49 ` [PATCH 3/4] push: accept push options Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-14 21:49 UTC (permalink / raw)
To: git, gitster; +Cc: dwwang, e, peff, dennis, jrnieder, Stefan Beller
Jeff,
here is the more idiomatic way.
Thanks,
Stefan
diff to v6:
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 9bb9afc..3c9360a 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -1499,11 +1499,8 @@ static struct command *read_head_info(struct sha1_array *shallow)
return commands;
}
-static struct string_list *read_push_options(void)
+static void read_push_options(struct string_list *options)
{
- struct string_list *ret = xmalloc(sizeof(*ret));
- string_list_init(ret, 1);
-
while (1) {
char *line;
int len;
@@ -1513,10 +1510,8 @@ static struct string_list *read_push_options(void)
if (!line)
break;
- string_list_append(ret, line);
+ string_list_append(options, line);
}
-
- return ret;
}
static const char *parse_pack_header(struct pack_header *hdr)
@@ -1804,10 +1799,10 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
if ((commands = read_head_info(&shallow)) != NULL) {
const char *unpack_status = NULL;
- struct string_list *push_options = NULL;
+ struct string_list push_options = STRING_LIST_INIT_DUP;
if (use_push_options)
- push_options = read_push_options();
+ read_push_options(&push_options);
prepare_shallow_info(&si, &shallow);
if (!si.nr_ours && !si.nr_theirs)
@@ -1817,18 +1812,16 @@ int cmd_receive_pack(int argc, const char **argv, const char *prefix)
update_shallow_info(commands, &si, &ref);
}
execute_commands(commands, unpack_status, &si,
- push_options);
+ &push_options);
if (pack_lockfile)
unlink_or_warn(pack_lockfile);
if (report_status)
report(commands, unpack_status);
run_receive_hook(commands, "post-receive", 1,
- push_options);
+ &push_options);
run_update_post_hook(commands);
- if (push_options) {
- string_list_clear(push_options, 0);
- free(push_options);
- }
+ if (push_options.nr)
+ string_list_clear(&push_options, 0);
if (auto_gc) {
const char *argv_gc_auto[] = {
"gc", "--auto", "--quiet", NULL,
v6 consisted of 2/4 only:
Junio,
please replace v5 2/4 with this patch (I only resend this single patch
as the other 3 remain as is).
This only changes read_push_options to not care at all about any
limitations.
Thanks,
Stefan
# interdiff to v5:
# diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
# index 917ac18..9bb9afc 100644
# --- a/builtin/receive-pack.c
# +++ b/builtin/receive-pack.c
# @@ -1505,7 +1505,7 @@ static struct string_list *read_push_options(void)
# string_list_init(ret, 1);
#
# while (1) {
# - char *line, *lf;
# + char *line;
# int len;
#
# line = packet_read_line(0, &len);
# @@ -1513,30 +1513,6 @@ static struct string_list *read_push_options(void)
# if (!line)
# break;
#
# - /*
# - * NEEDSWORK: expose the limitations to be configurable;
# - * Once the limit can be lifted, include a way for payloads
# - * larger than one pkt, e.g use last byte to indicate if
# - * the push option continues in the next packet or implement
# - * larger packets.
# - */
# - if (len > LARGE_PACKET_MAX - 1) {
# - /*
# - * NEEDSWORK: The error message in die(..) is not
# - * transmitted in call cases, so ideally all die(..)
# - * calls are prefixed with rp_error and then we can
# - * combine rp_error && die into one helper function.
# - */
# - rp_error("protocol error: server configuration allows push "
# - "options of size up to %d bytes",
# - LARGE_PACKET_MAX - 1);
# - die("protocol error: push options too large");
# - }
# -
# - lf = strchr(line, '\n');
# - if (lf)
# - *lf = '\0';
# -
# string_list_append(ret, line);
# }
v5:
Jeff wrote:
> Junio wrote:
>> I think those extra knobs can come later. If we are not going to
>> limit with max_options in the end, however, wouldn't it be more
>> natural for the initial iteration without any configuration not to
>> have hard-coded max_options at all?
>
> Yeah, I am OK with adding restrictive knobs later as a separate topic.
> As Stefan notes, upstream does not have the other knobs anyway, and IIRC
> the push-options feature is not even enabled by default.
>
> -Peff
* now it actually is not a default. ;)
* removed knobs, but instead we only reject at > LARGE_PACKET_MAX - 1,
Thanks,
Stefan
v5:
git diff origin/sb/push-options:
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 4d8041a..917ac18 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -44,7 +44,7 @@ static struct strbuf fsck_msg_types = STRBUF_INIT;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int advertise_atomic_push = 1;
-static int advertise_push_options = 1;
+static int advertise_push_options;
static int unpack_limit = 100;
static int report_status;
static int use_sideband;
@@ -1501,24 +1501,11 @@ static struct command *read_head_info(struct sha1_array *shallow)
static struct string_list *read_push_options(void)
{
- int i;
struct string_list *ret = xmalloc(sizeof(*ret));
- /* NEEDSWORK: expose the limitations to be configurable. */
- int max_options = 32;
-
- /*
- * NEEDSWORK: expose the limitations to be configurable;
- * Once the limit can be lifted, include a way for payloads
- * larger than one pkt, e.g allow a payload of up to
- * LARGE_PACKET_MAX - 1 only, and reserve the last byte
- * to indicate whether the next pkt continues with this
- * push option.
- */
- int max_size = 1024;
-
string_list_init(ret, 1);
- for (i = 0; i < max_options; i++) {
- char *line;
+
+ while (1) {
+ char *line, *lf;
int len;
line = packet_read_line(0, &len);
@@ -1526,7 +1513,14 @@ static struct string_list *read_push_options(void)
if (!line)
break;
- if (len > max_size) {
+ /*
+ * NEEDSWORK: expose the limitations to be configurable;
+ * Once the limit can be lifted, include a way for payloads
+ * larger than one pkt, e.g use last byte to indicate if
+ * the push option continues in the next packet or implement
+ * larger packets.
+ */
+ if (len > LARGE_PACKET_MAX - 1) {
/*
* NEEDSWORK: The error message in die(..) is not
* transmitted in call cases, so ideally all die(..)
@@ -1534,20 +1528,17 @@ static struct string_list *read_push_options(void)
* combine rp_error && die into one helper function.
*/
rp_error("protocol error: server configuration allows push "
- "options of size up to %d bytes", max_size);
+ "options of size up to %d bytes",
+ LARGE_PACKET_MAX - 1);
die("protocol error: push options too large");
}
- len = strcspn(line, "\n");
- line[len] = '\0';
+ lf = strchr(line, '\n');
+ if (lf)
+ *lf = '\0';
string_list_append(ret, line);
}
- if (i == max_options) {
- rp_error("protocol error: server configuration only allows up "
- "to %d push options", max_options);
- die("protocol error: push options too large");
- }
return ret;
}
diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh
index 8dd3c8e..ea813b9 100755
--- a/t/t5545-push-options.sh
+++ b/t/t5545-push-options.sh
@@ -57,6 +57,7 @@ test_refs () {
test_expect_success 'one push option works for a single branch' '
mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
(
cd workbench &&
test_commit one &&
@@ -85,6 +86,7 @@ test_expect_success 'push option denied by remote' '
test_expect_success 'two push options work' '
mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
(
cd workbench &&
test_commit one &&
cover letter v4:
Thanks Junio, Jeff, Jonathan for discussion and feedback!
I went over the emails again and we seem to agree that the initial design (in v3)
was sane and the error messages and reporting for corner cases were to be
dismissed as "it happens as often as 'BUG:' messages appear, so let's not care
too deeply now".
Thanks,
Stefan
This is a diff against a modified v3 (it's actually origin/sb/push-options):
diff --git a/Documentation/config.txt b/Documentation/config.txt
index df1b314..25b5db1 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2410,13 +2410,13 @@ rebase.instructionFormat
receive.advertiseAtomic::
By default, git-receive-pack will advertise the atomic push
- capability to its clients. If you don't want this capability
- to be advertised, set this variable to false.
+ capability to its clients. If you don't want to advertise this
+ capability, set this variable to false.
receive.advertisePushOptions::
- By default, git-receive-pack will advertise the push options capability
- to its clients. If you don't want this capability
- to be advertised, set this variable to false.
+ By default, git-receive-pack will advertise the push options
+ capability to its clients. If you don't want to advertise this
+ capability, set this variable to false.
receive.autogc::
By default, git-receive-pack will run "git-gc --auto" after
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index b0b1273..e960258 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -156,11 +156,11 @@ already exists on the remote side.
Either all refs are updated, or on error, no refs are updated.
If the server does not support atomic pushes the push will fail.
--L::
+-o::
--push-option::
Transmit the given string to the server, which passes them to
- the pre-receive as well as the post-receive hook. Only C strings
- containing no new lines are allowed.
+ the pre-receive as well as the post-receive hook. The given string
+ must not contain a NUL or LF character.
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index c875cde..9565dc3 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -247,8 +247,14 @@ Both standard output and standard error output are forwarded to
'git send-pack' on the other end, so you can simply `echo` messages
for the user.
-The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
-and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
+The number of push options given on the command line of
+`git push --push-option=...` can be read from the environment
+variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
+found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
+If it is negotiated to not use the push options phase, the
+environment variables will not be set. If the client selects
+to use push options, but doesn't transmit any, the count variable
+will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
[[update]]
update
@@ -325,8 +331,14 @@ a sample script `post-receive-email` provided in the `contrib/hooks`
directory in Git distribution, which implements sending commit
emails.
-The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
-and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
+The number of push options given on the command line of
+`git push --push-option=...` can be read from the environment
+variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
+found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
+If it is negotiated to not use the push options phase, the
+environment variables will not be set. If the client selects
+to use push options, but doesn't transmit any, the count variable
+will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
[[post-update]]
post-update
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index b71eda9..4c28d3a 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -256,10 +256,11 @@ updated or none.
push-options
------------
-If the server sends the 'push-options' capability it is capable to accept
-push options after the update commands have been sent. If the pushing client
-requests this capability, the server will pass the options to the pre and post
-receive hooks that process this push request.
+If the server sends the 'push-options' capability it is able to accept
+push options after the update commands have been sent, but before the
+packfile is streamed. If the pushing client requests this capability,
+the server will pass the options to the pre- and post- receive hooks
+that process this push request.
allow-tip-sha1-in-want
----------------------
diff --git a/builtin/push.c b/builtin/push.c
index 1b5d205..3bb9d6b 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -508,6 +508,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int rc;
const char *repo = NULL; /* default repository */
static struct string_list push_options = STRING_LIST_INIT_DUP;
+ static struct string_list_item *item;
struct option options[] = {
OPT__VERBOSITY(&verbosity),
@@ -573,6 +574,10 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
+ for_each_string_list_item(item, &push_options)
+ if (strchr(item->string, '\n'))
+ die(_("push options must not have new line characters"));
+
rc = do_push(repo, flags, &push_options);
if (rc == -1)
usage_with_options(push_usage, options);
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e71041a..754db6e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -214,12 +214,12 @@ static void show_ref(const char *path, const unsigned char *sha1)
"report-status delete-refs side-band-64k quiet");
if (advertise_atomic_push)
strbuf_addstr(&cap, " atomic");
- if (advertise_push_options)
- strbuf_addstr(&cap, " push-options");
if (prefer_ofs_delta)
strbuf_addstr(&cap, " ofs-delta");
if (push_cert_nonce)
strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
+ if (advertise_push_options)
+ strbuf_addstr(&cap, " push-options");
strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
packet_write(1, "%s %s%c%s\n",
sha1_to_hex(sha1), path, 0, cap.buf);
@@ -584,7 +584,7 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
- if (feed_state && feed_state->push_options) {
+ if (feed_state->push_options) {
int i;
for (i = 0; i < feed_state->push_options->nr; i++)
argv_array_pushf(&proc.env_array,
@@ -592,7 +592,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
feed_state->push_options->items[i].string);
argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
feed_state->push_options->nr);
- }
+ } else
+ argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
@@ -1498,7 +1499,7 @@ static struct command *read_head_info(struct sha1_array *shallow)
return commands;
}
-static struct string_list *read_push_options()
+static struct string_list *read_push_options(void)
{
int i;
struct string_list *ret = xmalloc(sizeof(*ret));
@@ -1526,18 +1527,28 @@ static struct string_list *read_push_options()
if (!line)
break;
- if (len > max_size)
- die("protocol error: server configuration allows push "
- "options of size up to %d bytes", max_size);
+ if (len > max_size) {
+ /*
+ * NEEDSWORK: The error message in die(..) is not
+ * transmitted in call cases, so ideally all die(..)
+ * calls are prefixed with rp_error and then we can
+ * combine rp_error && die into one helper function.
+ */
+ rp_error("protocol error: server configuration allows push "
+ "options of size up to %d bytes", max_size);
+ die("protocol error: push options too large");
+ }
len = strcspn(line, "\n");
line[len] = '\0';
string_list_append(ret, line);
}
- if (i == max_options)
- die("protocol error: server configuration only allows up "
+ if (i == max_options) {
+ rp_error("protocol error: server configuration only allows up "
"to %d push options", max_options);
+ die("protocol error: push options too large");
+ }
return ret;
}
diff --git a/send-pack.c b/send-pack.c
index c943560..3a842ac 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -277,13 +277,10 @@ static int generate_push_cert(struct strbuf *req_buf,
}
if (push_cert_nonce[0])
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
- strbuf_addstr(&cert, "\n");
-
- if (args->push_options) {
+ if (args->push_options)
for_each_string_list_item(item, args->push_options)
strbuf_addf(&cert, "push-option %s\n", item->string);
- strbuf_addstr(&cert, "\n");
- }
+ strbuf_addstr(&cert, "\n");
for (ref = remote_refs; ref; ref = ref->next) {
if (check_to_send_update(ref, args) < 0)
diff --git a/templates/hooks--pre-receive.sample b/templates/hooks--pre-receive.sample
index e4d3edc..a1fd29e 100644
--- a/templates/hooks--pre-receive.sample
+++ b/templates/hooks--pre-receive.sample
@@ -6,13 +6,15 @@
#
# To enable this hook, rename this file to "pre-receive".
-if test -n "$GIT_PUSH_OPTION_COUNT"; then
+if test -n "$GIT_PUSH_OPTION_COUNT"
+then
i=0
- while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
+ do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
- echo "echo from the pre-receive-hook ${value#*=}" >&2
+ echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
Cover letter v3:
================
This is not marked for RFC any more, as I do not recall any open points
left for discussion. This addresses the only reply from Eric Wong on patch 3:
diff to v2:
diff --git a/send-pack.c b/send-pack.c
index e328276..c943560 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -536,7 +536,8 @@ int send_pack(struct send_pack_args *args,
for_each_string_list_item(item, args->push_options)
packet_buf_write(&sb, "%s", item->string);
- write_or_die(out, sb.buf, sb.len);
+
+ write_or_die(out, sb.buf, sb.len);
packet_flush(out);
strbuf_release(&sb);
}
diff --git a/transport.c b/transport.c
index 598bd1f..0298be1 100644
--- a/transport.c
+++ b/transport.c
@@ -641,7 +641,6 @@ struct transport *transport_get(struct remote *remote, const char *url)
struct transport *ret = xcalloc(1, sizeof(*ret));
ret->progress = isatty(2);
- ret->push_options = NULL;
if (!remote)
die("No remote provided to transport_get()");
Cover letter v2:
================
Allow a user to pass information along a push to the pre/post-receive hook
on the remote.
Jeff writes on v1:
> Whereas in Dennis's patches, it was about specific information directly
> related to the act of pushing.
This allows to transmit arbitrary information as the backends of $VENDOR
may have different options available related to the direct act of pushing.
Thanks,
Stefan
Cover letter v1:
================
Allow a user to pass information along a push to the pre/post-receive hook
on the remote.
When using a remote that is more than just a plain Git host (e.g. Gerrit,
Git{hub/lab}, etc) this may become more obvious: The (server backend specific)
push options can instruct the server to:
* open a pull request
* send out emails asking for review
* (un)trigger continuous integration
* set priority for continuous integration (i.e. bots pushing may ask to be
treated with lower priority compared to humans)
* ...
Most of these actions can be done on the client side as well,
but in these remote-centric workflows it is easier to do that on the remote,
which is why we need to transport the information there.
More concrete examples:
* When you want a change in Gerrit to be submitted to refs/heads/master, you
push instead to a magic branch refs/for/master and Gerrit will create a change
for you (similar to a pull request). Instead we could imagine that you push
to a magical refs/heads/master with a push option "create-change".
* When pushing to Gerrit you can already attach some of this information by
adding a '%' followed by the parameter to the ref, i.e. when interacting with
Gerrit it is possible to do things like[1]:
git push origin HEAD:refs/for/master%draft%topic=example%cc=jon.doe@example.org
This is not appealing to our users as it looks like hacks upon hacks to make
it work. It would read better if it was spelled as:
git push origin HEAD:refs/for/master \
--push-option draft \
--push-option topic=example \
--push-option cc=jon.doe@example.org
(with a short form that is even easier to type,
but this is is more intuitive already)
This is a patch series to Git core, which is developed at the same time
as a change is proposed to JGit by Dan Wang, see [2].
This code is also available at [3].
Thanks,
Stefan
[1] Not all Gerrit '%' options are documented, so here is a link to source code instead :(
https://gerrit.googlesource.com/gerrit/+/refs/heads/master/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java#1141
[2] https://git.eclipse.org/r/#/c/74570/
[3] https://github.com/stefanbeller/git/tree/pushoptions
Stefan Beller (4):
push options: {pre,post}-receive hook learns about push options
receive-pack: implement advertising and receiving push options
push: accept push options
add a test for push options
Documentation/config.txt | 7 +-
Documentation/git-push.txt | 8 ++-
Documentation/githooks.txt | 4 ++
Documentation/technical/pack-protocol.txt | 10 +--
Documentation/technical/protocol-capabilities.txt | 8 +++
builtin/push.c | 16 ++++-
builtin/receive-pack.c | 85 +++++++++++++++++++----
send-pack.c | 29 ++++++++
send-pack.h | 3 +
t/t5544-push-options.sh | 85 +++++++++++++++++++++++
transport.c | 2 +
transport.h | 7 ++
12 files changed, 242 insertions(+), 22 deletions(-)
create mode 100755 t/t5544-push-options.sh
--
2.9.0.141.gdd65b60
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 3/4] push: accept push options
2016-07-14 21:49 [PATCHv7 0/4] Push options Stefan Beller
@ 2016-07-14 21:49 ` Stefan Beller
0 siblings, 0 replies; 35+ messages in thread
From: Stefan Beller @ 2016-07-14 21:49 UTC (permalink / raw)
To: git, gitster; +Cc: dwwang, e, peff, dennis, jrnieder, Stefan Beller
This implements everything that is required on the client side to make use
of push options from the porcelain push command.
Signed-off-by: Stefan Beller <sbeller@google.com>
---
Documentation/git-push.txt | 8 +++++++-
builtin/push.c | 21 ++++++++++++++++++---
send-pack.c | 27 +++++++++++++++++++++++++++
send-pack.h | 3 +++
transport.c | 1 +
transport.h | 7 +++++++
6 files changed, 63 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 93c3527..ec514f6 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -11,7 +11,7 @@ SYNOPSIS
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
- [-u | --set-upstream]
+ [-u | --set-upstream] [--push-option=<string>]
[--[no-]signed|--sign=(true|false|if-asked)]
[--force-with-lease[=<refname>[:<expect>]]]
[--no-verify] [<repository> [<refspec>...]]
@@ -156,6 +156,12 @@ already exists on the remote side.
Either all refs are updated, or on error, no refs are updated.
If the server does not support atomic pushes the push will fail.
+-o::
+--push-option::
+ Transmit the given string to the server, which passes them to
+ the pre-receive as well as the post-receive hook. The given string
+ must not contain a NUL or LF character.
+
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
Path to the 'git-receive-pack' program on the remote
diff --git a/builtin/push.c b/builtin/push.c
index 4e9e4db..3bb9d6b 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -353,7 +353,8 @@ static int push_with_options(struct transport *transport, int flags)
return 1;
}
-static int do_push(const char *repo, int flags)
+static int do_push(const char *repo, int flags,
+ const struct string_list *push_options)
{
int i, errs;
struct remote *remote = pushremote_get(repo);
@@ -376,6 +377,9 @@ static int do_push(const char *repo, int flags)
if (remote->mirror)
flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+ if (push_options->nr)
+ flags |= TRANSPORT_PUSH_OPTIONS;
+
if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
if (!strcmp(*refspec, "refs/tags/*"))
return error(_("--all and --tags are incompatible"));
@@ -406,13 +410,16 @@ static int do_push(const char *repo, int flags)
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
} else {
struct transport *transport =
transport_get(remote, NULL);
-
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
@@ -500,6 +507,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int push_cert = -1;
int rc;
const char *repo = NULL; /* default repository */
+ static struct string_list push_options = STRING_LIST_INIT_DUP;
+ static struct string_list_item *item;
+
struct option options[] = {
OPT__VERBOSITY(&verbosity),
OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")),
@@ -533,6 +543,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
PARSE_OPT_OPTARG, option_parse_push_signed },
OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
+ OPT_STRING_LIST('o', "push-option", &push_options, N_("server-specific"), N_("option to transmit")),
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@ -563,7 +574,11 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
- rc = do_push(repo, flags);
+ for_each_string_list_item(item, &push_options)
+ if (strchr(item->string, '\n'))
+ die(_("push options must not have new line characters"));
+
+ rc = do_push(repo, flags, &push_options);
if (rc == -1)
usage_with_options(push_usage, options);
else
diff --git a/send-pack.c b/send-pack.c
index 299d303..3a842ac 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -260,6 +260,7 @@ static int generate_push_cert(struct strbuf *req_buf,
const char *push_cert_nonce)
{
const struct ref *ref;
+ struct string_list_item *item;
char *signing_key = xstrdup(get_signing_key());
const char *cp, *np;
struct strbuf cert = STRBUF_INIT;
@@ -276,6 +277,9 @@ static int generate_push_cert(struct strbuf *req_buf,
}
if (push_cert_nonce[0])
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
+ if (args->push_options)
+ for_each_string_list_item(item, args->push_options)
+ strbuf_addf(&cert, "push-option %s\n", item->string);
strbuf_addstr(&cert, "\n");
for (ref = remote_refs; ref; ref = ref->next) {
@@ -370,6 +374,8 @@ int send_pack(struct send_pack_args *args,
int agent_supported = 0;
int use_atomic = 0;
int atomic_supported = 0;
+ int use_push_options = 0;
+ int push_options_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
@@ -392,6 +398,8 @@ int send_pack(struct send_pack_args *args,
args->use_thin_pack = 0;
if (server_supports("atomic"))
atomic_supported = 1;
+ if (server_supports("push-options"))
+ push_options_supported = 1;
if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
int len;
@@ -418,6 +426,11 @@ int send_pack(struct send_pack_args *args,
use_atomic = atomic_supported && args->atomic;
+ if (args->push_options && !push_options_supported)
+ die(_("the receiving end does not support push options"));
+
+ use_push_options = push_options_supported && args->push_options;
+
if (status_report)
strbuf_addstr(&cap_buf, " report-status");
if (use_sideband)
@@ -426,6 +439,8 @@ int send_pack(struct send_pack_args *args,
strbuf_addstr(&cap_buf, " quiet");
if (use_atomic)
strbuf_addstr(&cap_buf, " atomic");
+ if (use_push_options)
+ strbuf_addstr(&cap_buf, " push-options");
if (agent_supported)
strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
@@ -512,6 +527,18 @@ int send_pack(struct send_pack_args *args,
strbuf_release(&req_buf);
strbuf_release(&cap_buf);
+ if (use_push_options) {
+ struct string_list_item *item;
+ struct strbuf sb = STRBUF_INIT;
+
+ for_each_string_list_item(item, args->push_options)
+ packet_buf_write(&sb, "%s", item->string);
+
+ write_or_die(out, sb.buf, sb.len);
+ packet_flush(out);
+ strbuf_release(&sb);
+ }
+
if (use_sideband && cmds_sent) {
memset(&demux, 0, sizeof(demux));
demux.proc = sideband_demux;
diff --git a/send-pack.h b/send-pack.h
index 57f222a..67fc40f 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -1,6 +1,8 @@
#ifndef SEND_PACK_H
#define SEND_PACK_H
+#include "string-list.h"
+
/* Possible values for push_cert field in send_pack_args. */
#define SEND_PACK_PUSH_CERT_NEVER 0
#define SEND_PACK_PUSH_CERT_IF_ASKED 1
@@ -21,6 +23,7 @@ struct send_pack_args {
push_cert:2,
stateless_rpc:1,
atomic:1;
+ const struct string_list *push_options;
};
struct option;
diff --git a/transport.c b/transport.c
index 59b911e..65ba93f 100644
--- a/transport.c
+++ b/transport.c
@@ -510,6 +510,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
+ args.push_options = transport->push_options;
args.url = transport->url;
if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
diff --git a/transport.h b/transport.h
index c681408..6fe3485 100644
--- a/transport.h
+++ b/transport.h
@@ -48,6 +48,12 @@ struct transport {
*/
unsigned cloning : 1;
+ /*
+ * These strings will be passed to the {pre, post}-receive hook,
+ * on the remote side, if both sides support the push options capability.
+ */
+ const struct string_list *push_options;
+
/**
* Returns 0 if successful, positive if the option is not
* recognized or is inapplicable, and negative if the option
@@ -134,6 +140,7 @@ struct transport {
#define TRANSPORT_PUSH_CERT_ALWAYS 2048
#define TRANSPORT_PUSH_CERT_IF_ASKED 4096
#define TRANSPORT_PUSH_ATOMIC 8192
+#define TRANSPORT_PUSH_OPTIONS 16384
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
--
2.9.0.247.gf748855.dirty
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCHv5 0/4] Push options
@ 2016-07-14 17:39 Stefan Beller
2016-07-14 17:39 ` [PATCH 3/4] push: accept push options Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-14 17:39 UTC (permalink / raw)
To: git, gitster; +Cc: dwwang, e, peff, dennis, jrnieder, Stefan Beller
Jeff wrote:
> Junio wrote:
>> I think those extra knobs can come later. If we are not going to
>> limit with max_options in the end, however, wouldn't it be more
>> natural for the initial iteration without any configuration not to
>> have hard-coded max_options at all?
>
> Yeah, I am OK with adding restrictive knobs later as a separate topic.
> As Stefan notes, upstream does not have the other knobs anyway, and IIRC
> the push-options feature is not even enabled by default.
>
> -Peff
* now it actually is not a default. ;)
* removed knobs, but instead we only reject at > LARGE_PACKET_MAX - 1,
Thanks,
Stefan
v5:
git diff origin/sb/push-options:
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index 4d8041a..917ac18 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -44,7 +44,7 @@ static struct strbuf fsck_msg_types = STRBUF_INIT;
static int receive_unpack_limit = -1;
static int transfer_unpack_limit = -1;
static int advertise_atomic_push = 1;
-static int advertise_push_options = 1;
+static int advertise_push_options;
static int unpack_limit = 100;
static int report_status;
static int use_sideband;
@@ -1501,24 +1501,11 @@ static struct command *read_head_info(struct sha1_array *shallow)
static struct string_list *read_push_options(void)
{
- int i;
struct string_list *ret = xmalloc(sizeof(*ret));
- /* NEEDSWORK: expose the limitations to be configurable. */
- int max_options = 32;
-
- /*
- * NEEDSWORK: expose the limitations to be configurable;
- * Once the limit can be lifted, include a way for payloads
- * larger than one pkt, e.g allow a payload of up to
- * LARGE_PACKET_MAX - 1 only, and reserve the last byte
- * to indicate whether the next pkt continues with this
- * push option.
- */
- int max_size = 1024;
-
string_list_init(ret, 1);
- for (i = 0; i < max_options; i++) {
- char *line;
+
+ while (1) {
+ char *line, *lf;
int len;
line = packet_read_line(0, &len);
@@ -1526,7 +1513,14 @@ static struct string_list *read_push_options(void)
if (!line)
break;
- if (len > max_size) {
+ /*
+ * NEEDSWORK: expose the limitations to be configurable;
+ * Once the limit can be lifted, include a way for payloads
+ * larger than one pkt, e.g use last byte to indicate if
+ * the push option continues in the next packet or implement
+ * larger packets.
+ */
+ if (len > LARGE_PACKET_MAX - 1) {
/*
* NEEDSWORK: The error message in die(..) is not
* transmitted in call cases, so ideally all die(..)
@@ -1534,20 +1528,17 @@ static struct string_list *read_push_options(void)
* combine rp_error && die into one helper function.
*/
rp_error("protocol error: server configuration allows push "
- "options of size up to %d bytes", max_size);
+ "options of size up to %d bytes",
+ LARGE_PACKET_MAX - 1);
die("protocol error: push options too large");
}
- len = strcspn(line, "\n");
- line[len] = '\0';
+ lf = strchr(line, '\n');
+ if (lf)
+ *lf = '\0';
string_list_append(ret, line);
}
- if (i == max_options) {
- rp_error("protocol error: server configuration only allows up "
- "to %d push options", max_options);
- die("protocol error: push options too large");
- }
return ret;
}
diff --git a/t/t5545-push-options.sh b/t/t5545-push-options.sh
index 8dd3c8e..ea813b9 100755
--- a/t/t5545-push-options.sh
+++ b/t/t5545-push-options.sh
@@ -57,6 +57,7 @@ test_refs () {
test_expect_success 'one push option works for a single branch' '
mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
(
cd workbench &&
test_commit one &&
@@ -85,6 +86,7 @@ test_expect_success 'push option denied by remote' '
test_expect_success 'two push options work' '
mk_repo_pair &&
+ git -C upstream config receive.advertisePushOptions true &&
(
cd workbench &&
test_commit one &&
cover letter v4:
Thanks Junio, Jeff, Jonathan for discussion and feedback!
I went over the emails again and we seem to agree that the initial design (in v3)
was sane and the error messages and reporting for corner cases were to be
dismissed as "it happens as often as 'BUG:' messages appear, so let's not care
too deeply now".
Thanks,
Stefan
This is a diff against a modified v3 (it's actually origin/sb/push-options):
diff --git a/Documentation/config.txt b/Documentation/config.txt
index df1b314..25b5db1 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2410,13 +2410,13 @@ rebase.instructionFormat
receive.advertiseAtomic::
By default, git-receive-pack will advertise the atomic push
- capability to its clients. If you don't want this capability
- to be advertised, set this variable to false.
+ capability to its clients. If you don't want to advertise this
+ capability, set this variable to false.
receive.advertisePushOptions::
- By default, git-receive-pack will advertise the push options capability
- to its clients. If you don't want this capability
- to be advertised, set this variable to false.
+ By default, git-receive-pack will advertise the push options
+ capability to its clients. If you don't want to advertise this
+ capability, set this variable to false.
receive.autogc::
By default, git-receive-pack will run "git-gc --auto" after
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index b0b1273..e960258 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -156,11 +156,11 @@ already exists on the remote side.
Either all refs are updated, or on error, no refs are updated.
If the server does not support atomic pushes the push will fail.
--L::
+-o::
--push-option::
Transmit the given string to the server, which passes them to
- the pre-receive as well as the post-receive hook. Only C strings
- containing no new lines are allowed.
+ the pre-receive as well as the post-receive hook. The given string
+ must not contain a NUL or LF character.
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index c875cde..9565dc3 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -247,8 +247,14 @@ Both standard output and standard error output are forwarded to
'git send-pack' on the other end, so you can simply `echo` messages
for the user.
-The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
-and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
+The number of push options given on the command line of
+`git push --push-option=...` can be read from the environment
+variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
+found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
+If it is negotiated to not use the push options phase, the
+environment variables will not be set. If the client selects
+to use push options, but doesn't transmit any, the count variable
+will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
[[update]]
update
@@ -325,8 +331,14 @@ a sample script `post-receive-email` provided in the `contrib/hooks`
directory in Git distribution, which implements sending commit
emails.
-The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
-and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
+The number of push options given on the command line of
+`git push --push-option=...` can be read from the environment
+variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
+found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
+If it is negotiated to not use the push options phase, the
+environment variables will not be set. If the client selects
+to use push options, but doesn't transmit any, the count variable
+will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
[[post-update]]
post-update
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index b71eda9..4c28d3a 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -256,10 +256,11 @@ updated or none.
push-options
------------
-If the server sends the 'push-options' capability it is capable to accept
-push options after the update commands have been sent. If the pushing client
-requests this capability, the server will pass the options to the pre and post
-receive hooks that process this push request.
+If the server sends the 'push-options' capability it is able to accept
+push options after the update commands have been sent, but before the
+packfile is streamed. If the pushing client requests this capability,
+the server will pass the options to the pre- and post- receive hooks
+that process this push request.
allow-tip-sha1-in-want
----------------------
diff --git a/builtin/push.c b/builtin/push.c
index 1b5d205..3bb9d6b 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -508,6 +508,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int rc;
const char *repo = NULL; /* default repository */
static struct string_list push_options = STRING_LIST_INIT_DUP;
+ static struct string_list_item *item;
struct option options[] = {
OPT__VERBOSITY(&verbosity),
@@ -573,6 +574,10 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
+ for_each_string_list_item(item, &push_options)
+ if (strchr(item->string, '\n'))
+ die(_("push options must not have new line characters"));
+
rc = do_push(repo, flags, &push_options);
if (rc == -1)
usage_with_options(push_usage, options);
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e71041a..754db6e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -214,12 +214,12 @@ static void show_ref(const char *path, const unsigned char *sha1)
"report-status delete-refs side-band-64k quiet");
if (advertise_atomic_push)
strbuf_addstr(&cap, " atomic");
- if (advertise_push_options)
- strbuf_addstr(&cap, " push-options");
if (prefer_ofs_delta)
strbuf_addstr(&cap, " ofs-delta");
if (push_cert_nonce)
strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
+ if (advertise_push_options)
+ strbuf_addstr(&cap, " push-options");
strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
packet_write(1, "%s %s%c%s\n",
sha1_to_hex(sha1), path, 0, cap.buf);
@@ -584,7 +584,7 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
- if (feed_state && feed_state->push_options) {
+ if (feed_state->push_options) {
int i;
for (i = 0; i < feed_state->push_options->nr; i++)
argv_array_pushf(&proc.env_array,
@@ -592,7 +592,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
feed_state->push_options->items[i].string);
argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
feed_state->push_options->nr);
- }
+ } else
+ argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
@@ -1498,7 +1499,7 @@ static struct command *read_head_info(struct sha1_array *shallow)
return commands;
}
-static struct string_list *read_push_options()
+static struct string_list *read_push_options(void)
{
int i;
struct string_list *ret = xmalloc(sizeof(*ret));
@@ -1526,18 +1527,28 @@ static struct string_list *read_push_options()
if (!line)
break;
- if (len > max_size)
- die("protocol error: server configuration allows push "
- "options of size up to %d bytes", max_size);
+ if (len > max_size) {
+ /*
+ * NEEDSWORK: The error message in die(..) is not
+ * transmitted in call cases, so ideally all die(..)
+ * calls are prefixed with rp_error and then we can
+ * combine rp_error && die into one helper function.
+ */
+ rp_error("protocol error: server configuration allows push "
+ "options of size up to %d bytes", max_size);
+ die("protocol error: push options too large");
+ }
len = strcspn(line, "\n");
line[len] = '\0';
string_list_append(ret, line);
}
- if (i == max_options)
- die("protocol error: server configuration only allows up "
+ if (i == max_options) {
+ rp_error("protocol error: server configuration only allows up "
"to %d push options", max_options);
+ die("protocol error: push options too large");
+ }
return ret;
}
diff --git a/send-pack.c b/send-pack.c
index c943560..3a842ac 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -277,13 +277,10 @@ static int generate_push_cert(struct strbuf *req_buf,
}
if (push_cert_nonce[0])
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
- strbuf_addstr(&cert, "\n");
-
- if (args->push_options) {
+ if (args->push_options)
for_each_string_list_item(item, args->push_options)
strbuf_addf(&cert, "push-option %s\n", item->string);
- strbuf_addstr(&cert, "\n");
- }
+ strbuf_addstr(&cert, "\n");
for (ref = remote_refs; ref; ref = ref->next) {
if (check_to_send_update(ref, args) < 0)
diff --git a/templates/hooks--pre-receive.sample b/templates/hooks--pre-receive.sample
index e4d3edc..a1fd29e 100644
--- a/templates/hooks--pre-receive.sample
+++ b/templates/hooks--pre-receive.sample
@@ -6,13 +6,15 @@
#
# To enable this hook, rename this file to "pre-receive".
-if test -n "$GIT_PUSH_OPTION_COUNT"; then
+if test -n "$GIT_PUSH_OPTION_COUNT"
+then
i=0
- while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
+ do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
- echo "echo from the pre-receive-hook ${value#*=}" >&2
+ echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
Cover letter v3:
================
This is not marked for RFC any more, as I do not recall any open points
left for discussion. This addresses the only reply from Eric Wong on patch 3:
diff to v2:
diff --git a/send-pack.c b/send-pack.c
index e328276..c943560 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -536,7 +536,8 @@ int send_pack(struct send_pack_args *args,
for_each_string_list_item(item, args->push_options)
packet_buf_write(&sb, "%s", item->string);
- write_or_die(out, sb.buf, sb.len);
+
+ write_or_die(out, sb.buf, sb.len);
packet_flush(out);
strbuf_release(&sb);
}
diff --git a/transport.c b/transport.c
index 598bd1f..0298be1 100644
--- a/transport.c
+++ b/transport.c
@@ -641,7 +641,6 @@ struct transport *transport_get(struct remote *remote, const char *url)
struct transport *ret = xcalloc(1, sizeof(*ret));
ret->progress = isatty(2);
- ret->push_options = NULL;
if (!remote)
die("No remote provided to transport_get()");
Cover letter v2:
================
Allow a user to pass information along a push to the pre/post-receive hook
on the remote.
Jeff writes on v1:
> Whereas in Dennis's patches, it was about specific information directly
> related to the act of pushing.
This allows to transmit arbitrary information as the backends of $VENDOR
may have different options available related to the direct act of pushing.
Thanks,
Stefan
Cover letter v1:
================
Allow a user to pass information along a push to the pre/post-receive hook
on the remote.
When using a remote that is more than just a plain Git host (e.g. Gerrit,
Git{hub/lab}, etc) this may become more obvious: The (server backend specific)
push options can instruct the server to:
* open a pull request
* send out emails asking for review
* (un)trigger continuous integration
* set priority for continuous integration (i.e. bots pushing may ask to be
treated with lower priority compared to humans)
* ...
Most of these actions can be done on the client side as well,
but in these remote-centric workflows it is easier to do that on the remote,
which is why we need to transport the information there.
More concrete examples:
* When you want a change in Gerrit to be submitted to refs/heads/master, you
push instead to a magic branch refs/for/master and Gerrit will create a change
for you (similar to a pull request). Instead we could imagine that you push
to a magical refs/heads/master with a push option "create-change".
* When pushing to Gerrit you can already attach some of this information by
adding a '%' followed by the parameter to the ref, i.e. when interacting with
Gerrit it is possible to do things like[1]:
git push origin HEAD:refs/for/master%draft%topic=example%cc=jon.doe@example.org
This is not appealing to our users as it looks like hacks upon hacks to make
it work. It would read better if it was spelled as:
git push origin HEAD:refs/for/master \
--push-option draft \
--push-option topic=example \
--push-option cc=jon.doe@example.org
(with a short form that is even easier to type,
but this is is more intuitive already)
This is a patch series to Git core, which is developed at the same time
as a change is proposed to JGit by Dan Wang, see [2].
This code is also available at [3].
Thanks,
Stefan
[1] Not all Gerrit '%' options are documented, so here is a link to source code instead :(
https://gerrit.googlesource.com/gerrit/+/refs/heads/master/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java#1141
[2] https://git.eclipse.org/r/#/c/74570/
[3] https://github.com/stefanbeller/git/tree/pushoptions
Stefan Beller (4):
push options: {pre,post}-receive hook learns about push options
receive-pack: implement advertising and receiving push options
push: accept push options
add a test for push options
Documentation/config.txt | 7 +-
Documentation/git-push.txt | 8 ++-
Documentation/githooks.txt | 4 ++
Documentation/technical/pack-protocol.txt | 10 +--
Documentation/technical/protocol-capabilities.txt | 8 +++
builtin/push.c | 16 ++++-
builtin/receive-pack.c | 85 +++++++++++++++++++----
send-pack.c | 29 ++++++++
send-pack.h | 3 +
t/t5544-push-options.sh | 85 +++++++++++++++++++++++
transport.c | 2 +
transport.h | 7 ++
12 files changed, 242 insertions(+), 22 deletions(-)
create mode 100755 t/t5544-push-options.sh
--
2.9.0.141.gdd65b60
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 3/4] push: accept push options
2016-07-14 17:39 [PATCHv5 0/4] Push options Stefan Beller
@ 2016-07-14 17:39 ` Stefan Beller
0 siblings, 0 replies; 35+ messages in thread
From: Stefan Beller @ 2016-07-14 17:39 UTC (permalink / raw)
To: git, gitster; +Cc: dwwang, e, peff, dennis, jrnieder, Stefan Beller
This implements everything that is required on the client side to make use
of push options from the porcelain push command.
Signed-off-by: Stefan Beller <sbeller@google.com>
---
Documentation/git-push.txt | 8 +++++++-
builtin/push.c | 21 ++++++++++++++++++---
send-pack.c | 27 +++++++++++++++++++++++++++
send-pack.h | 3 +++
transport.c | 1 +
transport.h | 7 +++++++
6 files changed, 63 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 19f46b6..e960258 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -11,7 +11,7 @@ SYNOPSIS
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
- [-u | --set-upstream]
+ [-u | --set-upstream] [--push-option=<string>]
[--[no-]signed|--sign=(true|false|if-asked)]
[--force-with-lease[=<refname>[:<expect>]]]
[--no-verify] [<repository> [<refspec>...]]
@@ -156,6 +156,12 @@ already exists on the remote side.
Either all refs are updated, or on error, no refs are updated.
If the server does not support atomic pushes the push will fail.
+-o::
+--push-option::
+ Transmit the given string to the server, which passes them to
+ the pre-receive as well as the post-receive hook. The given string
+ must not contain a NUL or LF character.
+
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
Path to the 'git-receive-pack' program on the remote
diff --git a/builtin/push.c b/builtin/push.c
index 4e9e4db..3bb9d6b 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -353,7 +353,8 @@ static int push_with_options(struct transport *transport, int flags)
return 1;
}
-static int do_push(const char *repo, int flags)
+static int do_push(const char *repo, int flags,
+ const struct string_list *push_options)
{
int i, errs;
struct remote *remote = pushremote_get(repo);
@@ -376,6 +377,9 @@ static int do_push(const char *repo, int flags)
if (remote->mirror)
flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+ if (push_options->nr)
+ flags |= TRANSPORT_PUSH_OPTIONS;
+
if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
if (!strcmp(*refspec, "refs/tags/*"))
return error(_("--all and --tags are incompatible"));
@@ -406,13 +410,16 @@ static int do_push(const char *repo, int flags)
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
} else {
struct transport *transport =
transport_get(remote, NULL);
-
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
@@ -500,6 +507,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int push_cert = -1;
int rc;
const char *repo = NULL; /* default repository */
+ static struct string_list push_options = STRING_LIST_INIT_DUP;
+ static struct string_list_item *item;
+
struct option options[] = {
OPT__VERBOSITY(&verbosity),
OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")),
@@ -533,6 +543,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
PARSE_OPT_OPTARG, option_parse_push_signed },
OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
+ OPT_STRING_LIST('o', "push-option", &push_options, N_("server-specific"), N_("option to transmit")),
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@ -563,7 +574,11 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
- rc = do_push(repo, flags);
+ for_each_string_list_item(item, &push_options)
+ if (strchr(item->string, '\n'))
+ die(_("push options must not have new line characters"));
+
+ rc = do_push(repo, flags, &push_options);
if (rc == -1)
usage_with_options(push_usage, options);
else
diff --git a/send-pack.c b/send-pack.c
index 299d303..3a842ac 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -260,6 +260,7 @@ static int generate_push_cert(struct strbuf *req_buf,
const char *push_cert_nonce)
{
const struct ref *ref;
+ struct string_list_item *item;
char *signing_key = xstrdup(get_signing_key());
const char *cp, *np;
struct strbuf cert = STRBUF_INIT;
@@ -276,6 +277,9 @@ static int generate_push_cert(struct strbuf *req_buf,
}
if (push_cert_nonce[0])
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
+ if (args->push_options)
+ for_each_string_list_item(item, args->push_options)
+ strbuf_addf(&cert, "push-option %s\n", item->string);
strbuf_addstr(&cert, "\n");
for (ref = remote_refs; ref; ref = ref->next) {
@@ -370,6 +374,8 @@ int send_pack(struct send_pack_args *args,
int agent_supported = 0;
int use_atomic = 0;
int atomic_supported = 0;
+ int use_push_options = 0;
+ int push_options_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
@@ -392,6 +398,8 @@ int send_pack(struct send_pack_args *args,
args->use_thin_pack = 0;
if (server_supports("atomic"))
atomic_supported = 1;
+ if (server_supports("push-options"))
+ push_options_supported = 1;
if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
int len;
@@ -418,6 +426,11 @@ int send_pack(struct send_pack_args *args,
use_atomic = atomic_supported && args->atomic;
+ if (args->push_options && !push_options_supported)
+ die(_("the receiving end does not support push options"));
+
+ use_push_options = push_options_supported && args->push_options;
+
if (status_report)
strbuf_addstr(&cap_buf, " report-status");
if (use_sideband)
@@ -426,6 +439,8 @@ int send_pack(struct send_pack_args *args,
strbuf_addstr(&cap_buf, " quiet");
if (use_atomic)
strbuf_addstr(&cap_buf, " atomic");
+ if (use_push_options)
+ strbuf_addstr(&cap_buf, " push-options");
if (agent_supported)
strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
@@ -512,6 +527,18 @@ int send_pack(struct send_pack_args *args,
strbuf_release(&req_buf);
strbuf_release(&cap_buf);
+ if (use_push_options) {
+ struct string_list_item *item;
+ struct strbuf sb = STRBUF_INIT;
+
+ for_each_string_list_item(item, args->push_options)
+ packet_buf_write(&sb, "%s", item->string);
+
+ write_or_die(out, sb.buf, sb.len);
+ packet_flush(out);
+ strbuf_release(&sb);
+ }
+
if (use_sideband && cmds_sent) {
memset(&demux, 0, sizeof(demux));
demux.proc = sideband_demux;
diff --git a/send-pack.h b/send-pack.h
index 57f222a..67fc40f 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -1,6 +1,8 @@
#ifndef SEND_PACK_H
#define SEND_PACK_H
+#include "string-list.h"
+
/* Possible values for push_cert field in send_pack_args. */
#define SEND_PACK_PUSH_CERT_NEVER 0
#define SEND_PACK_PUSH_CERT_IF_ASKED 1
@@ -21,6 +23,7 @@ struct send_pack_args {
push_cert:2,
stateless_rpc:1,
atomic:1;
+ const struct string_list *push_options;
};
struct option;
diff --git a/transport.c b/transport.c
index 095e61f..0298be1 100644
--- a/transport.c
+++ b/transport.c
@@ -510,6 +510,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
+ args.push_options = transport->push_options;
args.url = transport->url;
if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
diff --git a/transport.h b/transport.h
index c681408..6fe3485 100644
--- a/transport.h
+++ b/transport.h
@@ -48,6 +48,12 @@ struct transport {
*/
unsigned cloning : 1;
+ /*
+ * These strings will be passed to the {pre, post}-receive hook,
+ * on the remote side, if both sides support the push options capability.
+ */
+ const struct string_list *push_options;
+
/**
* Returns 0 if successful, positive if the option is not
* recognized or is inapplicable, and negative if the option
@@ -134,6 +140,7 @@ struct transport {
#define TRANSPORT_PUSH_CERT_ALWAYS 2048
#define TRANSPORT_PUSH_CERT_IF_ASKED 4096
#define TRANSPORT_PUSH_ATOMIC 8192
+#define TRANSPORT_PUSH_OPTIONS 16384
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
--
2.9.0.247.gf748855.dirty
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCHv4 0/4] Push options
@ 2016-07-09 0:31 Stefan Beller
2016-07-09 0:31 ` [PATCH 3/4] push: accept push options Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-07-09 0:31 UTC (permalink / raw)
To: gitster; +Cc: git, e, peff, dwwang, dennis, jrnieder, Stefan Beller
Thanks Junio, Jeff, Jonathan for discussion and feedback!
I went over the emails again and we seem to agree that the initial design (in v3)
was sane and the error messages and reporting for corner cases were to be
dismissed as "it happens as often as 'BUG:' messages appear, so let's not care
too deeply now".
Thanks,
Stefan
This is a diff against a modified v3 (it's actually origin/sb/push-options):
diff --git a/Documentation/config.txt b/Documentation/config.txt
index df1b314..25b5db1 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2410,13 +2410,13 @@ rebase.instructionFormat
receive.advertiseAtomic::
By default, git-receive-pack will advertise the atomic push
- capability to its clients. If you don't want this capability
- to be advertised, set this variable to false.
+ capability to its clients. If you don't want to advertise this
+ capability, set this variable to false.
receive.advertisePushOptions::
- By default, git-receive-pack will advertise the push options capability
- to its clients. If you don't want this capability
- to be advertised, set this variable to false.
+ By default, git-receive-pack will advertise the push options
+ capability to its clients. If you don't want to advertise this
+ capability, set this variable to false.
receive.autogc::
By default, git-receive-pack will run "git-gc --auto" after
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index b0b1273..e960258 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -156,11 +156,11 @@ already exists on the remote side.
Either all refs are updated, or on error, no refs are updated.
If the server does not support atomic pushes the push will fail.
--L::
+-o::
--push-option::
Transmit the given string to the server, which passes them to
- the pre-receive as well as the post-receive hook. Only C strings
- containing no new lines are allowed.
+ the pre-receive as well as the post-receive hook. The given string
+ must not contain a NUL or LF character.
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
diff --git a/Documentation/githooks.txt b/Documentation/githooks.txt
index c875cde..9565dc3 100644
--- a/Documentation/githooks.txt
+++ b/Documentation/githooks.txt
@@ -247,8 +247,14 @@ Both standard output and standard error output are forwarded to
'git send-pack' on the other end, so you can simply `echo` messages
for the user.
-The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
-and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
+The number of push options given on the command line of
+`git push --push-option=...` can be read from the environment
+variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
+found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
+If it is negotiated to not use the push options phase, the
+environment variables will not be set. If the client selects
+to use push options, but doesn't transmit any, the count variable
+will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
[[update]]
update
@@ -325,8 +331,14 @@ a sample script `post-receive-email` provided in the `contrib/hooks`
directory in Git distribution, which implements sending commit
emails.
-The number of push options are available in the variable GIT_PUSH_OPTION_COUNT
-and the options itself are in GIT_PUSH_OPTION_{0,1,...}.
+The number of push options given on the command line of
+`git push --push-option=...` can be read from the environment
+variable `GIT_PUSH_OPTION_COUNT`, and the options themselves are
+found in `GIT_PUSH_OPTION_0`, `GIT_PUSH_OPTION_1`,...
+If it is negotiated to not use the push options phase, the
+environment variables will not be set. If the client selects
+to use push options, but doesn't transmit any, the count variable
+will be set to zero, `GIT_PUSH_OPTION_COUNT=0`.
[[post-update]]
post-update
diff --git a/Documentation/technical/protocol-capabilities.txt b/Documentation/technical/protocol-capabilities.txt
index b71eda9..4c28d3a 100644
--- a/Documentation/technical/protocol-capabilities.txt
+++ b/Documentation/technical/protocol-capabilities.txt
@@ -256,10 +256,11 @@ updated or none.
push-options
------------
-If the server sends the 'push-options' capability it is capable to accept
-push options after the update commands have been sent. If the pushing client
-requests this capability, the server will pass the options to the pre and post
-receive hooks that process this push request.
+If the server sends the 'push-options' capability it is able to accept
+push options after the update commands have been sent, but before the
+packfile is streamed. If the pushing client requests this capability,
+the server will pass the options to the pre- and post- receive hooks
+that process this push request.
allow-tip-sha1-in-want
----------------------
diff --git a/builtin/push.c b/builtin/push.c
index 1b5d205..3bb9d6b 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -508,6 +508,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int rc;
const char *repo = NULL; /* default repository */
static struct string_list push_options = STRING_LIST_INIT_DUP;
+ static struct string_list_item *item;
struct option options[] = {
OPT__VERBOSITY(&verbosity),
@@ -573,6 +574,10 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
+ for_each_string_list_item(item, &push_options)
+ if (strchr(item->string, '\n'))
+ die(_("push options must not have new line characters"));
+
rc = do_push(repo, flags, &push_options);
if (rc == -1)
usage_with_options(push_usage, options);
diff --git a/builtin/receive-pack.c b/builtin/receive-pack.c
index e71041a..754db6e 100644
--- a/builtin/receive-pack.c
+++ b/builtin/receive-pack.c
@@ -214,12 +214,12 @@ static void show_ref(const char *path, const unsigned char *sha1)
"report-status delete-refs side-band-64k quiet");
if (advertise_atomic_push)
strbuf_addstr(&cap, " atomic");
- if (advertise_push_options)
- strbuf_addstr(&cap, " push-options");
if (prefer_ofs_delta)
strbuf_addstr(&cap, " ofs-delta");
if (push_cert_nonce)
strbuf_addf(&cap, " push-cert=%s", push_cert_nonce);
+ if (advertise_push_options)
+ strbuf_addstr(&cap, " push-options");
strbuf_addf(&cap, " agent=%s", git_user_agent_sanitized());
packet_write(1, "%s %s%c%s\n",
sha1_to_hex(sha1), path, 0, cap.buf);
@@ -584,7 +584,7 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
proc.argv = argv;
proc.in = -1;
proc.stdout_to_stderr = 1;
- if (feed_state && feed_state->push_options) {
+ if (feed_state->push_options) {
int i;
for (i = 0; i < feed_state->push_options->nr; i++)
argv_array_pushf(&proc.env_array,
@@ -592,7 +592,8 @@ static int run_and_feed_hook(const char *hook_name, feed_fn feed,
feed_state->push_options->items[i].string);
argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT=%d",
feed_state->push_options->nr);
- }
+ } else
+ argv_array_pushf(&proc.env_array, "GIT_PUSH_OPTION_COUNT");
if (use_sideband) {
memset(&muxer, 0, sizeof(muxer));
@@ -1498,7 +1499,7 @@ static struct command *read_head_info(struct sha1_array *shallow)
return commands;
}
-static struct string_list *read_push_options()
+static struct string_list *read_push_options(void)
{
int i;
struct string_list *ret = xmalloc(sizeof(*ret));
@@ -1526,18 +1527,28 @@ static struct string_list *read_push_options()
if (!line)
break;
- if (len > max_size)
- die("protocol error: server configuration allows push "
- "options of size up to %d bytes", max_size);
+ if (len > max_size) {
+ /*
+ * NEEDSWORK: The error message in die(..) is not
+ * transmitted in call cases, so ideally all die(..)
+ * calls are prefixed with rp_error and then we can
+ * combine rp_error && die into one helper function.
+ */
+ rp_error("protocol error: server configuration allows push "
+ "options of size up to %d bytes", max_size);
+ die("protocol error: push options too large");
+ }
len = strcspn(line, "\n");
line[len] = '\0';
string_list_append(ret, line);
}
- if (i == max_options)
- die("protocol error: server configuration only allows up "
+ if (i == max_options) {
+ rp_error("protocol error: server configuration only allows up "
"to %d push options", max_options);
+ die("protocol error: push options too large");
+ }
return ret;
}
diff --git a/send-pack.c b/send-pack.c
index c943560..3a842ac 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -277,13 +277,10 @@ static int generate_push_cert(struct strbuf *req_buf,
}
if (push_cert_nonce[0])
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
- strbuf_addstr(&cert, "\n");
-
- if (args->push_options) {
+ if (args->push_options)
for_each_string_list_item(item, args->push_options)
strbuf_addf(&cert, "push-option %s\n", item->string);
- strbuf_addstr(&cert, "\n");
- }
+ strbuf_addstr(&cert, "\n");
for (ref = remote_refs; ref; ref = ref->next) {
if (check_to_send_update(ref, args) < 0)
diff --git a/templates/hooks--pre-receive.sample b/templates/hooks--pre-receive.sample
index e4d3edc..a1fd29e 100644
--- a/templates/hooks--pre-receive.sample
+++ b/templates/hooks--pre-receive.sample
@@ -6,13 +6,15 @@
#
# To enable this hook, rename this file to "pre-receive".
-if test -n "$GIT_PUSH_OPTION_COUNT"; then
+if test -n "$GIT_PUSH_OPTION_COUNT"
+then
i=0
- while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"; do
+ while test "$i" -lt "$GIT_PUSH_OPTION_COUNT"
+ do
eval "value=\$GIT_PUSH_OPTION_$i"
case "$value" in
echoback=*)
- echo "echo from the pre-receive-hook ${value#*=}" >&2
+ echo "echo from the pre-receive-hook: ${value#*=}" >&2
;;
reject)
exit 1
Cover letter v3:
================
This is not marked for RFC any more, as I do not recall any open points
left for discussion. This addresses the only reply from Eric Wong on patch 3:
diff to v2:
diff --git a/send-pack.c b/send-pack.c
index e328276..c943560 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -536,7 +536,8 @@ int send_pack(struct send_pack_args *args,
for_each_string_list_item(item, args->push_options)
packet_buf_write(&sb, "%s", item->string);
- write_or_die(out, sb.buf, sb.len);
+
+ write_or_die(out, sb.buf, sb.len);
packet_flush(out);
strbuf_release(&sb);
}
diff --git a/transport.c b/transport.c
index 598bd1f..0298be1 100644
--- a/transport.c
+++ b/transport.c
@@ -641,7 +641,6 @@ struct transport *transport_get(struct remote *remote, const char *url)
struct transport *ret = xcalloc(1, sizeof(*ret));
ret->progress = isatty(2);
- ret->push_options = NULL;
if (!remote)
die("No remote provided to transport_get()");
Cover letter v2:
================
Allow a user to pass information along a push to the pre/post-receive hook
on the remote.
Jeff writes on v1:
> Whereas in Dennis's patches, it was about specific information directly
> related to the act of pushing.
This allows to transmit arbitrary information as the backends of $VENDOR
may have different options available related to the direct act of pushing.
Thanks,
Stefan
Cover letter v1:
================
Allow a user to pass information along a push to the pre/post-receive hook
on the remote.
When using a remote that is more than just a plain Git host (e.g. Gerrit,
Git{hub/lab}, etc) this may become more obvious: The (server backend specific)
push options can instruct the server to:
* open a pull request
* send out emails asking for review
* (un)trigger continuous integration
* set priority for continuous integration (i.e. bots pushing may ask to be
treated with lower priority compared to humans)
* ...
Most of these actions can be done on the client side as well,
but in these remote-centric workflows it is easier to do that on the remote,
which is why we need to transport the information there.
More concrete examples:
* When you want a change in Gerrit to be submitted to refs/heads/master, you
push instead to a magic branch refs/for/master and Gerrit will create a change
for you (similar to a pull request). Instead we could imagine that you push
to a magical refs/heads/master with a push option "create-change".
* When pushing to Gerrit you can already attach some of this information by
adding a '%' followed by the parameter to the ref, i.e. when interacting with
Gerrit it is possible to do things like[1]:
git push origin HEAD:refs/for/master%draft%topic=example%cc=jon.doe@example.org
This is not appealing to our users as it looks like hacks upon hacks to make
it work. It would read better if it was spelled as:
git push origin HEAD:refs/for/master \
--push-option draft \
--push-option topic=example \
--push-option cc=jon.doe@example.org
(with a short form that is even easier to type,
but this is is more intuitive already)
This is a patch series to Git core, which is developed at the same time
as a change is proposed to JGit by Dan Wang, see [2].
This code is also available at [3].
Thanks,
Stefan
[1] Not all Gerrit '%' options are documented, so here is a link to source code instead :(
https://gerrit.googlesource.com/gerrit/+/refs/heads/master/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java#1141
[2] https://git.eclipse.org/r/#/c/74570/
[3] https://github.com/stefanbeller/git/tree/pushoptions
Stefan Beller (4):
push options: {pre,post}-receive hook learns about push options
receive-pack: implement advertising and receiving push options
push: accept push options
add a test for push options
Documentation/config.txt | 7 +-
Documentation/git-push.txt | 8 ++-
Documentation/githooks.txt | 4 ++
Documentation/technical/pack-protocol.txt | 10 +--
Documentation/technical/protocol-capabilities.txt | 8 +++
builtin/push.c | 16 ++++-
builtin/receive-pack.c | 85 +++++++++++++++++++----
send-pack.c | 29 ++++++++
send-pack.h | 3 +
t/t5544-push-options.sh | 85 +++++++++++++++++++++++
transport.c | 2 +
transport.h | 7 ++
12 files changed, 242 insertions(+), 22 deletions(-)
create mode 100755 t/t5544-push-options.sh
--
2.9.0.141.gdd65b60
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [PATCH 3/4] push: accept push options
2016-07-09 0:31 [PATCHv4 0/4] Push options Stefan Beller
@ 2016-07-09 0:31 ` Stefan Beller
0 siblings, 0 replies; 35+ messages in thread
From: Stefan Beller @ 2016-07-09 0:31 UTC (permalink / raw)
To: gitster; +Cc: git, e, peff, dwwang, dennis, jrnieder, Stefan Beller
This implements everything that is required on the client side to make use
of push options from the porcelain push command.
Signed-off-by: Stefan Beller <sbeller@google.com>
---
Documentation/git-push.txt | 8 +++++++-
builtin/push.c | 21 ++++++++++++++++++---
send-pack.c | 27 +++++++++++++++++++++++++++
send-pack.h | 3 +++
transport.c | 1 +
transport.h | 7 +++++++
6 files changed, 63 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 19f46b6..e960258 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -11,7 +11,7 @@ SYNOPSIS
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
- [-u | --set-upstream]
+ [-u | --set-upstream] [--push-option=<string>]
[--[no-]signed|--sign=(true|false|if-asked)]
[--force-with-lease[=<refname>[:<expect>]]]
[--no-verify] [<repository> [<refspec>...]]
@@ -156,6 +156,12 @@ already exists on the remote side.
Either all refs are updated, or on error, no refs are updated.
If the server does not support atomic pushes the push will fail.
+-o::
+--push-option::
+ Transmit the given string to the server, which passes them to
+ the pre-receive as well as the post-receive hook. The given string
+ must not contain a NUL or LF character.
+
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
Path to the 'git-receive-pack' program on the remote
diff --git a/builtin/push.c b/builtin/push.c
index 4e9e4db..3bb9d6b 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -353,7 +353,8 @@ static int push_with_options(struct transport *transport, int flags)
return 1;
}
-static int do_push(const char *repo, int flags)
+static int do_push(const char *repo, int flags,
+ const struct string_list *push_options)
{
int i, errs;
struct remote *remote = pushremote_get(repo);
@@ -376,6 +377,9 @@ static int do_push(const char *repo, int flags)
if (remote->mirror)
flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+ if (push_options->nr)
+ flags |= TRANSPORT_PUSH_OPTIONS;
+
if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
if (!strcmp(*refspec, "refs/tags/*"))
return error(_("--all and --tags are incompatible"));
@@ -406,13 +410,16 @@ static int do_push(const char *repo, int flags)
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
} else {
struct transport *transport =
transport_get(remote, NULL);
-
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
@@ -500,6 +507,9 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int push_cert = -1;
int rc;
const char *repo = NULL; /* default repository */
+ static struct string_list push_options = STRING_LIST_INIT_DUP;
+ static struct string_list_item *item;
+
struct option options[] = {
OPT__VERBOSITY(&verbosity),
OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")),
@@ -533,6 +543,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
PARSE_OPT_OPTARG, option_parse_push_signed },
OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
+ OPT_STRING_LIST('o', "push-option", &push_options, N_("server-specific"), N_("option to transmit")),
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@ -563,7 +574,11 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
- rc = do_push(repo, flags);
+ for_each_string_list_item(item, &push_options)
+ if (strchr(item->string, '\n'))
+ die(_("push options must not have new line characters"));
+
+ rc = do_push(repo, flags, &push_options);
if (rc == -1)
usage_with_options(push_usage, options);
else
diff --git a/send-pack.c b/send-pack.c
index 299d303..3a842ac 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -260,6 +260,7 @@ static int generate_push_cert(struct strbuf *req_buf,
const char *push_cert_nonce)
{
const struct ref *ref;
+ struct string_list_item *item;
char *signing_key = xstrdup(get_signing_key());
const char *cp, *np;
struct strbuf cert = STRBUF_INIT;
@@ -276,6 +277,9 @@ static int generate_push_cert(struct strbuf *req_buf,
}
if (push_cert_nonce[0])
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
+ if (args->push_options)
+ for_each_string_list_item(item, args->push_options)
+ strbuf_addf(&cert, "push-option %s\n", item->string);
strbuf_addstr(&cert, "\n");
for (ref = remote_refs; ref; ref = ref->next) {
@@ -370,6 +374,8 @@ int send_pack(struct send_pack_args *args,
int agent_supported = 0;
int use_atomic = 0;
int atomic_supported = 0;
+ int use_push_options = 0;
+ int push_options_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
@@ -392,6 +398,8 @@ int send_pack(struct send_pack_args *args,
args->use_thin_pack = 0;
if (server_supports("atomic"))
atomic_supported = 1;
+ if (server_supports("push-options"))
+ push_options_supported = 1;
if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
int len;
@@ -418,6 +426,11 @@ int send_pack(struct send_pack_args *args,
use_atomic = atomic_supported && args->atomic;
+ if (args->push_options && !push_options_supported)
+ die(_("the receiving end does not support push options"));
+
+ use_push_options = push_options_supported && args->push_options;
+
if (status_report)
strbuf_addstr(&cap_buf, " report-status");
if (use_sideband)
@@ -426,6 +439,8 @@ int send_pack(struct send_pack_args *args,
strbuf_addstr(&cap_buf, " quiet");
if (use_atomic)
strbuf_addstr(&cap_buf, " atomic");
+ if (use_push_options)
+ strbuf_addstr(&cap_buf, " push-options");
if (agent_supported)
strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
@@ -512,6 +527,18 @@ int send_pack(struct send_pack_args *args,
strbuf_release(&req_buf);
strbuf_release(&cap_buf);
+ if (use_push_options) {
+ struct string_list_item *item;
+ struct strbuf sb = STRBUF_INIT;
+
+ for_each_string_list_item(item, args->push_options)
+ packet_buf_write(&sb, "%s", item->string);
+
+ write_or_die(out, sb.buf, sb.len);
+ packet_flush(out);
+ strbuf_release(&sb);
+ }
+
if (use_sideband && cmds_sent) {
memset(&demux, 0, sizeof(demux));
demux.proc = sideband_demux;
diff --git a/send-pack.h b/send-pack.h
index 57f222a..67fc40f 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -1,6 +1,8 @@
#ifndef SEND_PACK_H
#define SEND_PACK_H
+#include "string-list.h"
+
/* Possible values for push_cert field in send_pack_args. */
#define SEND_PACK_PUSH_CERT_NEVER 0
#define SEND_PACK_PUSH_CERT_IF_ASKED 1
@@ -21,6 +23,7 @@ struct send_pack_args {
push_cert:2,
stateless_rpc:1,
atomic:1;
+ const struct string_list *push_options;
};
struct option;
diff --git a/transport.c b/transport.c
index 095e61f..0298be1 100644
--- a/transport.c
+++ b/transport.c
@@ -510,6 +510,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
+ args.push_options = transport->push_options;
args.url = transport->url;
if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
diff --git a/transport.h b/transport.h
index c681408..6fe3485 100644
--- a/transport.h
+++ b/transport.h
@@ -48,6 +48,12 @@ struct transport {
*/
unsigned cloning : 1;
+ /*
+ * These strings will be passed to the {pre, post}-receive hook,
+ * on the remote side, if both sides support the push options capability.
+ */
+ const struct string_list *push_options;
+
/**
* Returns 0 if successful, positive if the option is not
* recognized or is inapplicable, and negative if the option
@@ -134,6 +140,7 @@ struct transport {
#define TRANSPORT_PUSH_CERT_ALWAYS 2048
#define TRANSPORT_PUSH_CERT_IF_ASKED 4096
#define TRANSPORT_PUSH_ATOMIC 8192
+#define TRANSPORT_PUSH_OPTIONS 16384
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
--
2.9.0.247.g176c4f7
^ permalink raw reply related [flat|nested] 35+ messages in thread
* [RFC PATCHv1 0/4] Push options in C Git
@ 2016-06-30 0:59 Stefan Beller
2016-06-30 0:59 ` [PATCH 3/4] push: accept push options Stefan Beller
0 siblings, 1 reply; 35+ messages in thread
From: Stefan Beller @ 2016-06-30 0:59 UTC (permalink / raw)
To: git, dwwang; +Cc: Stefan Beller
Allow a user to pass information along a push to the pre/post-receive hook
on the remote.
When using a remote that is more than just a plain Git host (e.g. Gerrit,
Git{hub/lab}, etc) this may become more obvious: The (server backend specific)
push options can instruct the server to:
* open a pull request
* send out emails asking for review
* (un)trigger continuous integration
* set priority for continuous integration (i.e. bots pushing may ask to be
treated with lower priority compared to humans)
* ...
Most of these actions can be done on the client side as well,
but in these remote-centric workflows it is easier to do that on the remote,
which is why we need to transport the information there.
More concrete examples:
* When you want a change in Gerrit to be submitted to refs/heads/master, you
push instead to a magic branch refs/for/master and Gerrit will create a change
for you (similar to a pull request). Instead we could imagine that you push
to a magical refs/heads/master with a push option "create-change".
* When pushing to Gerrit you can already attach some of this information by
adding a '%' followed by the parameter to the ref, i.e. when interacting with
Gerrit it is possible to do things like[1]:
git push origin HEAD:refs/for/master%draft%topic=example%cc=jon.doe@example.org
This is not appealing to our users as it looks like hacks upon hacks to make
it work. It would read better if it was spelled as:
git push origin HEAD:refs/for/master \
--push-option draft \
--push-option topic=example \
--push-option cc=jon.doe@example.org
(with a short form that is even easier to type,
but this is is more intuitive already)
This is a patch series to Git core, which is developed at the same time
as a change is proposed to JGit by Dan Wang, see [2].
This code is also available at [3].
Thanks,
Stefan
[1] Not all Gerrit '%' options are documented, so here is a link to source code instead :(
https://gerrit.googlesource.com/gerrit/+/refs/heads/master/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java#1141
[2] https://git.eclipse.org/r/#/c/74570/
[3] https://github.com/stefanbeller/git/tree/pushoptions
Stefan Beller (4):
push options: {pre,post}-receive hook learns about push options
receive-pack: implement advertising and receiving push options
push: accept push options
add a test for push options
Documentation/config.txt | 7 +-
Documentation/git-push.txt | 8 ++-
Documentation/githooks.txt | 4 ++
Documentation/technical/pack-protocol.txt | 10 +--
Documentation/technical/protocol-capabilities.txt | 8 +++
builtin/push.c | 16 ++++-
builtin/receive-pack.c | 85 +++++++++++++++++++----
send-pack.c | 29 ++++++++
send-pack.h | 3 +
t/t5544-push-options.sh | 85 +++++++++++++++++++++++
transport.c | 2 +
transport.h | 7 ++
12 files changed, 242 insertions(+), 22 deletions(-)
create mode 100755 t/t5544-push-options.sh
--
2.9.0.141.gdd65b60
^ permalink raw reply [flat|nested] 35+ messages in thread
* [PATCH 3/4] push: accept push options
2016-06-30 0:59 [RFC PATCHv1 0/4] Push options in C Git Stefan Beller
@ 2016-06-30 0:59 ` Stefan Beller
0 siblings, 0 replies; 35+ messages in thread
From: Stefan Beller @ 2016-06-30 0:59 UTC (permalink / raw)
To: git, dwwang; +Cc: Stefan Beller
This implements everything that is required on the client side to make use
of push options from the porcelain push command.
Signed-off-by: Stefan Beller <sbeller@google.com>
---
Documentation/git-push.txt | 8 +++++++-
builtin/push.c | 16 +++++++++++++---
send-pack.c | 29 +++++++++++++++++++++++++++++
send-pack.h | 3 +++
transport.c | 2 ++
transport.h | 7 +++++++
6 files changed, 61 insertions(+), 4 deletions(-)
diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt
index 19f46b6..b0b1273 100644
--- a/Documentation/git-push.txt
+++ b/Documentation/git-push.txt
@@ -11,7 +11,7 @@ SYNOPSIS
[verse]
'git push' [--all | --mirror | --tags] [--follow-tags] [--atomic] [-n | --dry-run] [--receive-pack=<git-receive-pack>]
[--repo=<repository>] [-f | --force] [-d | --delete] [--prune] [-v | --verbose]
- [-u | --set-upstream]
+ [-u | --set-upstream] [--push-option=<string>]
[--[no-]signed|--sign=(true|false|if-asked)]
[--force-with-lease[=<refname>[:<expect>]]]
[--no-verify] [<repository> [<refspec>...]]
@@ -156,6 +156,12 @@ already exists on the remote side.
Either all refs are updated, or on error, no refs are updated.
If the server does not support atomic pushes the push will fail.
+-L::
+--push-option::
+ Transmit the given string to the server, which passes them to
+ the pre-receive as well as the post-receive hook. Only C strings
+ containing no new lines are allowed.
+
--receive-pack=<git-receive-pack>::
--exec=<git-receive-pack>::
Path to the 'git-receive-pack' program on the remote
diff --git a/builtin/push.c b/builtin/push.c
index 4e9e4db..418f786 100644
--- a/builtin/push.c
+++ b/builtin/push.c
@@ -353,7 +353,8 @@ static int push_with_options(struct transport *transport, int flags)
return 1;
}
-static int do_push(const char *repo, int flags)
+static int do_push(const char *repo, int flags,
+ const struct string_list *push_options)
{
int i, errs;
struct remote *remote = pushremote_get(repo);
@@ -376,6 +377,9 @@ static int do_push(const char *repo, int flags)
if (remote->mirror)
flags |= (TRANSPORT_PUSH_MIRROR|TRANSPORT_PUSH_FORCE);
+ if (push_options->nr)
+ flags |= TRANSPORT_PUSH_OPTIONS;
+
if ((flags & TRANSPORT_PUSH_ALL) && refspec) {
if (!strcmp(*refspec, "refs/tags/*"))
return error(_("--all and --tags are incompatible"));
@@ -406,13 +410,16 @@ static int do_push(const char *repo, int flags)
for (i = 0; i < url_nr; i++) {
struct transport *transport =
transport_get(remote, url[i]);
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
} else {
struct transport *transport =
transport_get(remote, NULL);
-
+ if (flags & TRANSPORT_PUSH_OPTIONS)
+ transport->push_options = push_options;
if (push_with_options(transport, flags))
errs++;
}
@@ -500,6 +507,8 @@ int cmd_push(int argc, const char **argv, const char *prefix)
int push_cert = -1;
int rc;
const char *repo = NULL; /* default repository */
+ static struct string_list push_options = STRING_LIST_INIT_DUP;
+
struct option options[] = {
OPT__VERBOSITY(&verbosity),
OPT_STRING( 0 , "repo", &repo, N_("repository"), N_("repository")),
@@ -533,6 +542,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
0, "signed", &push_cert, "yes|no|if-asked", N_("GPG sign the push"),
PARSE_OPT_OPTARG, option_parse_push_signed },
OPT_BIT(0, "atomic", &flags, N_("request atomic transaction on remote side"), TRANSPORT_PUSH_ATOMIC),
+ OPT_STRING_LIST('L', "push-option", &push_options, N_("server-specific"), N_("options to transmit")),
OPT_SET_INT('4', "ipv4", &family, N_("use IPv4 addresses only"),
TRANSPORT_FAMILY_IPV4),
OPT_SET_INT('6', "ipv6", &family, N_("use IPv6 addresses only"),
@@ -563,7 +573,7 @@ int cmd_push(int argc, const char **argv, const char *prefix)
set_refspecs(argv + 1, argc - 1, repo);
}
- rc = do_push(repo, flags);
+ rc = do_push(repo, flags, &push_options);
if (rc == -1)
usage_with_options(push_usage, options);
else
diff --git a/send-pack.c b/send-pack.c
index 37ee04e..17c30a1 100644
--- a/send-pack.c
+++ b/send-pack.c
@@ -261,6 +261,7 @@ static int generate_push_cert(struct strbuf *req_buf,
const char *push_cert_nonce)
{
const struct ref *ref;
+ struct string_list_item *item;
char *signing_key = xstrdup(get_signing_key());
const char *cp, *np;
struct strbuf cert = STRBUF_INIT;
@@ -279,6 +280,12 @@ static int generate_push_cert(struct strbuf *req_buf,
strbuf_addf(&cert, "nonce %s\n", push_cert_nonce);
strbuf_addstr(&cert, "\n");
+ if (args->push_options) {
+ for_each_string_list_item(item, args->push_options)
+ strbuf_addf(&cert, "push-option %s\n", item->string);
+ strbuf_addstr(&cert, "\n");
+ }
+
for (ref = remote_refs; ref; ref = ref->next) {
if (check_to_send_update(ref, args) < 0)
continue;
@@ -371,6 +378,8 @@ int send_pack(struct send_pack_args *args,
int agent_supported = 0;
int use_atomic = 0;
int atomic_supported = 0;
+ int use_push_options = 0;
+ int push_options_supported = 0;
unsigned cmds_sent = 0;
int ret;
struct async demux;
@@ -393,6 +402,8 @@ int send_pack(struct send_pack_args *args,
args->use_thin_pack = 0;
if (server_supports("atomic"))
atomic_supported = 1;
+ if (server_supports("push-options"))
+ push_options_supported = 1;
if (args->push_cert != SEND_PACK_PUSH_CERT_NEVER) {
int len;
@@ -419,6 +430,11 @@ int send_pack(struct send_pack_args *args,
use_atomic = atomic_supported && args->atomic;
+ if (args->push_options && !push_options_supported)
+ die(_("the receiving end does not support push options"));
+
+ use_push_options = push_options_supported && args->push_options;
+
if (status_report)
strbuf_addstr(&cap_buf, " report-status");
if (use_sideband)
@@ -427,6 +443,8 @@ int send_pack(struct send_pack_args *args,
strbuf_addstr(&cap_buf, " quiet");
if (use_atomic)
strbuf_addstr(&cap_buf, " atomic");
+ if (use_push_options)
+ strbuf_addstr(&cap_buf, " push-options");
if (agent_supported)
strbuf_addf(&cap_buf, " agent=%s", git_user_agent_sanitized());
@@ -513,6 +531,17 @@ int send_pack(struct send_pack_args *args,
strbuf_release(&req_buf);
strbuf_release(&cap_buf);
+ if (use_push_options) {
+ struct string_list_item *item;
+ struct strbuf sb = STRBUF_INIT;
+
+ for_each_string_list_item(item, args->push_options)
+ packet_buf_write(&sb, "%s", item->string);
+ write_or_die(out, sb.buf, sb.len);
+ packet_flush(out);
+ strbuf_release(&sb);
+ }
+
if (use_sideband && cmds_sent) {
memset(&demux, 0, sizeof(demux));
demux.proc = sideband_demux;
diff --git a/send-pack.h b/send-pack.h
index 57f222a..67fc40f 100644
--- a/send-pack.h
+++ b/send-pack.h
@@ -1,6 +1,8 @@
#ifndef SEND_PACK_H
#define SEND_PACK_H
+#include "string-list.h"
+
/* Possible values for push_cert field in send_pack_args. */
#define SEND_PACK_PUSH_CERT_NEVER 0
#define SEND_PACK_PUSH_CERT_IF_ASKED 1
@@ -21,6 +23,7 @@ struct send_pack_args {
push_cert:2,
stateless_rpc:1,
atomic:1;
+ const struct string_list *push_options;
};
struct option;
diff --git a/transport.c b/transport.c
index 095e61f..598bd1f 100644
--- a/transport.c
+++ b/transport.c
@@ -510,6 +510,7 @@ static int git_transport_push(struct transport *transport, struct ref *remote_re
args.dry_run = !!(flags & TRANSPORT_PUSH_DRY_RUN);
args.porcelain = !!(flags & TRANSPORT_PUSH_PORCELAIN);
args.atomic = !!(flags & TRANSPORT_PUSH_ATOMIC);
+ args.push_options = transport->push_options;
args.url = transport->url;
if (flags & TRANSPORT_PUSH_CERT_ALWAYS)
@@ -640,6 +641,7 @@ struct transport *transport_get(struct remote *remote, const char *url)
struct transport *ret = xcalloc(1, sizeof(*ret));
ret->progress = isatty(2);
+ ret->push_options = NULL;
if (!remote)
die("No remote provided to transport_get()");
diff --git a/transport.h b/transport.h
index c681408..6fe3485 100644
--- a/transport.h
+++ b/transport.h
@@ -48,6 +48,12 @@ struct transport {
*/
unsigned cloning : 1;
+ /*
+ * These strings will be passed to the {pre, post}-receive hook,
+ * on the remote side, if both sides support the push options capability.
+ */
+ const struct string_list *push_options;
+
/**
* Returns 0 if successful, positive if the option is not
* recognized or is inapplicable, and negative if the option
@@ -134,6 +140,7 @@ struct transport {
#define TRANSPORT_PUSH_CERT_ALWAYS 2048
#define TRANSPORT_PUSH_CERT_IF_ASKED 4096
#define TRANSPORT_PUSH_ATOMIC 8192
+#define TRANSPORT_PUSH_OPTIONS 16384
#define TRANSPORT_SUMMARY_WIDTH (2 * DEFAULT_ABBREV + 3)
#define TRANSPORT_SUMMARY(x) (int)(TRANSPORT_SUMMARY_WIDTH + strlen(x) - gettext_width(x)), (x)
--
2.9.0.141.gdd65b60
^ permalink raw reply related [flat|nested] 35+ messages in thread
end of thread, other threads:[~2016-07-14 21:50 UTC | newest]
Thread overview: 35+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2016-07-07 1:12 [PATCHv3 0/4] Push options in C Git Stefan Beller
2016-07-07 1:12 ` [PATCH 1/4] push options: {pre,post}-receive hook learns about push options Stefan Beller
2016-07-07 20:20 ` Junio C Hamano
2016-07-07 21:50 ` Stefan Beller
2016-07-07 21:53 ` Junio C Hamano
2016-07-07 1:12 ` [PATCH 2/4] receive-pack: implement advertising and receiving " Stefan Beller
2016-07-07 20:37 ` Junio C Hamano
2016-07-07 21:41 ` Stefan Beller
2016-07-07 21:56 ` Jeff King
2016-07-07 22:06 ` Stefan Beller
2016-07-07 22:09 ` Jeff King
2016-07-07 22:06 ` Junio C Hamano
2016-07-08 17:58 ` Jonathan Nieder
2016-07-08 18:39 ` Junio C Hamano
2016-07-08 18:57 ` Stefan Beller
2016-07-08 21:46 ` Jeff King
2016-07-08 22:17 ` Stefan Beller
2016-07-08 22:21 ` Jeff King
2016-07-08 22:29 ` Stefan Beller
2016-07-08 22:35 ` Jeff King
2016-07-08 22:43 ` Stefan Beller
2016-07-08 22:46 ` Jeff King
2016-07-08 22:51 ` Stefan Beller
2016-07-07 1:12 ` [PATCH 3/4] push: accept " Stefan Beller
2016-07-07 20:52 ` Junio C Hamano
2016-07-08 22:59 ` Stefan Beller
2016-07-11 18:42 ` Junio C Hamano
2016-07-07 1:12 ` [PATCH 4/4] add a test for " Stefan Beller
2016-07-07 19:51 ` Junio C Hamano
2016-07-07 20:01 ` Junio C Hamano
2016-07-07 21:51 ` Stefan Beller
-- strict thread matches above, loose matches on Subject: below --
2016-07-14 21:49 [PATCHv7 0/4] Push options Stefan Beller
2016-07-14 21:49 ` [PATCH 3/4] push: accept push options Stefan Beller
2016-07-14 17:39 [PATCHv5 0/4] Push options Stefan Beller
2016-07-14 17:39 ` [PATCH 3/4] push: accept push options Stefan Beller
2016-07-09 0:31 [PATCHv4 0/4] Push options Stefan Beller
2016-07-09 0:31 ` [PATCH 3/4] push: accept push options Stefan Beller
2016-06-30 0:59 [RFC PATCHv1 0/4] Push options in C Git Stefan Beller
2016-06-30 0:59 ` [PATCH 3/4] push: accept push options Stefan Beller
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).