281 lines
8.1 KiB
Bash
Executable File
281 lines
8.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
function help() {
|
|
pr=123
|
|
local_ref=foo
|
|
script="$(basename "$0") $pr"
|
|
cat <<HELP
|
|
-=[ GitHub Pull Request Helper - "Cowboy" Ben Alman - http://benalman.com/ ]=-
|
|
|
|
Usage: $(basename "$0") pull_request_id [ step ]
|
|
|
|
-=[ Description ]============================================================-
|
|
|
|
The "step" argument can be used to force execution of a particular step,
|
|
but by default this script will automatically choose the next step, from
|
|
STEP 1 to STEP 2 and finally to STEP 3. You'll have to execute STEP 4
|
|
manually, once everything is done.
|
|
|
|
-=[ Detailed workflow example ]==============================================-
|
|
|
|
For this example, assume PR $pr has been filed against the "$local_ref" branch.
|
|
|
|
-=[ STEP 1 ]=-
|
|
|
|
You run "$script" and this executes:
|
|
|
|
$(help_step1)
|
|
|
|
-=[ STEP 2 ]=-
|
|
|
|
You run "$script" and this executes:
|
|
|
|
$(help_step2)
|
|
|
|
-=[ STEP 3 ]=-
|
|
|
|
You run "$script" and this executes:
|
|
|
|
$(help_step3)
|
|
|
|
-=[ STEP 4 ]=-
|
|
|
|
You run "$script 4" (explicitly specifying the 4) and this executes:
|
|
|
|
$(help_step4)
|
|
|
|
-=[ Very important notes ]===================================================-
|
|
|
|
Before running this script, ensure that your local "$local_ref" branch is
|
|
up-to-date!
|
|
|
|
At the beginning of STEP 1, any existing "pr$pr" and "pr$pr-squash"
|
|
branches will be deleted.
|
|
|
|
At the beginning of STEP 2, any existing pr$pr-squash" branch will be
|
|
deleted.
|
|
|
|
If you want to hide per-step explanations, set ENV var GPR_SHH=1. For
|
|
example, create an alias like: alias gpr='GPR_SHH=1 gpr'
|
|
|
|
-=[ License ]================================================================-
|
|
|
|
Copyright (c) 2012 "Cowboy" Ben Alman
|
|
Licensed under the MIT license.
|
|
http://benalman.com/about/license/
|
|
HELP
|
|
[[ "$1" ]]; exit
|
|
}
|
|
|
|
function help_step1() {
|
|
echo " Fetch and rebase PR $pr into \"pr$pr\" branch"
|
|
[[ "$1" ]] && return
|
|
cat <<HELP
|
|
1. Fetch the repo and branch associated with PR $pr.
|
|
2. Checkout a new "pr$pr" branch at the HEAD of the fetched branch.
|
|
3. Rebase "$local_ref" branch onto "pr$pr" branch.
|
|
4. Display a list of files changed in the PR.
|
|
|
|
Note that you may need to resolve conflicts. If the rebase is successful,
|
|
test the PR and commit fixes. Commit as many times as necessary; It doesn't
|
|
matter because all PR-related commits will be squash merged in STEP 2.
|
|
HELP
|
|
}
|
|
|
|
function help_step2() {
|
|
echo " Perform squash merge into \"pr$pr-squash\" branch"
|
|
[[ "$1" ]] && return
|
|
cat <<HELP
|
|
1. Checkout a new "pr$pr-squash" branch from "$local_ref" branch.
|
|
2. Squash merge "pr$pr" branch into "pr$pr-squash" branch. (no commit)
|
|
3. Append "Closes gh-$pr." to SQUASH_MSG.
|
|
4. Commit using the PR branch's HEAD author name.
|
|
|
|
Since all the commits in the PR have been squashed into one commit, you will
|
|
need to edit the commit message. When done, inspect the commit log, ensuring
|
|
everything is perfect; you should see a single, beautiful commit.
|
|
HELP
|
|
}
|
|
|
|
function help_step3() {
|
|
echo " Perform final merge into \"$local_ref\" branch"
|
|
[[ "$1" ]] && return
|
|
cat <<HELP
|
|
1. Checkout "$local_ref" branch.
|
|
2. Merge "$pr-squash" branch into "$local_ref" branch.
|
|
|
|
If STEP 2 was successful, there should be no conflicts here. If there are,
|
|
you're doing it wrong. Just double-check the commit log and push when done.
|
|
HELP
|
|
}
|
|
|
|
function help_step4() {
|
|
echo " Cleanup temporary branches and tags"
|
|
[[ "$1" ]] && return
|
|
cat <<HELP
|
|
1. Temporary "pr$pr" and "pr$pr-squash" branches are deleted.
|
|
2. Temporary "_pr${pr}_author_head" tag is deleted.
|
|
HELP
|
|
}
|
|
|
|
function header() {
|
|
if [[ "$GPR_SHH" ]]; then
|
|
echo "-=[ STEP $1 ]=================================================================-"
|
|
else
|
|
echo "-=[ STEP $1 Overview ]========================================================-"
|
|
help_step$1
|
|
echo
|
|
echo "-=[ Actual ]=================================================================-"
|
|
fi
|
|
}
|
|
|
|
[[ ! "$1" || "$1" == "-h" || "$1" == "--help" ]] && help $1
|
|
|
|
# Generate and store an OAUTH token.
|
|
if [[ "$1" == "auth" ]]; then
|
|
read -p 'Enter GitHub username: '
|
|
json="$(curl -fsSL --data '{"note":"gpr","scopes":["repo"]}' https://api.github.com/authorizations -u "$REPLY")"
|
|
if [[ "$json" ]]; then
|
|
token="$(node -pe "($json).token")"
|
|
git config --global --remove-section gpr 2>/dev/null
|
|
git config --global --add gpr.token $token
|
|
echo "Authorization successful, token saved."
|
|
exit
|
|
else
|
|
echo "Error authorizing with GitHub, please try again."
|
|
exit 5
|
|
fi
|
|
fi
|
|
|
|
pr="$1"; shift
|
|
script="$(basename "$0") $pr"
|
|
branch="pr$pr"
|
|
|
|
repo="$(git remote show -n origin | perl -ne '/Fetch URL: .*github\.com[:\/](.*\/.*)\.git/ && print $1')"
|
|
|
|
# Let's fetch some JSON.
|
|
token="$(git config --get gpr.token)"
|
|
json="$(curl -fsSL "https://api.github.com/repos/$repo/pulls/$pr?access_token=$token" 2>/dev/null)"
|
|
if [[ $? != 0 || ! "$json" ]]; then
|
|
echo "Error fetching GitHub API data for $repo PR $pr!"
|
|
echo "If you're trying to access a private repo and haven't yet done so, please run"
|
|
echo "the \"$(basename "$0") auth\" command to generate a GitHub auth token."
|
|
exit 2
|
|
fi
|
|
|
|
# Let's parse some JSON.
|
|
remote_url="$(node -pe "($json).head.repo.git_url")"
|
|
remote_ref="$(node -pe "($json).head.ref")"
|
|
local_url="$(node -pe "($json).base.repo.git_url")"
|
|
local_ref="$(node -pe "($json).base.ref")"
|
|
num_commits="$(node -pe "($json).commits")"
|
|
|
|
# Let's get the project's .git folder.
|
|
git_dir="$(git rev-parse --show-toplevel)/.git"
|
|
|
|
function del_branch() {
|
|
if [[ "$(git branch | grep " $1\$")" ]]; then
|
|
git checkout "$local_ref" 2>/dev/null
|
|
git branch -D "$1" 2>/dev/null
|
|
fi
|
|
}
|
|
|
|
# Use the specified step, otherwise attempt to auto-detect it.
|
|
if [[ "$1" ]]; then
|
|
step=$1
|
|
elif [[ "$(git branch | grep " $branch-squash\$")" ]]; then
|
|
# STEP 3 should never auto-execute twice.
|
|
if [[ "$(git branch --contains "$(git rev-parse $branch-squash)" | grep " $local_ref\$")" ]]; then
|
|
echo "Error merging branch \"$branch-squash\" into \"$local_ref\" branch! (already done)"
|
|
echo
|
|
echo "Redo the last step with: $script 3"
|
|
exit 4
|
|
fi
|
|
step=3
|
|
elif [[ "$(git branch | grep " $branch\$")" ]]; then
|
|
step=2
|
|
else
|
|
step=1
|
|
fi
|
|
|
|
# Let's do some stuff.
|
|
if [[ $step == 1 ]]; then
|
|
header 1
|
|
|
|
# Clean up any prior work on this PR.
|
|
del_branch "$branch"
|
|
del_branch "$branch-squash"
|
|
|
|
# Fetch remote, create a branch, etc.
|
|
if [[ "$remote_url" == "$local_url" ]]; then
|
|
git fetch origin "$remote_ref"
|
|
else
|
|
git fetch "$remote_url" "$remote_ref"
|
|
fi
|
|
git checkout -b "$branch" FETCH_HEAD
|
|
|
|
# Save ref to last PR author commit for later use
|
|
git tag --force "_${branch}_author_head" FETCH_HEAD
|
|
|
|
# Rebase!
|
|
git rebase "$local_ref"
|
|
if [[ $? != 0 ]]; then
|
|
echo "Error while attempting rebase!"
|
|
exit 3
|
|
fi
|
|
|
|
echo
|
|
echo "Changed files in HEAD~$num_commits:"
|
|
git --no-pager diff --name-only HEAD~"$num_commits"
|
|
|
|
echo
|
|
echo "-=[ Next Steps ]=============================================================-"
|
|
echo "$(help_step2 1) with: $script"
|
|
echo " Or redo the current step with: $script 1"
|
|
|
|
elif [[ $step == 2 ]]; then
|
|
header 2
|
|
|
|
# Clean up any prior squashes for this PR.
|
|
del_branch "$branch-squash"
|
|
|
|
# Create branch and squash merge all commits.
|
|
git checkout -b "$branch-squash" "$local_ref"
|
|
git merge --squash "$branch"
|
|
|
|
# Append useful information to commit message.
|
|
squash_msg_file="$git_dir/SQUASH_MSG"
|
|
echo -e "\nCloses gh-$pr." >> "$squash_msg_file"
|
|
|
|
# Retrieve author name and email from stored commit, and commit.
|
|
author="$(git log "_${branch}_author_head" -n1 --format="%an <%ae>")"
|
|
git commit --author="$author"
|
|
|
|
echo
|
|
echo "-=[ Next Steps ]=============================================================-"
|
|
echo "$(help_step3 1) with: $script"
|
|
echo " Or redo the current step with: $script 2"
|
|
|
|
elif [[ $step == 3 ]]; then
|
|
header 3
|
|
|
|
# Actually merge squashed commits into branch.
|
|
git checkout "$local_ref"
|
|
git merge "$branch-squash"
|
|
|
|
echo
|
|
echo "-=[ Next Steps ]=============================================================-"
|
|
echo "$(help_step4 1) with: $script 4"
|
|
echo " Or redo the current step with: $script 3"
|
|
|
|
elif [[ $step == 4 ]]; then
|
|
header 4
|
|
|
|
del_branch "$branch"
|
|
del_branch "$branch-squash"
|
|
git tag -d "_${branch}_author_head" 2>/dev/null
|
|
|
|
echo
|
|
echo "All done."
|
|
fi
|