2018-12-18 02:08:53 +01:00
|
|
|
#!/usr/bin/env bash
|
2018-05-23 03:23:31 +02:00
|
|
|
set -e
|
|
|
|
|
2020-10-15 04:55:57 +02:00
|
|
|
usage() {
|
2021-01-22 19:47:45 +01:00
|
|
|
cat <<EOF
|
|
|
|
usage: $0 [--merge] PULL_REQUEST_ID [REMOTE]
|
2018-05-23 03:23:31 +02:00
|
|
|
|
|
|
|
Force-push our HEAD to the given GitHub pull request branch.
|
|
|
|
|
|
|
|
Useful for a maintainer to run just before pushing to master,
|
|
|
|
after tweaking the branch and/or rebasing to latest. This causes
|
|
|
|
GitHub to see the subsequent push to master as representing a
|
|
|
|
merge of the PR, rather than requiring the PR to be manually
|
|
|
|
(and to the casual observer misleadingly) closed instead.
|
|
|
|
|
2021-01-22 19:48:09 +01:00
|
|
|
With --merge, also go ahead and merge the PR.
|
|
|
|
|
2018-06-22 02:25:49 +02:00
|
|
|
REMOTE defaults to the value of the Git config variable
|
|
|
|
\`zulip.zulipRemote\` if set, else to \`upstream\`.
|
|
|
|
|
2020-02-11 23:37:42 +01:00
|
|
|
If we have a pseudo-remote-tracking branch for the PR (as created
|
|
|
|
by \`reset-to-pull-request\`, like \`pr/1234\`), then the tracking
|
|
|
|
branch is updated to reflect the pushed version.
|
|
|
|
|
2018-05-23 03:23:31 +02:00
|
|
|
See also \`reset-to-pull-request\`.
|
|
|
|
EOF
|
|
|
|
}
|
|
|
|
|
2021-01-22 19:48:09 +01:00
|
|
|
args="$(getopt -o '' --long help,merge -n "$0" -- "$@")"
|
2021-01-22 19:47:45 +01:00
|
|
|
eval "set -- $args"
|
|
|
|
|
2021-01-22 19:48:09 +01:00
|
|
|
merge=
|
|
|
|
|
2021-01-22 19:47:45 +01:00
|
|
|
while true; do
|
|
|
|
case "$1" in
|
|
|
|
--help)
|
|
|
|
usage
|
|
|
|
exit 0
|
|
|
|
;;
|
2021-01-22 19:48:09 +01:00
|
|
|
--merge)
|
|
|
|
merge=t
|
|
|
|
shift
|
|
|
|
;;
|
2021-01-22 19:47:45 +01:00
|
|
|
--)
|
|
|
|
shift
|
|
|
|
break
|
|
|
|
;;
|
|
|
|
esac
|
|
|
|
done
|
|
|
|
|
2018-06-22 02:25:49 +02:00
|
|
|
remote_default="$(git config zulip.zulipRemote || echo upstream)"
|
2018-09-12 00:48:20 +02:00
|
|
|
pseudo_remote="$(git config zulip.prPseudoRemote || echo)"
|
2018-06-22 02:25:49 +02:00
|
|
|
|
2018-05-23 03:23:31 +02:00
|
|
|
pr_id="$1"
|
2018-06-22 02:25:49 +02:00
|
|
|
remote="${2:-"$remote_default"}"
|
2018-05-23 03:23:31 +02:00
|
|
|
|
|
|
|
if [ -z "$pr_id" ]; then
|
2021-01-22 19:47:45 +01:00
|
|
|
usage >&2
|
|
|
|
exit 1
|
2018-05-23 03:23:31 +02:00
|
|
|
fi
|
|
|
|
|
2018-07-24 08:10:15 +02:00
|
|
|
if ! jq --version >/dev/null 2>&1; then
|
|
|
|
cat >&2 <<EOF
|
|
|
|
error: not found: jq
|
|
|
|
|
|
|
|
push-to-pull-request requires the \`jq\` utility; you should install it.
|
|
|
|
Try:
|
|
|
|
|
|
|
|
sudo apt install jq
|
|
|
|
EOF
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
push-to-pull-request: Use `git remote get-url`.
This gives us the right behavior when using the `url.*.insteadOf`
mechanism for aliases in Git remote URLs. For example, if
one's ~/.gitconfig has:
[url "git@github.com:"]
insteadOf = gh:
then `git remote add upstream gh:zulip/zulip` will work great, as
the nice, short, mnemonic `gh:` prefix gets expanded to the more
finicky `git@github.com:`. I use just such a prefix routinely.
But the feature does require that scripts go through the right
abstractions. In particular `git remote get-url`, since Git 2.7
(from 2016), exists for exactly this reason. A plain `git config`
command bypasses the expansion, getting the verbatim `gh:...`
version, which doesn't work.
So, switch to that.
As a bonus, we get to behave correctly if for some reason the user
has configured a push URL distinct from the fetch URL for this
remote, just by adding `--push`. With `git config`, we'd have had
to manually implement the fallback from `remote.upstream.pushUrl` to
`remote.upstream.url` in order to properly handle that case.
2020-02-11 23:04:18 +01:00
|
|
|
remote_url="$(git remote get-url --push "$remote")"
|
2018-05-23 03:23:31 +02:00
|
|
|
repo_fq="$(echo "$remote_url" | perl -lne 'print $1 if (
|
|
|
|
m, ^ git\@github\.com:
|
2018-06-02 01:36:56 +02:00
|
|
|
([^/]+ / [^/]+?)
|
|
|
|
(?:\.git)?
|
|
|
|
$ ,x )')"
|
2018-05-23 03:23:31 +02:00
|
|
|
|
|
|
|
if [ -z "$repo_fq" ]; then
|
|
|
|
# We're pretty specific about what we expect the URL to look like;
|
|
|
|
# there are probably more cases we could legitimately cover, which
|
|
|
|
# we can add if/when they come up for someone.
|
2020-02-11 23:40:49 +01:00
|
|
|
echo "error: couldn't parse remote URL as GitHub repo: $remote_url" >&2
|
2018-05-23 03:23:31 +02:00
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
|
|
|
# See https://developer.github.com/v3/pulls/#get-a-single-pull-request .
|
|
|
|
# This is the old REST API; the new GraphQL API does look neat, but it
|
|
|
|
# seems to require authentication even for simple lookups of public data,
|
|
|
|
# and that'd be a pain for a simple script like this.
|
2018-06-02 00:59:36 +02:00
|
|
|
pr_url=https://api.github.com/repos/"${repo_fq}"/pulls/"${pr_id}"
|
2021-07-13 21:00:50 +02:00
|
|
|
pr_details="$(curl -fLsS "$pr_url")"
|
2018-05-23 03:23:31 +02:00
|
|
|
|
2020-10-15 04:55:57 +02:00
|
|
|
pr_jq() {
|
2018-05-23 03:23:31 +02:00
|
|
|
echo "$pr_details" | jq "$@"
|
|
|
|
}
|
2018-06-02 01:43:23 +02:00
|
|
|
|
|
|
|
if [ "$(pr_jq -r .message)" = "Not Found" ]; then
|
2018-07-24 08:13:03 +02:00
|
|
|
echo "error: invalid PR URL: $pr_url" >&2
|
2018-06-02 00:59:36 +02:00
|
|
|
exit 1
|
|
|
|
fi
|
2018-05-23 03:23:31 +02:00
|
|
|
|
|
|
|
if [ "$(pr_jq .maintainer_can_modify)" != "true" ]; then
|
2018-06-02 01:49:36 +02:00
|
|
|
# This happens when the PR has already been merged or closed, or
|
|
|
|
# if the contributor has turned off the (default) setting to allow
|
|
|
|
# maintainers of the target repo to push to their PR branch.
|
|
|
|
#
|
|
|
|
# The latter seems to be rare (in Greg's experience doing the
|
|
|
|
# manual equivalent of this script for many different
|
|
|
|
# contributors, none have ever chosen this setting), but give a
|
|
|
|
# decent error message if it does happen.
|
|
|
|
echo "error: PR already closed, or contributor has disallowed pushing to branch" >&2
|
2018-05-23 03:23:31 +02:00
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
|
2021-01-22 19:48:09 +01:00
|
|
|
if [ "$merge" ]; then
|
|
|
|
pr_base_ref="$(pr_jq -r .base.ref)"
|
|
|
|
git fetch -- "$remote"
|
|
|
|
if ! git merge-base --is-ancestor -- "$remote/$pr_base_ref" @; then
|
|
|
|
echo "error: You need to rebase on $remote/$pr_base_ref first" >&2
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
fi
|
|
|
|
|
2018-05-23 03:23:31 +02:00
|
|
|
pr_head_repo_fq="$(pr_jq -r .head.repo.full_name)"
|
|
|
|
pr_head_refname="$(pr_jq -r .head.ref)"
|
|
|
|
|
2018-09-12 00:48:20 +02:00
|
|
|
tracking_ref=
|
|
|
|
if [ -n "$pseudo_remote" ]; then
|
|
|
|
tracking_ref=$(git rev-parse -q --verify --symbolic refs/remotes/"$pseudo_remote"/"$pr_id" || echo)
|
|
|
|
fi
|
|
|
|
|
2018-05-23 03:23:31 +02:00
|
|
|
set -x
|
2018-09-12 00:48:20 +02:00
|
|
|
git push git@github.com:"$pr_head_repo_fq" +@:"$pr_head_refname"
|
|
|
|
|
|
|
|
{ set +x; } 2>&-
|
|
|
|
if [ -n "$tracking_ref" ]; then
|
|
|
|
set -x
|
|
|
|
git update-ref "$tracking_ref" @
|
|
|
|
fi
|
2021-01-22 19:48:09 +01:00
|
|
|
|
|
|
|
if [ "$merge" ]; then
|
2021-02-03 21:07:06 +01:00
|
|
|
tries=10
|
|
|
|
sha="$(git rev-parse @)"
|
|
|
|
while
|
|
|
|
out=$(git ls-remote -- "$remote" "refs/pull/$pr_id/head")
|
|
|
|
[ "$out" != "$sha refs/pull/$pr_id/head" ]
|
|
|
|
do
|
|
|
|
if ! ((--tries)); then
|
|
|
|
echo 'error: Push was not observed in PR' >&2
|
|
|
|
exit 1
|
|
|
|
fi
|
|
|
|
sleep 1
|
|
|
|
done
|
2021-01-22 19:48:09 +01:00
|
|
|
git push -- "$remote" "@:$pr_base_ref"
|
|
|
|
fi
|