diff --git a/README.md b/README.md index e52fd55..0a3efc4 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ jobs: uses: repo-sync/pull-request@v2 with: destination_branch: "main" - github_token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} ``` This will automatically create a pull request from `feature-1` to `main`. @@ -57,8 +57,14 @@ jobs: with: source_branch: "" # If blank, default: triggered branch destination_branch: "master" # If blank, default: master + repository: ${{ github.repository }} # repository with owner, can be another repository than currently checked out when using a PAT during checkout that has access to the other repo. pr_title: "Pulling ${{ github.ref }} into master" # Title of pull request - pr_body: ":crown: *An automated PR*" # Full markdown support, requires pr_title to be set + pr_body: | # Full markdown support, requires pr_title to be set + :crown: *An automated PR* + :arrow_heading_up: Closes: #issueid + + **Describe the Change** + "Put a description here" pr_template: ".github/PULL_REQUEST_TEMPLATE.md" # Path to pull request template, requires pr_title to be set, excludes pr_body pr_reviewer: "wei,worker" # Comma-separated list (no spaces) pr_assignee: "wei,worker" # Comma-separated list (no spaces) @@ -66,7 +72,8 @@ jobs: pr_milestone: "Milestone 1" # Milestone name pr_draft: true # Creates pull request as draft pr_allow_empty: true # Creates pull request even if there are no changes - github_token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_PERSONAL_ACCESS_TOKEN }} + debug: false # bash set -x verbose debugging output ``` ### Outputs @@ -89,7 +96,7 @@ jobs: uses: repo-sync/pull-request@v2 with: destination_branch: "main" - github_token: ${{ secrets.GITHUB_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} - name: output-url run: echo ${{steps.open-pr.outputs.pr_url}} - name: output-number @@ -99,6 +106,43 @@ jobs: ``` +### Example: Pull-Request on another repo +This example demonstrates how to create a pull-request in another repo. There are a few caveats such as the requirement of checking out the code with a [Github Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token). There are some pretty advanced use cases for this such as building an app on every push to develop, updating the docker image tag and repo url in the config repo, and creating a pull-request to the config repo. After the pr in the config repo is merged a deployment is kicked off. +```yaml +on: + push: + branches: + - 'develop' +jobs: + draft-new-pr: + name: "Create PR in my-apps-config-repo" + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + token: ${{ secrets.ACTIONS_WORKFLOW_PAT }} + - name: Create Alt Repo Pull-Request + uses: ./.github/actions/actions-create-pull-request + with: + source_branch: 'develop' + destination_branch: 'master' + repository: '${{ github.repository_owner }}/my-apps-config-repo' + pr_title: "Pulling 'release/${{ steps.ver.outputs.version-after }}' into master" + pr_body: | + :crown: *An automated PR* + + This PR was created in response to a manual trigger of the release workflow here: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}. + ..And creates a pr in this other repo here: https://github.com/${{ github.repository_owner }}/my-apps-config-repo. + + "Put a description here" + 'Quotes are being handled' + pr_label: "some-label,another-label" + pr_allow_empty: false + token: ${{ secrets.ACTIONS_WORKFLOW_PAT }} + debug: false +``` + ## Contributors ✨ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): diff --git a/action.yml b/action.yml index 5372cbd..86fcf62 100644 --- a/action.yml +++ b/action.yml @@ -12,6 +12,9 @@ inputs: description: Branch name to sync to in this repo, default is master required: false default: master + repository: + required: false + description: Repository on which to perform the pull-request. Defaults to current repo. Can different from the current one. pr_title: description: Pull request title required: false @@ -39,9 +42,11 @@ inputs: pr_allow_empty: description: Create PR even if no changes required: false - github_token: - description: GitHub token secret + token: + description: GitHub token secret or Personal Access Token required: true + debug: + description: Bash set -x debugging mode outputs: pr_url: description: 'Pull request URL' @@ -52,5 +57,3 @@ outputs: runs: using: 'docker' image: 'Dockerfile' - env: - GITHUB_TOKEN: ${{ inputs.github_token }} diff --git a/entrypoint.sh b/entrypoint.sh index ce2dd73..c8c2212 100755 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -3,94 +3,229 @@ set -e set -o pipefail -if [[ -z "$GITHUB_TOKEN" ]]; then - echo "Set the GITHUB_TOKEN environment variable." +##################################### +# establish a few helper functions +reset_color="\\e[0m" +color_red="\\e[31m" +color_green="\\e[32m" +color_yellow="\\e[33m" +color_blue="\\e[36m" +color_gray="\\e[37m" +function echo_blue { echo -e "${color_blue}$*${reset_color}"; } +function echo_green { echo -e "${color_green}$*${reset_color}"; } +function echo_red { echo -e "${color_red}$*${reset_color}"; } +function echo_yellow { echo -e "${color_yellow}$*${reset_color}"; } +function echo_gray { echo -e "${color_gray}$*${reset_color}"; } +function echo_grey { echo -e "${color_gray}$*${reset_color}"; } +function echo_info { echo -e "${color_blue}info: $*${reset_color}"; } +function echo_error { echo -e "${color_red}error: $*${reset_color}"; } +function echo_warning { echo -e "${color_yellow}✔ $*${reset_color}"; } +function echo_success { echo -e "${color_green}✔ $*${reset_color}"; } +function echo_fail { echo -e "${color_red}✖ $*${reset_color}"; } +function enable_debug { + if [[ "${INPUT_DEBUG}" == "true" ]]; then + echo_info "Enabling debug mode." + set -x + fi +} +function disable_debug { + if [[ "${INPUT_DEBUG}" == "true" ]]; then + set +x + fi +} +# no more helper functions. +################################### + + +echo "::group::Check inputs" +if [[ -z "$INPUT_TOKEN" ]]; then + echo_yellow "INPUT_TOKEN unsupplied. Defaulting to GITHUB_TOKEN." + INPUT_TOKEN="$GITHUB_TOKEN" +fi + +if [[ -z "$INPUT_TOKEN" ]]; then + echo_fail "GITHUB_TOKEN is missing. This is usually provided as an environment variable by Github Actions. If INPUT_TOKEN or GITHUB_TOKEN are unsupplied, unable to continue." exit 1 fi +if [[ ! -z "$INPUT_TOKEN" ]]; then + echo "::add-mask::$INPUT_TOKEN" +fi + +# enable debug after token handling +enable_debug + +if [[ -z "$GITHUB_ACTOR" ]]; then + echo_yellow "GITHUB_ACTOR is missing. This is usually provided as an environment variable by Github Actions." + echo_yellow "Setting GITHUB_ACTOR to 'github-actions'" + GITHUB_ACTOR='github-actions' +fi + +if [[ -z "$INPUT_REPOSITORY" ]]; then + echo_yellow "INPUT_REPOSITORY is empty to not set. Defaulting INPUT_REPOSITORY to GITHUB_REPOSITORY" + if [[ -z "$GITHUB_REPOSITORY" ]]; then + echo_fail "GITHUB_REPOSITORY is missing. This is usually provided as an environment variable by Github Actions." + echo_fail "Expected Format is generally /. i.e. aws-official/s3-sync" + exit 1 + fi + INPUT_REPOSITORY="$GITHUB_REPOSITORY" +fi + if [[ ! -z "$INPUT_SOURCE_BRANCH" ]]; then SOURCE_BRANCH="$INPUT_SOURCE_BRANCH" elif [[ ! -z "$GITHUB_REF" ]]; then SOURCE_BRANCH=${GITHUB_REF/refs\/heads\//} # Remove branch prefix else - echo "Set the INPUT_SOURCE_BRANCH environment variable or trigger from a branch." + echo_fail "Set the INPUT_SOURCE_BRANCH environment variable or trigger from a branch." exit 1 fi DESTINATION_BRANCH="${INPUT_DESTINATION_BRANCH:-"master"}" +echo "::endgroup::" + +echo "::group::Configure git" # Github actions no longer auto set the username and GITHUB_TOKEN -git remote set-url origin "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY" +GIT_ORIGIN_URL="$(git remote get-url origin)" +git remote set-url origin "https://$GITHUB_ACTOR:$INPUT_TOKEN@github.com/$INPUT_REPOSITORY" # Pull all branches references down locally so subsequent commands can see them -git fetch origin '+refs/heads/*:refs/heads/*' --update-head-ok +git fetch origin '+refs/heads/*:refs/heads/*' --update-head-ok --prune +git fetch --prune # Print out all branches git --no-pager branch -a -vv +echo "::endgroup::" + +echo "::group::Ensure pull-request contains differences" if [ "$(git rev-parse --revs-only "$SOURCE_BRANCH")" = "$(git rev-parse --revs-only "$DESTINATION_BRANCH")" ]; then - echo "Source and destination branches are the same." + echo_blue "Source and destination branches are the same." exit 0 fi # Do not proceed if there are no file differences, this avoids PRs with just a merge commit and no content LINES_CHANGED=$(git diff --name-only "$DESTINATION_BRANCH" "$SOURCE_BRANCH" -- | wc -l | awk '{print $1}') if [[ "$LINES_CHANGED" = "0" ]] && [[ ! "$INPUT_PR_ALLOW_EMPTY" == "true" ]]; then - echo "No file changes detected between source and destination branches." + echo_blue "No file changes detected between source and destination branches." exit 0 fi +echo_yellow "Source and Destination Branches Contain Differences." +echo_yellow "Number of lines changed: $LINES_CHANGED" +echo "::endgroup::" +echo "::group::Establish hub cli parameters" # Workaround for `hub` auth error https://github.com/github/hub/issues/2149#issuecomment-513214342 export GITHUB_USER="$GITHUB_ACTOR" +# set GITHUB_TOKEN envar so hub cli commands can authenticate. +export GITHUB_TOKEN="$INPUT_TOKEN" -PR_ARG="$INPUT_PR_TITLE" -if [[ ! -z "$PR_ARG" ]]; then - PR_ARG="-m \"$PR_ARG\"" +if [[ ! -z "$DESTINATION_BRANCH" ]]; then + FLAGS=(-b "$DESTINATION_BRANCH") +fi + +if [[ ! -z "$SOURCE_BRANCH" ]]; then + FLAGS+=(-h "$SOURCE_BRANCH") +fi +FLAGS+=(--no-edit) + +if [[ ! -z "$INPUT_PR_TITLE" ]]; then + FLAGS+=(-m "$INPUT_PR_TITLE") if [[ ! -z "$INPUT_PR_TEMPLATE" ]]; then sed -i 's/`/\\`/g; s/\$/\\\$/g' "$INPUT_PR_TEMPLATE" - PR_ARG="$PR_ARG -m \"$(echo -e "$(cat "$INPUT_PR_TEMPLATE")")\"" + FLAGS+=(-m "$(echo -e "$(cat "$INPUT_PR_TEMPLATE")")") elif [[ ! -z "$INPUT_PR_BODY" ]]; then - PR_ARG="$PR_ARG -m \"$INPUT_PR_BODY\"" + FLAGS+=(-m "$INPUT_PR_BODY") fi fi if [[ ! -z "$INPUT_PR_REVIEWER" ]]; then - PR_ARG="$PR_ARG -r \"$INPUT_PR_REVIEWER\"" + FLAGS+=(-r "$INPUT_PR_REVIEWER") fi if [[ ! -z "$INPUT_PR_ASSIGNEE" ]]; then - PR_ARG="$PR_ARG -a \"$INPUT_PR_ASSIGNEE\"" + FLAGS+=(-a "$INPUT_PR_ASSIGNEE") fi if [[ ! -z "$INPUT_PR_LABEL" ]]; then - PR_ARG="$PR_ARG -l \"$INPUT_PR_LABEL\"" + FLAGS+=(-l "$INPUT_PR_LABEL") fi if [[ ! -z "$INPUT_PR_MILESTONE" ]]; then - PR_ARG="$PR_ARG -M \"$INPUT_PR_MILESTONE\"" + FLAGS+=(-M "$INPUT_PR_MILESTONE") fi if [[ "$INPUT_PR_DRAFT" == "true" ]]; then - PR_ARG="$PR_ARG -d" + FLAGS+=(-d) fi -COMMAND="hub pull-request \ - -b $DESTINATION_BRANCH \ - -h $SOURCE_BRANCH \ - --no-edit \ - $PR_ARG \ - || true" +echo "${FLAGS[@]}" +echo "::endgroup::" + +echo "::group::Create Pull-Request $SOURCE_BRANCH -> $DESTINATION_BRANCH" +RAND_UUID=$(cat /proc/sys/kernel/random/uuid) +COMMAND="hub pull-request "${FLAGS[@]}" 2>\"./create-pull-request.$RAND_UUID.stderr\" || true" echo "$COMMAND" -PR_URL=$(sh -c "$COMMAND") -if [[ "$?" != "0" ]]; then - exit 1 +PR_URL=$( \ + hub pull-request "${FLAGS[@]}" \ + 2>"./create-pull-request.$RAND_UUID.stderr" || true \ +) +STD_ERROR="$( cat "./create-pull-request.$RAND_UUID.stderr" || true )" +rm -rf "./create-pull-request.$RAND_UUID.stderr" +echo "::endgroup::" + + +echo "::group::Revert Git Config Changes" +# set origin back as was previously configured. +git remote set-url origin "$GIT_ORIGIN_URL" +git fetch origin '+refs/heads/*:refs/heads/*' --update-head-ok +git fetch --prune +echo "::endgroup::" + + +# determine success / failure +# since various things can go wrong such as bad user input or non-existant branches, there is a need to handle outputs to determine if the pr was successfully created or not. +if [[ -z "$PR_URL" ]]; then + if [[ ! -z "$(echo "$STD_ERROR" | grep -oie "A pull request already exists for")" ]]; then + echo_yellow "Pull-Request Already Exists. This is the stderr output:" + echo_yellow "$STD_ERROR" + else + echo_fail "Pull-Request Command Failed. This is the stderr output:" + echo_red "$STD_ERROR" + exit 1 + fi +else + echo_success "Pull-Request was successfully created." + echo_success "pr_url: ${PR_URL}" +fi + +# attempt to obtain the pull-request details - pr already exists. +if [[ -z "$PR_URL" ]]; then + echo "::group::Retrieving Pull-Request Details" + RAND_UUID=$(cat /proc/sys/kernel/random/uuid) + PR_URL=$( \ + hub pr list -h $SOURCE_BRANCH -b $DESTINATION_BRANCH -f %U \ + 2>"./get-pull-request.$RAND_UUID.stderr" || true \ + ) + STD_ERROR="$( cat "./get-pull-request.$RAND_UUID.stderr" || true )" + rm -rf "./get-pull-request.$RAND_UUID.stderr" + + if [[ -z "$PR_URL" ]]; then + echo_fail "Pull-Request Already Exists, but was unable to retrieve url. This is the stderr output:" + echo_red "$STD_ERROR" + else + echo_success "Pull-Request details successfully obtained." + echo_success "pr_url: ${PR_URL}" + fi + echo "::endgroup::" fi -echo ${PR_URL} + +echo "::group::Set Outputs" echo "::set-output name=pr_url::${PR_URL}" echo "::set-output name=pr_number::${PR_URL##*/}" if [[ "$LINES_CHANGED" = "0" ]]; then @@ -98,3 +233,9 @@ if [[ "$LINES_CHANGED" = "0" ]]; then else echo "::set-output name=has_changed_files::true" fi +echo_yellow "Outputs Set." +echo "::endgroup::" + + +# disable debug mode +disable_debug