git/t/t5411/test-0013-bad-protocol.sh
Jiang Xin 15d3af5e22 receive-pack: add new proc-receive hook
Git calls an internal `execute_commands` function to handle commands
sent from client to `git-receive-pack`.  Regardless of what references
the user pushes, git creates or updates the corresponding references if
the user has write-permission.  A contributor who has no
write-permission, cannot push to the repository directly.  So, the
contributor has to write commits to an alternate location, and sends
pull request by emails or by other ways.  We call this workflow as a
distributed workflow.

It would be more convenient to work in a centralized workflow like what
Gerrit provided for some cases.  For example, a read-only user who
cannot push to a branch directly can run the following `git push`
command to push commits to a pseudo reference (has a prefix "refs/for/",
not "refs/heads/") to create a code review.

    git push origin \
        HEAD:refs/for/<branch-name>/<session>

The `<branch-name>` in the above example can be as simple as "master",
or a more complicated branch name like "foo/bar".  The `<session>` in
the above example command can be the local branch name of the client
side, such as "my/topic".

We cannot implement a centralized workflow elegantly by using
"pre-receive" + "post-receive", because Git will call the internal
function "execute_commands" to create references (even the special
pseudo reference) between these two hooks.  Even though we can delete
the temporarily created pseudo reference via the "post-receive" hook,
having a temporary reference is not safe for concurrent pushes.

So, add a filter and a new handler to support this kind of workflow.
The filter will check the prefix of the reference name, and if the
command has a special reference name, the filter will turn a specific
field (`run_proc_receive`) on for the command.  Commands with this filed
turned on will be executed by a new handler (a hook named
"proc-receive") instead of the internal `execute_commands` function.
We can use this "proc-receive" command to create pull requests or send
emails for code review.

Suggested by Junio, this "proc-receive" hook reads the commands,
push-options (optional), and send result using a protocol in pkt-line
format.  In the following example, the letter "S" stands for
"receive-pack" and letter "H" stands for the hook.

    # Version and features negotiation.
    S: PKT-LINE(version=1\0push-options atomic...)
    S: flush-pkt
    H: PKT-LINE(version=1\0push-options...)
    H: flush-pkt

    # Send commands from server to the hook.
    S: PKT-LINE(<old-oid> <new-oid> <ref>)
    S: ... ...
    S: flush-pkt
    # Send push-options only if the 'push-options' feature is enabled.
    S: PKT-LINE(push-option)
    S: ... ...
    S: flush-pkt

    # Receive result from the hook.
    # OK, run this command successfully.
    H: PKT-LINE(ok <ref>)
    # NO, I reject it.
    H: PKT-LINE(ng <ref> <reason>)
    # Fall through, let 'receive-pack' to execute it.
    H: PKT-LINE(ok <ref>)
    H: PKT-LINE(option fall-through)
    # OK, but has an alternate reference.  The alternate reference name
    # and other status can be given in options
    H: PKT-LINE(ok <ref>)
    H: PKT-LINE(option refname <refname>)
    H: PKT-LINE(option old-oid <old-oid>)
    H: PKT-LINE(option new-oid <new-oid>)
    H: PKT-LINE(option forced-update)
    H: ... ...
    H: flush-pkt

After receiving a command, the hook will execute the command, and may
create/update different reference.  For example, a command for a pseudo
reference "refs/for/master/topic" may create/update different reference
such as "refs/pull/123/head".  The alternate reference name and other
status are given in option lines.

The list of commands returned from "proc-receive" will replace the
relevant commands that are sent from user to "receive-pack", and
"receive-pack" will continue to run the "execute_commands" function and
other routines.  Finally, the result of the execution of these commands
will be reported to end user.

The reporting function from "receive-pack" to "send-pack" will be
extended in latter commit just like what the "proc-receive" hook reports
to "receive-pack".

Signed-off-by: Jiang Xin <zhiyou.jx@alibaba-inc.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2020-08-27 12:47:47 -07:00

217 lines
7.3 KiB
Bash

