Skip to content

Instantly share code, notes, and snippets.

@tangxiaocheng
Created April 8, 2024 06:07
Show Gist options
  • Save tangxiaocheng/997bd9f9dc0222a3dc59c99d5637c476 to your computer and use it in GitHub Desktop.
Save tangxiaocheng/997bd9f9dc0222a3dc59c99d5637c476 to your computer and use it in GitHub Desktop.
quick create or merge pr
#!/bin/bash
##############################################################################
##
## File: quick_pr.sh
## Author: Randy
## Date: 2021-09-30
## Description: Create and merge a PR in a agile way. Especially for testing ci/cd pipeline.
## Usage example: ./quick_pr.sh --runner 'github' --repo_path '/path/to/repo' --base_branch 'main'
##
## Required input parameters:
## runner: not uesd yet (for testing only)
##
## Optional input parameters:
## base_branch: pr base branch
## new_base_branch: if set, checkout base_branch and create a new branch with this name for PR
## commit_msg: support tags: todo, diff (e.g. --commit_msg 'diff: fix bug')
## jira: jira ticket number, if set, the branch name will be 'randy/${jira}/${current_time}'
##
## Optional boolean parameters:
## do_not_merge: if set or set to true, the PR will not be merged. Default is false.
## do_not_open: if set or set to true, it will not open the pr link. Default is false.
##
## Note:
## 1, please install gh cli before running this script
## 2, please set up the ssh key for the remote repository before running this script
## 3, the reason I can use gh cli to create and merge PR is because I have set up GH_TOKEN in my environment, such as in ~/.bashrc
##############################################################################
# pre-check for essential parameters
while [ $# -gt 0 ]; do
if [[ $1 == *"--"* ]]; then
v="${1/--/}"
if [[ -n "$2" ]] && [[ ! "$2" =~ ^-- ]]; then
declare -r "$v"="$2"
else
declare -r "$v"="true"
fi
fi
shift
done
required_parameters=("runner")
for parameter in "${required_parameters[@]}"; do
if [[ -n "${parameter}" ]]; then
parameter_value=$(eval echo \$"$parameter")
if [ -z "$parameter_value" ]; then
echo "please provides required input parameter with format: --$parameter 'value'"
exit 1
fi
fi
done
echo ""
echo "--------------------------------------------------------------------------------------"
echo "Required input parameters:"
for parameter in "${required_parameters[@]}"; do
if [[ -n "${parameter}" ]]; then
parameter_value=$(eval echo \$"$parameter")
echo "${parameter} = ${parameter_value}"
fi
done
echo "--------------------------------------------------------------------------------------"
echo ""
optional_parameters=( "base_branch" "new_base_branch" "commit_msg" "jira" )
echo ""
echo "--------------------------------------------------------------------------------------"
echo "Optional parameters:"
for parameter in "${optional_parameters[@]}"; do
if [[ -n "${parameter}" ]]; then
parameter_value=$(eval echo \$"$parameter")
if [[ -z "${parameter_value}" ]]; then
echo "${parameter} = UNSET"
else
echo "${parameter} = ${parameter_value}"
fi
fi
done
echo "--------------------------------------------------------------------------------------"
echo ""
optional_boolean_parameters=( "do_not_merge" "do_not_open")
echo ""
echo "--------------------------------------------------------------------------------------"
echo "Optional boolean parameters:"
for parameter in "${optional_boolean_parameters[@]}"; do
if [[ -n "${parameter}" ]]; then
parameter_value=$(eval echo \$"$parameter")
if [[ -z "${parameter_value}" ]]; then
echo "${parameter} = true"
else
echo "${parameter} = ${parameter_value}"
fi
fi
done
echo "--------------------------------------------------------------------------------------"
echo ""
# shellcheck disable=SC2154
if [ ! -d "$repo_path" ]; then
echo "Invalid directory repo_path: $repo_path"
exit 1
else
cd "$repo_path" || exit
fi
# check if the repo_path is a git repository
if [ ! -d ".git" ]; then
echo "This path repo_path: $repo_path is not a git repository "
exit 1
else
echo "This path repo_path: $repo_path is a git repository"
fi
current_time=$(date +"%m_%d-%H.%M.%S")
if [[ -n "${jira}" ]]; then
echo "string is not empty"
pr_source_branch="randy/${jira}/${current_time}"
else
pr_source_branch="randy/${current_time}"
fi
# shellcheck disable=SC2154
final_commit_msg="${commit_msg}"
if [[ -z "${final_commit_msg}" ]]; then
final_commit_msg="Empty input commit message on branch: ${pr_source_branch}"
fi
commit_msg_tags=( "todo" "diff" "" )
tag_count=0
for tag in "${commit_msg_tags[@]}"; do
if [[ -n "${tag}" ]]; then
tag_with_semicolon="${tag}:"
if grep -qiE "(^| )${tag_with_semicolon}" <<<"$final_commit_msg"; then
echo "found tag: '${tag_with_semicolon}' in commit message"
final_commit_msg=$(sed "s/${tag_with_semicolon}//g" <<<"$final_commit_msg")
declare -r "has_$tag"="true"
((tag_count++))
else
echo "not found tag: '${tag_with_semicolon}' in commit message"
fi
fi
done
if [[ $tag_count -gt 1 ]]; then
echo "Invalid amount ($tag_count) of commit mesaage tags detected for commit message: ${commit_msg}"
exit 1
fi
for tag in "${commit_msg_tags[@]}"; do
if [[ -n "${tag}" ]]; then
has_tag=$(eval echo \$"has_$tag")
echo "has_$tag: $has_tag"
fi
done
local_branch=$(git rev-parse --abbrev-ref HEAD)
if [[ -n "${base_branch}" ]]; then
if [ "$local_branch" != "${base_branch}" ]; then
git checkout "${base_branch}"
echo "Current branch is not the same as base_branch: ${base_branch}"
else
echo "Current branch is the same as base_branch: ${base_branch}"
fi
else
base_branch="$local_branch"
fi
git stash
git pull --rebase
final_base_branch="${base_branch}"
if [[ -n "${new_base_branch}" ]]; then
if [[ "${new_base_branch}" == "${final_base_branch}" ]]; then
echo "new_base_branch is the same as base_branch: ${base_branch}, do nothing"
else
echo "new_base_branch is different from base_branch: ${base_branch}"
git checkout -b "${new_base_branch}"
git push --set-upstream origin "${new_base_branch}"
final_base_branch="${new_base_branch}"
fi
fi
if git stash list | grep -q "stash"; then
if ! git stash apply; then
echo "git stash apply encountered an error. "
exit 1
else
echo "git stash apply ran successfully"
fi
fi
git checkout -b "${pr_source_branch}"
# Check for unstaged changes (git diff) and staged but not committed changes (git status)
if git diff --exit-code && git diff --cached --exit-code; then
echo "no changes detected, let's make some changes for triggering ci via pr"
ci_trigger_file="ci_trigger_file.txt"
touch "$ci_trigger_file"
echo "$pr_source_branch" >> "$ci_trigger_file"
git add "$ci_trigger_file"
git commit -m "make code change for trigger ci with: $final_commit_msg"
else
git add .
git commit -a -m "${final_commit_msg}"
fi
output=$(git push --set-upstream origin "${pr_source_branch}" 2>&1)
exit_status=$?
if [ $exit_status -eq 0 ]; then
url=$(echo "$output" | grep -o 'https://github.com/[a-zA-Z0-9./_-]*')
if [ -n "$url" ]; then
echo "Branch url: $url"
# open "$url"
else
echo "Push successful, but no pull request URL found in the output."
fi
else
echo "Error pushing to GitHub:"
echo "$output"
exit 1
fi
pr_link=$(gh pr create --base "$final_base_branch" --title "$final_commit_msg" --body "Merge PR by script: $final_commit_msg")
if [[ "${do_not_open}" != "true" ]]; then
open "$pr_link"
fi
sleep 3
echo "pr: ${pr_link}"
if [[ "${do_not_merge}" != "true" ]]; then
gh pr merge "${pr_link}" -r -d --admin
git checkout "$final_base_branch"
git pull --rebase
else
echo "do_not_merge is set to true, so the PR will not be merged"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment