Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow for pruning of older PRs #438

Merged
merged 8 commits into from
Jan 25, 2024
60 changes: 39 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
source_repo_path: <owner/repo>
upstream_branch: <target_branch> # defaults to main
pr_labels: <label1>,<label2>[,...] # optional, no default
pr_labels: <label1>,<label2>[,...] # defaults to template_sync
```

You will receive a pull request within your repository if there are some changes available in the template.
Expand Down Expand Up @@ -139,7 +139,7 @@ jobs:
github_token: ${{ steps.generate_token.outputs.token }}
source_repo_path: <owner/repo>
upstream_branch: <target_branch> # defaults to main
pr_labels: <label1>,<label2>[,...] # optional, no default
pr_labels: <label1>,<label2>[,...] # defaults to template_sync
```

#### 2. Using SSH
Expand Down Expand Up @@ -175,7 +175,7 @@ jobs:
github_token: ${{ secrets.GITHUB_TOKEN }}
source_repo_path: ${{ secrets.SOURCE_REPO_PATH }} # <owner/repo>, should be within secrets
upstream_branch: ${{ secrets.TARGET_BRANCH }} #<target_branch> # defaults to main
pr_labels: <label1>,<label2>[,...] # optional, no default
pr_labels: <label1>,<label2>[,...] # defaults to template_sync
source_repo_ssh_private_key: ${{ secrets.SOURCE_REPO_SSH_PRIVATE_KEY }} # contains the private ssh key of the private repository
```

Expand Down Expand Up @@ -232,24 +232,25 @@ jobs:

### Configuration parameters

| Variable | Description | Required | `[Default]` |
|----|----|----|----|
| github_token | Token for the repo. Can be passed in using `$\{{ secrets.GITHUB_TOKEN }}` | `true` | |
| source_repo_path | Repository path of the template | `true` | |
| upstream_branch | The target branch | `false` | `<The_remote_default>` |
| source_repo_ssh_private_key | `[optional]` private ssh key for the source repository. [see](#private-template-repository)| `false` | |
| pr_branch_name_prefix | `[optional]` the prefix of branches created by this action | `false` | `chore/template_sync` |
| pr_title | `[optional]` the title of PRs opened by this action. Must be already created. | `false` | `upstream merge template repository` |
| pr_labels | `[optional]` comma separated list. [pull request labels][pr-labels]. Must be already created. | `false` | |
| pr_reviewers | `[optional]` comma separated list of pull request reviewers. | `false` | |
| pr_commit_msg | `[optional]` commit message in the created pull request | `false` | `chore(template): merge template changes :up:` |
| hostname | `[optional]` the hostname of the repository | `false` | `github.com` |
| is_dry_run | `[optional]` set to `true` if you do not want to push the changes and not want to create a PR | `false` | |
| is_allow_hooks | `[optional]` set to `true` if you want to enable lifecycle hooks. Use this with caution! | `false` | `false` |
| is_not_source_github | `[optional]` set to `true` if the source git provider is not GitHub | `false` | `false` |
| git_user_name | `[optional]` set the committer git user.name | `false` | `${GITHUB_ACTOR}` |
| git_user_email | `[optional]` set the committer git user.email | `false` | `github-action@actions-template-sync.noreply.${SOURCE_REPO_HOSTNAME}` |
| git_remote_pull_params |`[optional]` set remote pull parameters | `false` | `--allow-unrelated-histories --squash --strategy=recursive -X theirs` |
| Variable | Description | Required | `[Default]` |
|-----------------------------|---------------------------------------------------------------------------------------------------------------|----------|-----------------------------------------------------------------------|
| github_token | Token for the repo. Can be passed in using `$\{{ secrets.GITHUB_TOKEN }}` | `true` | |
| source_repo_path | Repository path of the template | `true` | |
| upstream_branch | The target branch | `false` | `<The_remote_default>` |
| source_repo_ssh_private_key | `[optional]` private ssh key for the source repository. [see](#private-template-repository) | `false` | |
| pr_branch_name_prefix | `[optional]` the prefix of branches created by this action | `false` | `chore/template_sync` |
| pr_title | `[optional]` the title of PRs opened by this action. Must be already created. | `false` | `upstream merge template repository` |
| pr_labels | `[optional]` comma separated list. [pull request labels][pr-labels]. | `false` | `sync_template` |
| pr_reviewers | `[optional]` comma separated list of pull request reviewers. | `false` | |
| pr_commit_msg | `[optional]` commit message in the created pull request | `false` | `chore(template): merge template changes :up:` |
| hostname | `[optional]` the hostname of the repository | `false` | `github.com` |
| is_dry_run | `[optional]` set to `true` if you do not want to push the changes and not want to create a PR | `false` | |
| is_allow_hooks | `[optional]` set to `true` if you want to enable lifecycle hooks. Use this with caution! | `false` | `false` |
| is_pr_cleanup | `[optional]` set to `true` if you want to cleanup older PRs targeting the same branch. Use this with caution! | `false` | `false` |
| is_not_source_github | `[optional]` set to `true` if the source git provider is not GitHub | `false` | `false` |
| git_user_name | `[optional]` set the committer git user.name | `false` | `${GITHUB_ACTOR}` |
| git_user_email | `[optional]` set the committer git user.email | `false` | `github-action@actions-template-sync.noreply.${SOURCE_REPO_HOSTNAME}` |
| git_remote_pull_params | `[optional]` set remote pull parameters | `false` | `--allow-unrelated-histories --squash --strategy=recursive -X theirs` |

### Docker

Expand Down Expand Up @@ -304,6 +305,7 @@ The following hooks are supported (please check [docs/ARCHITECTURE.md](docs/ARCH
* `prepull` is executed before the code is pulled from the source repository
* `precommit` is executed before the code is commited
* `prepush` is executed before the push is executed, right after the commit
* `precleanup` is executed before older PRs targeting the same branch are closed
* `prepr` is executed before the PR is done

**Remark** The underlying OS is defined by an Alpine container.
Expand All @@ -329,12 +331,28 @@ hooks:
commands:
- echo 'hi, we are within the prepush phase'
- echo 'maybe you want to add further changes and commits'
precleanup:
commands:
- echo 'hi, we are within the precleanup phase'
- echo 'maybe you want to interact with older PRs before they are closed'
prepr:
commands:
- echo 'hi, we are within the prepr phase'
- echo 'maybe you want to change the code a bit and do another push before creating the pr'
```