test_expect_success "setup proc-receive hook (unknown version, $PROTOCOL)" '
write_script "$upstream/hooks/proc-receive" <<-EOF
printf >&2 "# proc-receive hook\n"
test-tool proc-receive -v --version 2
EOF
'
# Refs of upstream : master(A)
# Refs of workbench: master(A) tags/v123
# git push : refs/for/master/topic(A)
test_expect_success "proc-receive: bad protocol (unknown version, $PROTOCOL)" '
test_must_fail git -C workbench push origin \
HEAD:refs/for/master/topic \
>out 2>&1 &&
make_user_friendly_and_stable_output <out >actual &&
# Check status report for git-push
sed -n \
-e "/^To / { p; n; p; }" \
<actual >actual-report &&
cat >expect <<-EOF &&
To <URL/of/upstream.git>
! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
EOF
test_cmp expect actual-report &&
# Check error message from "receive-pack", but ignore unstable fatal error
# message ("remote: fatal: the remote end hung up unexpectedly") which
# is different from the remote HTTP server with different locale settings.
grep "^remote: error:" <actual >actual-error &&
cat >expect <<-EOF &&
remote: error: proc-receive version "2" is not supported
EOF
test_cmp expect actual-error &&
git -C "$upstream" show-ref >out &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
<COMMIT-A> refs/heads/master
EOF
test_cmp expect actual
'
test_expect_success "setup proc-receive hook (hook --die-version, $PROTOCOL)" '
write_script "$upstream/hooks/proc-receive" <<-EOF
printf >&2 "# proc-receive hook\n"
test-tool proc-receive -v --die-version
EOF
'
# Refs of upstream : master(A)
# Refs of workbench: master(A) tags/v123
# git push : refs/for/master/topic(A)
test_expect_success "proc-receive: bad protocol (hook --die-version, $PROTOCOL)" '
test_must_fail git -C workbench push origin \
HEAD:refs/for/master/topic \
>out 2>&1 &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
remote: # pre-receive hook
remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
remote: # proc-receive hook
remote: fatal: bad protocol version: 1
remote: error: proc-receive version "0" is not supported
To <URL/of/upstream.git>
! [remote rejected] HEAD -> refs/for/master/topic (fail to run proc-receive hook)
EOF
test_cmp expect actual &&
git -C "$upstream" show-ref >out &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
<COMMIT-A> refs/heads/master
EOF
test_cmp expect actual
'
test_expect_success "setup proc-receive hook (hook --die-readline, $PROTOCOL)" '
write_script "$upstream/hooks/proc-receive" <<-EOF
printf >&2 "# proc-receive hook\n"
test-tool proc-receive -v --die-readline
EOF
'
# Refs of upstream : master(A)
# Refs of workbench: master(A) tags/v123
# git push : refs/for/master/topic(A)
test_expect_success "proc-receive: bad protocol (hook --die-readline, $PROTOCOL)" '
test_must_fail git -C workbench push origin \
HEAD:refs/for/master/topic \
>out 2>&1 &&
make_user_friendly_and_stable_output <out >actual &&
grep "remote: fatal: protocol error: expected \"old new ref\", got \"<ZERO-OID> <COMMIT-A> refs/for/master/topic\"" actual &&
git -C "$upstream" show-ref >out &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
<COMMIT-A> refs/heads/master
EOF
test_cmp expect actual
'
test_expect_success "setup proc-receive hook (no report, $PROTOCOL)" '
write_script "$upstream/hooks/proc-receive" <<-EOF
printf >&2 "# proc-receive hook\n"
test-tool proc-receive -v
EOF
'
# Refs of upstream : master(A)
# Refs of workbench: master(A) tags/v123
# git push : next(A) refs/for/master/topic(A)
test_expect_success "proc-receive: bad protocol (no report, $PROTOCOL)" '
test_must_fail git -C workbench push origin \
HEAD:refs/heads/next \
HEAD:refs/for/master/topic >out 2>&1 &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
remote: # pre-receive hook
remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
remote: # proc-receive hook
remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
remote: # post-receive hook
remote: post-receive< <ZERO-OID> <COMMIT-A> refs/heads/next
To <URL/of/upstream.git>
* [new branch] HEAD -> next
! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
EOF
test_cmp expect actual &&
git -C "$upstream" show-ref >out &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
<COMMIT-A> refs/heads/master
<COMMIT-A> refs/heads/next
EOF
test_cmp expect actual
'
# Refs of upstream : master(A) next(A)
# Refs of workbench: master(A) tags/v123
test_expect_success "cleanup ($PROTOCOL)" '
git -C "$upstream" update-ref -d refs/heads/next
'
test_expect_success "setup proc-receive hook (no ref, $PROTOCOL)" '
write_script "$upstream/hooks/proc-receive" <<-EOF
printf >&2 "# proc-receive hook\n"
test-tool proc-receive -v \
-r "ok"
EOF
'
# Refs of upstream : master(A)
# Refs of workbench: master(A) tags/v123
# git push : refs/for/master/topic
test_expect_success "proc-receive: bad protocol (no ref, $PROTOCOL)" '
test_must_fail git -C workbench push origin \
HEAD:refs/for/master/topic\
>out 2>&1 &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
remote: # pre-receive hook
remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
remote: # proc-receive hook
remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
remote: proc-receive> ok
remote: error: proc-receive reported incomplete status line: "ok"
To <URL/of/upstream.git>
! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
EOF
test_cmp expect actual &&
git -C "$upstream" show-ref >out &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
<COMMIT-A> refs/heads/master
EOF
test_cmp expect actual
'
test_expect_success "setup proc-receive hook (unknown status, $PROTOCOL)" '
write_script "$upstream/hooks/proc-receive" <<-EOF
printf >&2 "# proc-receive hook\n"
test-tool proc-receive -v \
-r "xx refs/for/master/topic"
EOF
'
# Refs of upstream : master(A)
# Refs of workbench: master(A) tags/v123
# git push : refs/for/master/topic
test_expect_success "proc-receive: bad protocol (unknown status, $PROTOCOL)" '
test_must_fail git -C workbench push origin \
HEAD:refs/for/master/topic \
>out 2>&1 &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
remote: # pre-receive hook
remote: pre-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
remote: # proc-receive hook
remote: proc-receive< <ZERO-OID> <COMMIT-A> refs/for/master/topic
remote: proc-receive> xx refs/for/master/topic
remote: error: proc-receive reported bad status "xx" on ref "refs/for/master/topic"
To <URL/of/upstream.git>
! [remote rejected] HEAD -> refs/for/master/topic (proc-receive failed to report status)
EOF
test_cmp expect actual &&
git -C "$upstream" show-ref >out &&
make_user_friendly_and_stable_output <out >actual &&
cat >expect <<-EOF &&
<COMMIT-A> refs/heads/master
EOF
test_cmp expect actual
'