1+ #! /bin/bash
2+ set -uo pipefail # prevent accessing unset env vars, prevent masking pipeline errors to the next command
3+
4+ # docs
5+ # title :create_pr_for_staged_changes.sh
6+ # description :This script will create a PR for staged changes, detect and close duplicate PRs. All PRs will be omitted from Release Notes and Changelogs
7+ # author :@heitorlessa
8+ # date :May 8th 2023
9+ # version :0.1
10+ # usage :bash create_pr_for_staged_changes.sh {git_staged_files_or_directories_separated_by_space}
11+ # notes :Meant to use in GitHub Actions only. Temporary branch will be named $TEMP_BRANCH_PREFIX-$GITHUB_RUN_ID
12+ # os_version :Ubuntu 22.04.2 LTS
13+ # required_env_vars :PR_TITLE, TEMP_BRANCH_PREFIX, GH_TOKEN
14+ # ==============================================================================
15+
16+ # Sets GitHub Action with error message to ease troubleshooting
17+ function error() {
18+ echo " ::error file=${FILENAME} ::$1 "
19+ exit 1
20+ }
21+
22+ function debug() {
23+ TIMESTAMP=$( date -u " +%FT%TZ" ) # 2023-05-10T07:53:59Z
24+ echo " " ${TIMESTAMP} " - $1 "
25+ }
26+
27+ function notice() {
28+ echo " ::notice file=${FILENAME} ::$1 "
29+ }
30+
31+ function start_span() {
32+ echo " ::group::$1 "
33+ }
34+
35+ function end_span() {
36+ echo " ::endgroup::"
37+ }
38+
39+ function has_required_config() {
40+ start_span " Validating required config"
41+ test -z " ${TEMP_BRANCH_PREFIX} " && error " TEMP_BRANCH_PREFIX env must be set to create a PR"
42+ test -z " ${PR_TITLE} " && error " PR_TITLE env must be set"
43+ test -z " ${GH_TOKEN} " && error " GH_TOKEN env must be set for GitHub CLI"
44+
45+ # Default GitHub Actions Env Vars: https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables
46+ debug " Are we running in GitHub Action environment?"
47+ test -z " ${GITHUB_RUN_ID} " && error " GITHUB_RUN_ID env must be set to trace Workflow Run ID back to PR"
48+ test -z " ${GITHUB_SERVER_URL} " && error " GITHUB_SERVER_URL env must be set to trace Workflow Run ID back to PR"
49+ test -z " ${GITHUB_REPOSITORY} " && error " GITHUB_REPOSITORY env must be set to trace Workflow Run ID back to PR"
50+
51+ debug " Config validated successfully!"
52+ set_environment_variables
53+ end_span
54+ }
55+
56+ function set_environment_variables() {
57+ start_span " Setting environment variables"
58+ export readonly WORKFLOW_URL=" ${GITHUB_SERVER_URL} " /" ${GITHUB_REPOSITORY} " /actions/runs/" ${GITHUB_RUN_ID} " # e.g., heitorlessa/aws-lambda-powertools-test/actions/runs/4913570678
59+ export readonly TEMP_BRANCH=" ${TEMP_BRANCH_PREFIX} " -" ${GITHUB_RUN_ID} " # e.g., ci-changelog-4894658712
60+ export readonly BASE_BRANCH=" ${BASE_BRANCH:- main} " # e.g., main, defaults to develop if missing
61+ export readonly PR_BODY=" This is an automated PR created from the following workflow"
62+ export readonly FILENAME=" .github/scripts/$( basename " $0 " ) "
63+ export readonly NO_DUPLICATES_MESSAGE=" No duplicated PRs found"
64+ export readonly SKIP_LABEL=" skip-changelog"
65+
66+ end_span
67+ }
68+
69+ function has_anything_changed() {
70+ start_span " Validating git staged files"
71+ HAS_ANY_SOURCE_CODE_CHANGED=" $( git status --porcelain) "
72+
73+ test -z " ${HAS_ANY_SOURCE_CODE_CHANGED} " && debug " Nothing to update; exitting early" && exit 0
74+ end_span
75+ }
76+
77+ function create_temporary_branch_with_changes() {
78+ start_span " Creating temporary branch: " ${TEMP_BRANCH} " "
79+ git checkout -b " ${TEMP_BRANCH} "
80+
81+ debug " Committing staged files: $* "
82+ echo " $@ " | xargs -n1 git add || error " Failed to add staged changes: " $@ " "
83+ git commit -m " ${PR_TITLE} "
84+
85+ git push origin " ${TEMP_BRANCH} " || error " Failed to create new temporary branch"
86+ end_span
87+ }
88+
89+ function create_pr() {
90+ start_span " Creating PR against ${TEMP_BRANCH} branch"
91+ # TODO: create label
92+ NEW_PR_URL=$( gh pr create --title " ${PR_TITLE} " --body " ${PR_BODY} : ${WORKFLOW_URL} " --base " ${BASE_BRANCH} " --label " ${SKIP_LABEL} " || error " Failed to create PR" ) # e.g, https://github.com/aws-powertools/powertools-lambda-python/pull/13
93+
94+ # greedy remove any string until the last URL path, including the last '/'. https://opensource.com/article/17/6/bash-parameter-expansion
95+ debug " Extracing PR Number from PR URL: " ${NEW_PR_URL} " "
96+ NEW_PR_ID=" ${NEW_PR_URL##*/ } " # 13
97+ export NEW_PR_URL
98+ export NEW_PR_ID
99+ end_span
100+ }
101+
102+ function close_duplicate_prs() {
103+ start_span " Searching for duplicate PRs"
104+ DUPLICATE_PRS=$( gh pr list --search " ${PR_TITLE} " --json number --jq " .[] | select(.number != ${NEW_PR_ID} ) | .number" ) # e.g, 13\n14
105+
106+ if [ -z " ${DUPLICATE_PRS} " ]; then
107+ debug " No duplicate PRs found"
108+ DUPLICATE_PRS=" ${NO_DUPLICATES_MESSAGE} "
109+ else
110+ debug " Closing duplicated PRs: " ${DUPLICATE_PRS} " "
111+ echo " ${DUPLICATE_PRS} " | xargs -L1 gh pr close --delete-branch --comment " Superseded by #${NEW_PR_ID} "
112+ fi
113+
114+ export readonly DUPLICATE_PRS
115+ end_span
116+ }
117+
118+ function report_job_output() {
119+ start_span " Updating job outputs"
120+ echo pull_request_id=" ${NEW_PR_ID} " >> " $GITHUB_OUTPUT "
121+ echo temp_branch=" ${TEMP_BRANCH} " >> " $GITHUB_OUTPUT "
122+ end_span
123+ }
124+
125+ function report_summary() {
126+ start_span " Creating job summary"
127+ echo " ### Pull request created successfully :rocket: ${NEW_PR_URL} <br/><br/> Closed duplicated PRs: ${DUPLICATE_PRS} " >> " $GITHUB_STEP_SUMMARY "
128+
129+ notice " PR_URL is: ${NEW_PR_URL} "
130+ notice " PR_BRANCH is: ${TEMP_BRANCH} "
131+ notice " PR_DUPLICATES are: ${DUPLICATE_PRS} "
132+ end_span
133+ }
134+
135+ function main() {
136+ # Sanity check
137+ has_anything_changed
138+ has_required_config
139+
140+ create_temporary_branch_with_changes " $@ "
141+ create_pr
142+ close_duplicate_prs
143+
144+ report_job_output
145+ report_summary
146+ }
147+
148+ main " $@ "
0 commit comments