reconquest / atlassian-external-hooks

External Hooks plugin for Atlassian Bitbucket
https://external-hooks.reconquest.io
Other
44 stars 37 forks source link

Support for git push options #79

Open esaborg opened 5 years ago

esaborg commented 5 years ago

I would find it very useful to be able to send extra strings to pre receive hook from git push command.

seletskiy commented 5 years ago

@esaborg: what kind of data you want to pass from git push?

esaborg commented 5 years ago

@seletskiy: Here's my use case. With certain criteria I want to lock develop branch from all pushed and merges. This is trivial, but I need a way to bypass the lock with some key and that's where the push options would help. That would allow me prevent all unwanted accidental pushes but enable to push with some key.

I know this is likely quite niche use case, but I believe there could be also other cases where which would benefit from the ability to add extra string info to the push.

kovetskiy commented 5 years ago

Hi, I see now, the use case is pretty simple and reasonable.

Unfortunately, I don't see any way to pass any variable with git push, because git-send-pack and git-receive-pack just don't support it.

I think you can implement a workaround by adding a git tag on your branch and then pushing it to the remote repository with --follow-tags flag, then in your hook, you can obtain a list of tags on the pushed branch and decide either allow it or no.

Or you can push any file with special contents and check that file in your hook. (a dirty way, in my opinion)

Please let me know if it is working for you or no.

seletskiy commented 5 years ago

@esaborg: another way for your specific case is to create another user and push sensitive changes using key assigned to this username. In the hook itself you can add logic to restrict changes for given username only.

esaborg commented 5 years ago

@kovetskiy As I understand, git push supports --push-options (https://git-scm.com/docs/git-push#git-push--oltoptiongt) which would be quite suitable for this.

Adding a special file or tag would work as a dirty workaround. Creating a special user for this as @seletskiy suggests doesn't work as nicely since I would like any user to be able to push the changes. The trick is that having a hook like this would prevent the accidental pushed when develop branch is supposed to be more or less locked.

kovetskiy commented 5 years ago

@esaborg

I beg your pardon, git push indeed supports push options.

I made small research and you even can do it in bitbucket with External Hooks plugin.

Let's imagine some simple PreReceive hook:

#!/bin/bash

set -euo pipefail

enforced=false
if [[ "${GIT_PUSH_OPTION_COUNT:-}" && "${GIT_PUSH_OPTION_COUNT}" != "0" ]]; then
    for (( index=0; index<$GIT_PUSH_OPTION_COUNT; index++ )); do
        option_env="GIT_PUSH_OPTION_${index}"
        option=${!option_env}

        echo "option #${index}: ${option}"

        if [[ "$option" == "enforce" ]]; then
            enforced=true
        fi
    done
fi

if $enforced; then
    exit 0
fi

echo "Sorry, git push to this repository is temporarily disabled." >&2
exit 1

The script will not allow a user to push unless -o enforce specified while running git push Variables that user passes specified in GIT_PUSHOPTION variables as it's specified in manual page:

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.

The only problem is that by default git push options are not allowed in repositories, so when you push you will receive the following error:

fatal: the receiving end does not support push options
fatal: the remote end hung up unexpectedly
error: failed to push some refs to 'http://admin@desktop:7990/bitbucket/scm/project_1/rep_1.git'

I didn't find any more elegant way to allow it except modifying file in $BITBUCKET_HOME/shared/config/git/system-config and adding following lines at the end of file:

[receive]
    advertisePushOptions = true

With this configuration it's possible to do what you want.

Push without options:

 → rep_1 b✓ git push origin b
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 16 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 611 bytes | 611.00 KiB/s, done.
Total 6 (delta 3), reused 0 (delta 0)
remote: Push rejected by External Hook
remote: Sorry, git push to this repository is temporarily disabled.
remote:
To http://desktop:7990/bitbucket/scm/project_1/rep_1.git
 ! [remote rejected] b -> b (pre-receive hook declined)
error: failed to push some refs to 'http://admin@desktop:7990/bitbucket/scm/project_1/rep_1.git'

Push with a few but push options:

 → rep_1 b✓ git push origin b -o test_a -o test_b
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 16 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 611 bytes | 611.00 KiB/s, done.
Total 6 (delta 3), reused 0 (delta 0)
remote: Push rejected by External Hook
remote: option #0: test_a
remote: option #1: test_b
remote: Sorry, git push to this repository is temporarily disabled.
remote:
To http://desktop:7990/bitbucket/scm/project_1/rep_1.git
 ! [remote rejected] b -> b (pre-receive hook declined)
error: failed to push some refs to 'http://admin@desktop:7990/bitbucket/scm/project_1/rep_1.git'

And finally, push with enforce flag:

 → rep_1 b✓ git push origin b -o enforce
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 16 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 611 bytes | 611.00 KiB/s, done.
Total 6 (delta 3), reused 0 (delta 0)
remote:
remote: Create pull request for b:
remote:   http://desktop:7990/bitbucket/projects/PROJECT_1/repos/rep_1/compare/commits?sourceBranch=refs/heads/b
remote:
To http://desktop:7990/bitbucket/scm/project_1/rep_1.git
   9184775..81f94ec  b -> b

Hope it is helpful for you.

esaborg commented 5 years ago

@kovetskiy

Thank, that's exactly what I was looking for.

For some reason that script does not work on my environment. To be more specific, the environment variables remains unset for some reason. Adding the advertisePushOptions = true line enabled the possibility to pass the variables, but I'm not seeing any values on server end.

I'm running git version 2.18.0, so that shouldn't be the problem.

seletskiy commented 4 years ago

@esaborg did you resolve the issue?