## Labels creation

By default, generated PRs will be labeled with the `template_sync` label.
If that label doesn't exist in your repository, it will be created automatically unless you specify your own existing labels.
Associating a label with the generated PRs helps keeping track of them and allows for features like automatic PR cleanup.

## Pull request cleanup

Depending on your way of working, you may end up with multiple pull requests related to template syncing pointing to the same branch.
If you want to avoid this situation, you can instruct this action to clean up older PRs (search based on labels defined with the `pr_labels` config parameter).
:warning: this feature will close all pull requests with labels configured with `pr_labels` config parameter.

## Troubleshooting

* refusing to allow a GitHub App to create or update workflow `.github/workflows/******.yml` without `workflows` permission
Expand Down
5 changes: 5 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ inputs:
default: "upstream merge template repository"
pr_labels:
description: "[optional] comma separated list of pull request labels"
default: "template_sync"
pr_reviewers:
description: "[optional] comma separated list of pull request reviewers"
pr_commit_msg:
Expand All @@ -36,6 +37,9 @@ inputs:
is_allow_hooks:
description: "[optional] set to true if you want to allow hooks. Use this functionality with caution!"
default: "false"
is_pr_cleanup:
description: "[optional] set to true if you want to cleanup older PRs targeting the same branch."
default: "false"
is_not_source_github:
description: "[optional] set to true if the source repository is not a github related repository. Useful e.q. if the source is GitLab"
default: "false"
Expand All @@ -61,6 +65,7 @@ runs:
HOSTNAME: ${{ inputs.hostname }}
IS_DRY_RUN: ${{ inputs.is_dry_run }}
IS_ALLOW_HOOKS: ${{ inputs.is_allow_hooks }}
IS_PR_CLEANUP: ${{ inputs.is_pr_cleanup}}
IS_NOT_SOURCE_GITHUB: ${{ inputs.is_not_source_github }}
GIT_USER_NAME: ${{ inputs.git_user_name }}
GIT_USER_EMAIL: ${{ inputs.git_user_email }}
Expand Down
104 changes: 88 additions & 16 deletions src/sync_template.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ echo "::endgroup::"
cmd_from_yml_file "prepull"

echo "::group::Pull template"

debug "create new branch from default branch with name ${NEW_BRANCH}"
git checkout -b "${NEW_BRANCH}"
debug "pull changes from template"
Expand Down Expand Up @@ -97,6 +98,7 @@ fi
cmd_from_yml_file "precommit"

echo "::group::commit changes"

git add .

# we are checking the ignore file if it exists or is empty
Expand All @@ -121,26 +123,96 @@ git commit -m "${PR_COMMIT_MSG}"

echo "::endgroup::"

push_and_create_pr () {
cmd_from_yml_file "prepush"
if [ "$IS_DRY_RUN" != "true" ]; then
cleanup_older_prs () {
older_prs=$(gh pr list \
--base "${UPSTREAM_BRANCH}" \
--state open \
--label "${PR_LABELS}" \
--json number \
--template '{{range .}}{{printf "%v" .number}}{{"\n"}}{{end}}')

for older_pr in $older_prs
do
gh pr close "$older_pr"
debug "Closed PR #${older_pr}"
done
}
echo "::group::cleanup older PRs"

if [ "$IS_DRY_RUN" != "true" ]; then
if [ "$IS_PR_CLEANUP" != "false" ]; then
if [[ -z "${PR_LABELS}" ]]; then
warn "env var 'PR_LABELS' is empty. Skipping older prs cleanup"
else
cmd_from_yml_file "precleanup"
cleanup_older_prs
fi
else
warn "is_pr_cleanup option is set to off. Skipping older prs cleanup"
fi
else
warn "dry_run option is set to off. Skipping older prs cleanup"
fi

echo "::endgroup::"

echo "::group::push changes and create PR"
debug "push changes"
git push --set-upstream origin "${NEW_BRANCH}"

cmd_from_yml_file "prepr"
maybe_create_labels () {
all_labels=${PR_LABELS//,/$'\n'}
for label in $all_labels
do
search_result=$(gh label list \
--search "${label}" \
--limit 1 \
--json name \
--template '{{range .}}{{printf "%v" .name}}{{"\n"}}{{end}}')

if [ "${search_result}" = "${label}" ]; then
info "label '${label}' was found in the repository"
else
gh label create "${label}"
info "label '${label}' was missing and has been created"
fi
done
}

echo "::group::check for missing labels"

gh pr create \
--title "${PR_TITLE}" \
--body "Merge ${SOURCE_REPO_PATH} ${NEW_TEMPLATE_GIT_HASH}" \
--base "${UPSTREAM_BRANCH}" \
--label "${PR_LABELS}" \
--reviewer "${PR_REVIEWERS}"
echo "::endgroup::"
if [[ -z "${PR_LABELS}" ]]; then
info "env var 'PR_LABELS' is empty. Skipping labels check"
else
if [ "$IS_DRY_RUN" != "true" ]; then
maybe_create_labels
else
warn "dry_run option is set to off. Skipping push changes and skip create pr"
warn "dry_run option is set to off. Skipping labels check"
fi
fi

echo "::endgroup::"

push () {
debug "push changes"
git push --set-upstream origin "${NEW_BRANCH}"
}

create_pr () {
gh pr create \
--title "${PR_TITLE}" \
--body "Merge ${SOURCE_REPO_PATH} ${NEW_TEMPLATE_GIT_HASH}" \
--base "${UPSTREAM_BRANCH}" \
--label "${PR_LABELS}" \
--reviewer "${PR_REVIEWERS}"
}

push_and_create_pr
echo "::group::push changes and create PR"

if [ "$IS_DRY_RUN" != "true" ]; then
cmd_from_yml_file "prepush"
push
cmd_from_yml_file "prepr"
create_pr
else
warn "dry_run option is set to off. Skipping push changes and skip create pr"
fi

echo "::endgroup::"