├── .editorconfig ├── .env ├── .github ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md ├── RELEASE.md └── workflows │ ├── linter.yml │ ├── static_analysis.yml │ └── tests.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── bin └── pre-commit ├── build.sh ├── create-pr ├── install-dependencies.sh ├── src ├── check_os.sh ├── console_header.sh ├── dev │ └── dumper.sh ├── env_configuration.sh ├── helpers.sh ├── main.sh ├── pr_body.sh ├── pr_label.sh ├── pr_ticket.sh ├── pr_title.sh └── validate.sh └── tests ├── bootstrap.sh ├── e2e ├── create-pr_test.sh └── snapshots │ ├── create_pr_test_sh.test_script_with_dry_run.snapshot │ ├── create_pr_test_sh.test_script_with_dry_run_and_extra_args.snapshot │ └── create_pr_test_sh.test_success.snapshot └── unit ├── helpers_test.sh ├── pr_body_test.sh ├── pr_label_test.sh ├── pr_ticket_key_test.sh ├── pr_ticket_number_test.sh ├── pr_title_test.sh ├── snapshots ├── pr_body_test_sh.test_pr_body_background_without_link.snapshot ├── pr_body_test_sh.test_pr_body_link_with_PR_TICKET_LINK_PREFIX.snapshot ├── pr_body_test_sh.test_pr_body_link_with_branch_with_numbers_and_prefix.snapshot ├── pr_body_test_sh.test_pr_body_link_with_branch_with_numbers_no_prefix.snapshot ├── pr_body_test_sh.test_pr_body_link_with_pr_ticket_prefix_text.snapshot ├── pr_body_test_sh.test_pr_body_link_without_ticket_key.snapshot ├── pr_body_test_sh.test_pr_body_link_without_ticket_link_prefix.snapshot └── pr_body_test_sh.test_pr_body_link_without_ticket_number.snapshot └── validate_test.sh /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | 11 | [**.sh] 12 | max_line_length = 120 13 | 14 | [**.md] 15 | indent_size = 2 16 | 17 | [{Makefile,**.mk,.git*}] 18 | indent_style = tab 19 | 20 | [{lib/*,README.md}] 21 | indent_size = unset 22 | 23 | [{tests/acceptance/**.sh,src/console_header.sh}] 24 | indent_size = unset 25 | 26 | [tests/**/*.snapshot] 27 | indent_size = unset 28 | max_line_length = unset 29 | trim_trailing_whitespace = false 30 | 31 | [bin/*] 32 | charset = unset 33 | end_of_line = unset 34 | insert_final_newline = unset 35 | trim_trailing_whitespace = unset 36 | indent_style = unset 37 | indent_size = unset 38 | -------------------------------------------------------------------------------- /.env: -------------------------------------------------------------------------------- 1 | # create-pr 2 | PR_TEMPLATE_PATH=".github/PULL_REQUEST_TEMPLATE.md" 3 | PR_TITLE_TEMPLATE="{{TICKET_KEY}}-{{TICKET_NUMBER}} {{PR_TITLE}}" 4 | PR_TICKET_LINK_PREFIX="https://github.com/Chemaclass/create-pr/issues/" 5 | PR_LINK_PREFIX_TEXT="Closes: " 6 | PR_LABEL_MAPPING="docs:documentation; fix|bug|bugfix|hotfix:bug; default:enhancement" 7 | 8 | # lib/bashunit 9 | BASHUNIT_DEFAULT_PATH="tests" 10 | BASHUNIT_LOAD_FILE="tests/bootstrap.sh" 11 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we 4 | pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, 5 | submitting pull requests or patches, and other activities. 6 | 7 | We are committed to making participation in this project a harassment-free experience for everyone, regardless of level 8 | of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, 9 | race, ethnicity, age, religion, or nationality. 10 | 11 | Examples of unacceptable behavior by participants include: 12 | 13 | * The use of sexualized language or imagery 14 | * Personal attacks 15 | * Trolling or insulting/derogatory comments 16 | * Public or private harassment 17 | * Publishing other's private information, such as physical or electronic addresses, without explicit permission 18 | * Other unethical or unprofessional conduct 19 | 20 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, 21 | issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any 22 | contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 23 | 24 | By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these 25 | principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of 26 | Conduct may be permanently removed from the project team. 27 | 28 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the 29 | project or its community. 30 | 31 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project maintainer 32 | at chemaclass@outlook.es. All complaints will be reviewed and investigated and will result in a response that is deemed 33 | necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the 34 | reporter of an incident. 35 | 36 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available 37 | at [https://contributor-covenant.org/version/1/3/0/][version] 38 | 39 | [homepage]: https://contributor-covenant.org 40 | 41 | [version]: https://contributor-covenant.org/version/1/3/0/ 42 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to bashunit 2 | 3 | ## We have a Code of Conduct 4 | 5 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 6 | 7 | ## Any contributions you make will be under the MIT License 8 | 9 | When you submit code changes, your submissions are understood to be under the same [MIT](https://github.com/TypedDevs/bashunit/blob/main/LICENSE) that covers the project. By contributing to this project, you agree that your contributions will be licensed under its MIT. 10 | 11 | ## Write bug reports with detail, background, and sample code 12 | 13 | In your bug report, please provide the following: 14 | 15 | * A quick summary and/or background 16 | * Steps to reproduce 17 | * Be specific! 18 | * Give sample code if you can. 19 | * What you expected would happen 20 | * What actually happens 21 | * Notes (possibly including why you think this might be happening, or stuff you tried that didn't work) 22 | 23 | Please post code and output as text ([using proper markup](https://guides.github.com/features/mastering-markdown/)). Additional screenshots to help contextualize behavior are ok. 24 | 25 | ## Workflow for Pull Requests 26 | 27 | 1. Fork/clone the repository. 28 | 2. Create your branch from `main` if you plan to implement new functionality or change existing code significantly. 29 | 3. Implement your change and add tests for it. 30 | 4. Ensure the test suite passes. 31 | 5. Ensure the code complies with our coding guidelines (see below). 32 | 6. Send that pull request! 33 | 34 | Please make sure you have [set up your username and email address](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup) for use with Git. Strings such as `silly nick name ` looks bad in the commit history of a project. 35 | 36 | 37 | --- 38 | 39 | ## Development 40 | 41 | - Entry point `create-pr` 42 | - Isolated testable functions inside files under the `src` directory 43 | 44 | ### Build 45 | 46 | You can build the whole project into a single executable script combining all files from src and the entry point. 47 | 48 | ```bash 49 | ./build.sh 50 | ``` 51 | 52 | ## Testing 53 | 54 | Install dependencies: `./install-dependencies.sh` 55 | 56 | Run tests: 57 | ```bash 58 | # using make 59 | make test 60 | 61 | # using bashunit directly 62 | lib/bashunit tests 63 | ``` 64 | 65 | ## Coding Guidelines 66 | 67 | ### ShellCheck 68 | 69 | To contribute to this repository you must have [ShellCheck](https://github.com/koalaman/shellcheck) installed on your local machine or IDE, since it is the static code analyzer that is being used in continuous integration pipelines. 70 | 71 | Installation: https://github.com/koalaman/shellcheck#installing 72 | 73 | #### Example of usage 74 | 75 | ```bash 76 | # using make 77 | make sa 78 | 79 | # using ShellCheck itself 80 | shellcheck ./**/**/*.sh -C 81 | ``` 82 | 83 | ### editorconfig-checker 84 | 85 | To contribute to this repository, consider installing [editorconfig-checker](https://github.com/editorconfig-checker/editorconfig-checker) to check all project files regarding the `.editorconfig` to ensure we all fulfill the standard. 86 | 87 | Installation: https://github.com/editorconfig-checker/editorconfig-checker#installation 88 | 89 | To run it, use the following command: 90 | ```bash 91 | # using make 92 | make lint 93 | 94 | # using editorconfig-checker itself 95 | ec -config .editorconfig 96 | ``` 97 | 98 | This command will be executed on the CI to ensure the project's quality standards. 99 | 100 | #### We recommend 101 | 102 | To install the pre-commit of the project with the following command: 103 | 104 | **Please note that you will need to have ShellCheck and editorconfig-checker installed on your computer.** 105 | See above how to install in your local. 106 | 107 | ```bash 108 | make pre_commit/install 109 | ``` 110 | 111 | [Shell Guide](https://google.github.io/styleguide/shellguide.html#s7.2-variable-names) by Google Conventions. 112 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://chemaclass.com/sponsor"] 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### 🔗 Ticket 2 | 3 | {{ TICKET_LINK }} 4 | 5 | ## 🤔 Background 6 | 7 | 8 | 9 | ## 💡 Goal 10 | 11 | 12 | 13 | ## 🔖 Changes 14 | 15 | 16 | 17 | ## 🖼️ Screenshots 18 | 19 | 20 | 21 | #### BEFORE 22 | 23 | #### AFTER 24 | 25 | -------------------------------------------------------------------------------- /.github/RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release 2 | 3 | This is a guide to know the steps to create a new release. 4 | 5 | 1. Update the version in [CREATE_PR_VERSION](../create-pr) 6 | 1. Update the version in [CHANGELOG.md](../CHANGELOG.md) 7 | 1. Build the project `./build.sh bin` - This generates `bin/create-pr` & `bin/checksum` 8 | 1. Create a [new release](https://github.com/Chemaclass/create-pr/releases/new) from GitHub 9 | 1. Attach `bin/create-pr` and `bin/checksum` to the release 10 | -------------------------------------------------------------------------------- /.github/workflows/linter.yml: -------------------------------------------------------------------------------- 1 | name: Editorconfig Linter 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | linter: 11 | name: "Run Lint on ${{ matrix.os }}" 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout 15 | uses: actions/checkout@v4 16 | with: 17 | fetch-depth: 0 18 | 19 | - name: Editorconfig Linter 20 | uses: editorconfig-checker/action-editorconfig-checker@main 21 | 22 | - name: Run Linter 23 | run: editorconfig-checker 24 | 25 | -------------------------------------------------------------------------------- /.github/workflows/static_analysis.yml: -------------------------------------------------------------------------------- 1 | name: Static Analysis 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | shellcheck: 11 | name: ShellCheck 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: 16 | - ubuntu-latest 17 | - macos-latest 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | fetch-depth: 0 23 | 24 | - name: Run ShellCheck 25 | uses: ludeeus/action-shellcheck@master 26 | env: 27 | SHELLCHECK_OPTS: -e SC1091 -e SC2155 28 | 29 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | tests: 11 | name: "Run tests on ${{ matrix.os }}" 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: 16 | - ubuntu-latest 17 | - macos-latest 18 | include: 19 | - os: ubuntu-latest 20 | script_name: 'make test' 21 | - os: macos-latest 22 | script_name: 'make test' 23 | steps: 24 | - name: Checkout 25 | uses: actions/checkout@v4 26 | with: 27 | fetch-depth: 0 28 | 29 | - name: "Install bashunit" 30 | run: "./install-dependencies.sh" 31 | 32 | - name: Run Tests 33 | run: ${{ matrix.script_name }} 34 | 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | lib/ 3 | .idea/ 4 | .vscode/ 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## Unreleased 4 | 5 | - Fix validate target branch 6 | 7 | ## [0.8](https://github.com/Chemaclass/create-pr/compare/0.7...0.8) - 2024-10-28 8 | 9 | - Use ticket number only at the beginning of branch name 10 | - Add `PR_TITLE_REMOVE_PREFIX`: Remove custom prefix text from the generated title 11 | - Add `PR_REVIEWER`: Allow adding reviewers via local .env 12 | 13 | ## [0.7](https://github.com/Chemaclass/create-pr/compare/0.6...0.7) - 2024-09-29 14 | 15 | - Validate current branch is not target branch 16 | - Internal refactorings and minor improvements 17 | 18 | ## [0.6](https://github.com/Chemaclass/create-pr/compare/0.5...0.6) - 2024-09-16 19 | 20 | - Fix additional single quotes on `PR_TITLE_TEMPLATE` 21 | - Improve feedback when given wrong PR template 22 | - Add GitLab support 23 | - Add `--dry-run` to display the used data before creating the PR 24 | - Rename build executable script to `create-pr` 25 | - Allow passing extra arguments to the `gh` or `glab` tools 26 | 27 | ## [0.5](https://github.com/Chemaclass/create-pr/compare/0.4...0.5) - 2024-09-09 28 | 29 | - Support ticket link with number 30 | - Remove ticket number from PR title when there is no ticket key 31 | - Add `PR_LINK_PREFIX_TEXT` 32 | - Text to display if the link does not contain a `TICKET_KEY` 33 | - Add `PR_TITLE_TEMPLATE` 34 | - Enable custom PR title with placeholders (`{{TICKET_NUMBER}}`, `{{TICKET_KEY}}`, `{{PR_TITLE}}`) 35 | - eg `PR_TITLE_TEMPLATE="{{TICKET_KEY}}-{{TICKET_NUMBER}} {{PR_TITLE}}"` 36 | - Refactor unique src file into multiple (single scope/responsibility) files 37 | - Add option `-t|--title` to generate a branch name based on the PR title. 38 | - Replace `PR_TEMPLATE_DIR` to `PR_TEMPLATE_PATH` 39 | 40 | ## [0.4](https://github.com/Chemaclass/create-pr/compare/0.3...0.4) - 2024-09-06 41 | 42 | - Fix pr_ticket_number when branch name contains numbers 43 | - Load `.env.local` on top of `.env` in case it exists 44 | - Add `{{BACKGROUND}}` with "Details in the ticket" by default when a ticket link is used 45 | - Enable spaces inside placeholders 46 | - `{{ BACKGROUND }}` 47 | - Enable placeholders inside HTML comments in the PR template 48 | - `` 49 | - Add `PR_LABEL_MAPPING` 50 | - eg `PR_LABEL_MAPPING="feat:enhancement; fix|bug:bug; default:extra"` 51 | 52 | ## [0.3](https://github.com/Chemaclass/create-pr/compare/0.2...0.3) - 2024-09-05 53 | 54 | - Fix format PR body with number in branch name 55 | - Fix ticket key without numbers but branch prefix 56 | 57 | ## [0.2](https://github.com/Chemaclass/create-pr/compare/0.1...0.2) - 2024-08-28 58 | 59 | - Improved format_title 60 | - Ignore ticket key on PR title when it's not present on the branch 61 | - Display Nope on the {{TICKET_LINK}} inside the PR template if no valid link can be created 62 | 63 | ## [0.1](https://github.com/Chemaclass/create-pr/compare/main...0.1) - 2024-08-27 64 | 65 | Initial release. Check README for instructions about installation and how to use it. 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Jose M. Valera Reales 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL=/bin/bash 2 | 3 | -include .env 4 | 5 | STATIC_ANALYSIS_CHECKER := $(shell which shellcheck 2> /dev/null) 6 | LINTER_CHECKER := $(shell which ec 2> /dev/null) 7 | GIT_DIR = $(shell git rev-parse --git-dir 2> /dev/null) 8 | 9 | OS:= 10 | ifeq ($(OS),Windows_NT) 11 | OS +=WIN32 12 | ifeq ($(PROCESSOR_ARCHITECTURE),AMD64) 13 | OS +=_AMD64 14 | endif 15 | ifeq ($(PROCESSOR_ARCHITECTURE),x86) 16 | OS +=_IA32 17 | endif 18 | else 19 | UNAME_S := $(shell uname -s) 20 | ifeq ($(UNAME_S),Linux) 21 | OS+=LINUX 22 | endif 23 | ifeq ($(UNAME_S),Darwin) 24 | OS+=OSX 25 | endif 26 | UNAME_P := $(shell uname -p) 27 | ifeq ($(UNAME_P),x86_64) 28 | OS +=_AMD64 29 | endif 30 | ifneq ($(filter %86,$(UNAME_P)),) 31 | OS+=_IA32 32 | endif 33 | ifneq ($(filter arm%,$(UNAME_P)),) 34 | OS+=_ARM 35 | endif 36 | endif 37 | 38 | help: 39 | @echo "" 40 | @echo "Usage: make [command]" 41 | @echo "" 42 | @echo "Commands:" 43 | @echo " test Run the tests" 44 | @echo " pre_commit/install Install the pre-commit hook" 45 | @echo " pre_commit/run Function that will be called when the pre-commit hook runs" 46 | @echo " sa Run shellcheck static analysis tool" 47 | @echo " lint Run editorconfig linter tool" 48 | 49 | SRC_SCRIPTS_DIR=src 50 | PRE_COMMIT_SCRIPTS_FILE=./bin/pre-commit 51 | 52 | test: $(TEST_SCRIPTS_DIR) 53 | @lib/bashunit tests 54 | 55 | pre_commit/install: 56 | @echo "Installing pre-commit hook" 57 | cp $(PRE_COMMIT_SCRIPTS_FILE) $(GIT_DIR)/hooks/ 58 | 59 | pre_commit/run: test sa lint 60 | 61 | sa: 62 | ifndef STATIC_ANALYSIS_CHECKER 63 | @printf "\e[1m\e[31m%s\e[0m\n" "Shellcheck not installed: Static analysis not performed!" && exit 1 64 | else 65 | @shellcheck ./**/*.sh -C && printf "\e[1m\e[32m%s\e[0m\n" "ShellCheck: OK!" 66 | endif 67 | 68 | lint: 69 | ifndef LINTER_CHECKER 70 | @printf "\e[1m\e[31m%s\e[0m\n" "Editorconfig not installed: Lint not performed!" && exit 1 71 | else 72 | @ec -config .editorconfig && printf "\e[1m\e[32m%s\e[0m\n" "editorconfig-check: OK!" 73 | endif 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # create-pr 2 | 3 | A bash script that helps create your PRs. 4 | It normalizes the PR title, description, assignee by default, and initial label based on your branch name. 5 | 6 | ## Current support 7 | 8 | - **GitHub** requires https://cli.github.com/manual/gh_pr_create 9 | - **GitLab** requires https://gitlab.com/gitlab-org/cli/-/blob/main/docs/source/mr/create.md 10 | 11 | > These are needed to access your client via the terminal, and they are independent of this script. 12 | 13 | ### Extra arguments 14 | 15 | Any additional argument will be passed to `gh` or `glab` tool. 16 | 17 | ```bash 18 | # Create a draft PR 19 | ./create-pr --draft 20 | 21 | # Create a draft PR with an overridden title 22 | ./create-pr --draft --title="Custom Title" 23 | ``` 24 | 25 | * GitHub: https://cli.github.com/manual/gh_pr_create 26 | * GitLab: https://gitlab.com/gitlab-org/cli/-/blob/main/docs/source/mr/create.md 27 | 28 | ## Installation 29 | 30 | You can download the latest `single executable script` on the [Releases page](https://github.com/Chemaclass/bash-create-pr/releases) or build it yourself. 31 | 32 | If you download the executable file from a release's GitHub project, you can check the file's checksum to validate that 33 | it was not altered. The checksum for each release is on each release on GitHub. 34 | 35 | To build the project yourself, execute the script `./build.sh` and move the resulting `bin/create-pr` script wherever you want. 36 | 37 | ## How to use it? 38 | 39 | The script is customizable via `.env` variables. See [.env.example](.env.example). 40 | 41 | ### PR_TEMPLATE_PATH 42 | 43 | Define the path to locate the PR template. See [template example](.github/PULL_REQUEST_TEMPLATE.md). 44 | 45 | #### Example 46 | 47 | ```bash 48 | PR_TEMPLATE_PATH=.github/PULL_REQUEST_TEMPLATE.md 49 | ``` 50 | 51 | #### Placeholders 52 | 53 | - `{{ TICKET_LINK }}` 54 | - Uses `PR_TICKET_LINK_PREFIX` appending the ticket key and number to form the full URL 55 | - `{{ BACKGROUND }}` 56 | - if the link is found: `Details in the ticket` 57 | - if the link is not found: `Provide some context to the reviewer before jumping in the code` 58 | - You can define them inside a comment (to avoid rendering the placeholders when creating a PR without this script) 59 | - eg `` 60 | 61 | ### PR_LINK_PREFIX_TEXT 62 | 63 | Text to display if the link does not contain a `TICKET_KEY`. 64 | 65 | #### Example 66 | 67 | Branch name: `feat/27-my-branch-name` 68 | 69 | ```bash 70 | PR_TICKET_LINK_PREFIX="https://github.com/Chemaclass/create-pr/issues/" 71 | PR_LINK_PREFIX_TEXT="Closes: " 72 | ``` 73 | 74 | - Result: `Closes: https://github.com/Chemaclass/create-pr/issues/27` 75 | 76 | ### PR_TITLE_TEMPLATE 77 | 78 | Enable custom PR title with placeholders: 79 | 80 | - `{{TICKET_NUMBER}}` 81 | - `{{TICKET_KEY}}` 82 | - `{{PR_TITLE}}` 83 | 84 | #### Example 85 | 86 | ```bash 87 | PR_TITLE_TEMPLATE="{{TICKET_KEY}}-{{TICKET_NUMBER}} {{PR_TITLE}}" 88 | ``` 89 | 90 | ### PR_TITLE_REMOVE_PREFIX 91 | 92 | Remove custom prefix text from the generated title. 93 | Useful when you have tickets prefixed with some text like `BE:` or `FE:`. 94 | 95 | #### Example 96 | 97 | By default, having a branch named: `feat/ticket-123-be-crete-feature-foo` 98 | 99 | Default behaviour: 100 | - `TICKET-123 Be create feature foo` 101 | 102 | With `PR_TITLE_REMOVE_PREFIX=be` the result will be: 103 | - `TICKET-123 Create feature foo` 104 | 105 | > This variable accept multiple strings to consider/remove comma separated. 106 | > 107 | > Eg: `PR_TITLE_REMOVE_PREFIX=be,fe` 108 | 109 | ### PR_LABEL 110 | 111 | > Alias: LABEL 112 | 113 | Define a label for the PR. 114 | 115 | #### Example 116 | 117 | ```bash 118 | PR_LABEL=enhancement 119 | ``` 120 | 121 | If empty, extract the label from the branch's prefix - see `PR_LABEL_MAPPING`. 122 | 123 | ### PR_LABEL_MAPPING 124 | 125 | Define a mapping from prefix branches to GitHub label. 126 | 127 | #### Example 128 | 129 | ```bash 130 | PR_LABEL_MAPPING="docs:documentation; fix|bug|bugfix|hotfix:bug; default:enhancement" 131 | ``` 132 | 133 | - eg `docs/your-branch` -> `documentation` label 134 | - eg `fix/your-branch` -> `bug` label 135 | - eg `bug/your-branch` -> `bug` label 136 | - eg `unknown/your-branch` -> `enhancement` label 137 | 138 | ### PR_ASSIGNEE 139 | 140 | > Alias: ASSIGNEE 141 | 142 | - `PR_ASSIGNEE` or `@me` by default 143 | 144 | ### PR_REVIEWER 145 | 146 | > Alias: REVIEWER 147 | 148 | - Empty by default 149 | 150 | ### TARGET_BRANCH 151 | 152 | - `TARGET_BRANCH` or `main` by default 153 | 154 | ## HINTS 155 | 156 | - Add to your composer, npm or similar a script pointing to the `create-pr` 157 | - You can use the [PULL_REQUEST_TEMPLATE](./.github/PULL_REQUEST_TEMPLATE.md) from this project as example 158 | 159 | ## Contribute 160 | 161 | Suggestions, ideas and PRs are more than welcome here! 162 | Please, Check out our [CONTRIBUTING.md](.github/CONTRIBUTING.md) guidelines. 163 | -------------------------------------------------------------------------------- /bin/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | echo "Running pre-commit checks" 3 | 4 | make pre_commit/run 5 | EXIT_CODE=$? 6 | 7 | if [[ ${EXIT_CODE} -ne 0 ]]; then 8 | echo "Pre Commit checks failed. Please fix the above issues before committing" 9 | exit ${EXIT_CODE} 10 | else 11 | echo "Pre Commit checks passed, no problems found" 12 | exit 0 13 | fi 14 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | source src/check_os.sh 4 | 5 | function build() { 6 | local out=$1 7 | 8 | generate_bin "$out" 9 | generate_checksum "$out" 10 | 11 | echo "⚡️Build completed⚡️" 12 | } 13 | 14 | function generate_bin() { 15 | local out=$1 16 | local temp 17 | temp="$(dirname "$out")/temp.sh" 18 | 19 | echo '#!/bin/bash' > "$temp" 20 | echo "Generating create-pr in the '$(dirname "$out")' folder..." 21 | for file in src/*.sh; do 22 | { 23 | echo "# $file" 24 | tail -n +2 "$file" >> "$temp" 25 | echo "" 26 | } >> "$temp" 27 | done 28 | 29 | cat create-pr >> "$temp" 30 | grep -v '^source' "$temp" > "$out" 31 | rm "$temp" 32 | chmod u+x "$out" 33 | } 34 | 35 | function generate_checksum() { 36 | local out=$1 37 | 38 | if [[ "$_OS" == "Windows" ]]; then 39 | return 40 | fi 41 | 42 | if [[ "$_OS" == "OSX" ]]; then 43 | checksum=$(shasum -a 256 "$out") 44 | elif [[ "$_OS" == "Linux" ]]; then 45 | checksum=$(sha256sum "$out") 46 | fi 47 | 48 | echo "$checksum" > "$(dirname "$out")/checksum" 49 | echo "$checksum" 50 | } 51 | 52 | ######################## 53 | ######### MAIN ######### 54 | ######################## 55 | 56 | mkdir -p "bin" 57 | build "bin/create-pr" 58 | -------------------------------------------------------------------------------- /create-pr: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # shellcheck disable=SC2034 5 | declare -r CREATE_PR_VERSION="0.8.0" 6 | 7 | CREATE_PR_ROOT_DIR="$(dirname "${BASH_SOURCE[0]}")" 8 | export CREATE_PR_ROOT_DIR 9 | 10 | source "$CREATE_PR_ROOT_DIR/src/dev/dumper.sh" 11 | source "$CREATE_PR_ROOT_DIR/src/helpers.sh" 12 | source "$CREATE_PR_ROOT_DIR/src/validate.sh" 13 | source "$CREATE_PR_ROOT_DIR/src/pr_ticket.sh" 14 | source "$CREATE_PR_ROOT_DIR/src/pr_body.sh" 15 | source "$CREATE_PR_ROOT_DIR/src/pr_label.sh" 16 | source "$CREATE_PR_ROOT_DIR/src/pr_title.sh" 17 | source "$CREATE_PR_ROOT_DIR/src/env_configuration.sh" 18 | source "$CREATE_PR_ROOT_DIR/src/console_header.sh" 19 | source "$CREATE_PR_ROOT_DIR/src/main.sh" 20 | 21 | DRY_RUN=${DRY_RUN:-false} 22 | EXTRA_ARGS=() 23 | 24 | while [[ $# -gt 0 ]]; do 25 | argument="$1" 26 | case $argument in 27 | --debug) 28 | set -x 29 | ;; 30 | --dry-run) 31 | DRY_RUN=true 32 | ;; 33 | -e|--env) 34 | # shellcheck disable=SC1090 35 | source "$2" 36 | shift 37 | ;; 38 | -t|--title) 39 | helpers::generate_branch_name "$2" "${3:-}" 40 | trap '' EXIT && exit 0 41 | ;; 42 | -h|--help) 43 | console_header::print_help 44 | trap '' EXIT && exit 0 45 | ;; 46 | -v|--version) 47 | console_header::print_version 48 | trap '' EXIT && exit 0 49 | ;; 50 | *) 51 | EXTRA_ARGS+=("$argument") 52 | esac 53 | shift 54 | done 55 | 56 | PR_LABEL=${PR_LABEL:-${LABEL:-$(pr_label "$CURRENT_BRANCH" "${PR_LABEL_MAPPING:-}")}} 57 | PR_TITLE=$(pr_title "$CURRENT_BRANCH") 58 | PR_BODY=$(pr_body "$CURRENT_BRANCH" "$PR_TEMPLATE") 59 | 60 | if [[ "$DRY_RUN" == true ]]; then 61 | if [ ${#EXTRA_ARGS[@]} -gt 0 ]; then 62 | printf "EXTRA_ARGS: %s\n" "${EXTRA_ARGS[@]}" 63 | else 64 | printf "EXTRA_ARGS: empty\n" 65 | fi 66 | printf "REMOTE_URL: %s\n" "$REMOTE_URL" 67 | printf "TARGET_BRANCH: %s\n" "$TARGET_BRANCH" 68 | printf "CURRENT_BRANCH: %s\n" "$CURRENT_BRANCH" 69 | printf "PR_USING_CLIENT: %s\n" "$PR_USING_CLIENT" 70 | printf "PR_TEMPLATE: %s\n" "$PR_TEMPLATE" 71 | printf "PR_LABEL: %s\n" "$PR_LABEL" 72 | printf "PR_TITLE: %s\n" "$PR_TITLE" 73 | printf "PR_BODY:\n%s\n" "$PR_BODY" 74 | exit 0 75 | fi 76 | 77 | export PR_LABEL 78 | export PR_TITLE 79 | export PR_BODY 80 | 81 | main::create_pr 82 | 83 | echo "Script finished successfully." 84 | -------------------------------------------------------------------------------- /install-dependencies.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Ensure bashdep is installed 4 | [ ! -f lib/bashdep ] && { 5 | mkdir -p lib 6 | curl -sLo lib/bashdep https://github.com/Chemaclass/bashdep/releases/download/0.1/bashdep 7 | chmod +x lib/bashdep 8 | } 9 | 10 | DEPENDENCIES=( 11 | "https://github.com/TypedDevs/bashunit/releases/download/0.18.0/bashunit" 12 | "https://github.com/Chemaclass/bash-dumper/releases/download/0.1/dumper.sh@dev" 13 | ) 14 | 15 | source lib/bashdep 16 | bashdep::setup dir="lib" dev-dir="src/dev" silent=false 17 | bashdep::install "${DEPENDENCIES[@]}" 18 | -------------------------------------------------------------------------------- /src/check_os.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o allexport 3 | 4 | # shellcheck disable=SC2034 5 | _OS="Unknown" 6 | 7 | if [[ "$(uname)" == "Linux" ]]; then 8 | _OS="Linux" 9 | elif [[ "$(uname)" == "Darwin" ]]; then 10 | _OS="OSX" 11 | elif [[ $(uname) == *"MINGW"* ]]; then 12 | _OS="Windows" 13 | fi 14 | -------------------------------------------------------------------------------- /src/console_header.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o allexport 3 | 4 | function console_header::print_version() { 5 | printf "%s\n" "$CREATE_PR_VERSION" 6 | } 7 | 8 | function console_header::print_help() { 9 | cat < 24 | Load a custom env file overriding the .env environment variables 25 | 26 | -t|--title 27 | Generate a branch name based on the PR title 28 | "feat" by default 29 | 30 | -v|--version 31 | Displays the current version 32 | 33 | -h|--help 34 | This message 35 | 36 | See source code: https://github.com/Chemaclass/create-pr 37 | EOF 38 | } 39 | -------------------------------------------------------------------------------- /src/dev/dumper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Pass in any number of ANSI SGR codes. 4 | # 5 | # Code reference: 6 | # https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters 7 | # Credit: 8 | # https://superuser.com/a/1119396 9 | sgr() { 10 | local codes=${1:-0} 11 | shift 12 | 13 | for c in "$@"; do 14 | codes="$codes;$c" 15 | done 16 | 17 | echo $'\e'"[${codes}m" 18 | } 19 | 20 | _COLOR_BOLD="$(sgr 1)" 21 | _COLOR_FAINT="$(sgr 2)" 22 | _COLOR_RED="$(sgr 31)" 23 | _COLOR_YELLOW="$(sgr 33)" 24 | _COLOR_BLACK="$(sgr 30)" 25 | _COLOR_GREEN="$(sgr 32)" 26 | _COLOR_DEFAULT="$(sgr 0)" 27 | 28 | function trace() { 29 | set -x 30 | } 31 | 32 | function untrace() { 33 | set +x 34 | } 35 | 36 | # An alternative to echo when debugging. 37 | function dump() { 38 | printf "[%s] %s: %s\n" "${_COLOR_YELLOW}DUMP${_COLOR_DEFAULT}" \ 39 | "${_COLOR_GREEN}${BASH_SOURCE[1]}:${BASH_LINENO[0]}" \ 40 | "${_COLOR_DEFAULT}$*" 41 | } 42 | 43 | # Dump and Die. 44 | function dd() { 45 | printf "[%s] %s: %s\n" "${_COLOR_RED}DUMP${_COLOR_DEFAULT}" \ 46 | "${_COLOR_GREEN}${BASH_SOURCE[1]}:${BASH_LINENO[0]}" \ 47 | "${_COLOR_DEFAULT}$*" 48 | 49 | kill -9 $$ 50 | } 51 | 52 | function debug_var() { 53 | local var_name=$1 54 | local var_value=${!var_name} 55 | printf "[%s] %s: %s=%s\n" "${_COLOR_FAINT}DEBUG${_COLOR_DEFAULT}" \ 56 | "${_COLOR_GREEN}${BASH_SOURCE[1]}:${BASH_LINENO[0]}" \ 57 | "$var_name" "$var_value" 58 | } 59 | -------------------------------------------------------------------------------- /src/env_configuration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o allexport 3 | 4 | # shellcheck source=/dev/null 5 | [[ -f ".env" ]] && source .env set 6 | set +o allexport 7 | 8 | APP_CREATE_PR_ROOT_DIR=${APP_CREATE_PR_ROOT_DIR:-"$(git rev-parse --show-toplevel)"} \ 9 | || error_and_exit "This directory is not a git repository" 10 | PR_LINK_PREFIX_TEXT=${PR_LINK_PREFIX_TEXT:-""} 11 | PR_TICKET_LINK_PREFIX=${PR_TICKET_LINK_PREFIX:-""} 12 | PR_TEMPLATE_PATH=${PR_TEMPLATE_PATH:-".github/PULL_REQUEST_TEMPLATE.md"} 13 | PR_TEMPLATE="$APP_CREATE_PR_ROOT_DIR/$PR_TEMPLATE_PATH" 14 | PR_TITLE_TEMPLATE=${PR_TITLE_TEMPLATE:-"{{TICKET_KEY}}-{{TICKET_NUMBER}} {{PR_TITLE}}"} 15 | PR_TITLE_REMOVE_PREFIX=${PR_TITLE_REMOVE_PREFIX:-"be,fe"} 16 | PR_ASSIGNEE=${PR_ASSIGNEE:-${ASSIGNEE:-"@me"}} 17 | PR_REVIEWER=${PR_REVIEWER:-${REVIEWER:-""}} 18 | TARGET_BRANCH=${TARGET_BRANCH:-"main"} 19 | CURRENT_BRANCH=${CURRENT_BRANCH:-"$(git rev-parse --abbrev-ref HEAD 2>/dev/null)"} \ 20 | || error_and_exit "Failed to get the current branch name." 21 | 22 | REMOTE_URL=${REMOTE_URL:-"$(git config --get remote.origin.url)"} 23 | if [[ "$REMOTE_URL" == *"github.com"* ]]; then 24 | PR_USING_CLIENT="github" 25 | elif [[ "$REMOTE_URL" == *"gitlab.com"* ]]; then 26 | PR_USING_CLIENT="gitlab" 27 | else 28 | echo "Unsupported $REMOTE_URL. Please submit a PR or an issue - so we can work on it." 29 | exit 30 | fi 31 | 32 | export REMOTE_URL 33 | export PR_USING_CLIENT 34 | export PR_TITLE_TEMPLATE 35 | export PR_TITLE_REMOVE_PREFIX 36 | export PR_TEMPLATE 37 | export PR_ASSIGNEE 38 | export PR_REVIEWER 39 | export TARGET_BRANCH 40 | export CURRENT_BRANCH 41 | -------------------------------------------------------------------------------- /src/helpers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o allexport 3 | 4 | function helpers::generate_branch_name() { 5 | local input="$1" 6 | local prefix="${2:-feat}" 7 | 8 | local lowercase 9 | lowercase=$(echo "$input" | tr '[:upper:]' '[:lower:]') 10 | 11 | local branch_name 12 | branch_name=$(echo "$lowercase" | tr ' ' '-') 13 | 14 | echo "${prefix}/${branch_name}" 15 | } 16 | -------------------------------------------------------------------------------- /src/main.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | function main::create_pr() { 5 | validate::target_branch_exists 6 | validate::branch_has_commits 7 | validate::current_branch_is_not_target 8 | 9 | # Push the current branch 10 | if ! git push -u origin "$CURRENT_BRANCH"; then 11 | error_and_exit "Failed to push the current branch to the remote repository."\ 12 | "Please check your git remote settings." 13 | fi 14 | 15 | if [[ "$PR_USING_CLIENT" == "gitlab" ]]; then 16 | main::create_pr_gitlab 17 | else 18 | main::create_pr_github 19 | fi 20 | } 21 | 22 | function main::create_pr_gitlab() { 23 | validate::glab_cli_is_installed 24 | 25 | local glab_command=( 26 | glab mr create 27 | --title "$PR_TITLE" 28 | --target-branch "$TARGET_BRANCH" 29 | --source-branch "$CURRENT_BRANCH" 30 | --assignee "$PR_ASSIGNEE" 31 | --reviewer "$PR_REVIEWER" 32 | --label "$PR_LABEL" 33 | --description "$PR_BODY" 34 | ) 35 | 36 | if [[ ${#EXTRA_ARGS[@]} -gt 0 ]]; then 37 | glab_command+=("${EXTRA_ARGS[@]}") 38 | fi 39 | 40 | if ! "${glab_command[@]}"; then 41 | error_and_exit "Failed to create the Merge Request." \ 42 | "Ensure you have the correct permissions and the repository is properly configured." 43 | fi 44 | } 45 | 46 | function main::create_pr_github() { 47 | validate::gh_cli_is_installed 48 | 49 | local gh_command=( 50 | gh pr create 51 | --title "$PR_TITLE" 52 | --base "$TARGET_BRANCH" 53 | --head "$CURRENT_BRANCH" 54 | --assignee "$PR_ASSIGNEE" 55 | --reviewer "$PR_REVIEWER" 56 | --label "$PR_LABEL" 57 | --body "$PR_BODY" 58 | ) 59 | 60 | if [[ ${#EXTRA_ARGS[@]} -gt 0 ]]; then 61 | gh_command+=("${EXTRA_ARGS[@]}") 62 | fi 63 | 64 | if ! "${gh_command[@]}"; then 65 | error_and_exit "Failed to create the Pull Request." \ 66 | "Ensure you have the correct permissions and the repository is properly configured." 67 | fi 68 | } 69 | -------------------------------------------------------------------------------- /src/pr_body.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o allexport 3 | 4 | _CURRENT_DIR="$(dirname "${BASH_SOURCE[0]}")" 5 | # shellcheck disable=SC1091 6 | [ -f "$_CURRENT_DIR/pr_ticket.sh" ] && source "$_CURRENT_DIR/pr_ticket.sh" 7 | 8 | # shellcheck disable=SC2001 9 | function pr_body() { 10 | local branch_name=$1 11 | local pr_template=$2 12 | 13 | if [ -z "$pr_template" ]; then 14 | echo "PR_TEMPLATE is empty; therefore not a valid path." 15 | return 16 | fi 17 | 18 | if [ ! -f "$pr_template" ]; then 19 | echo "$pr_template is not a valid template path." 20 | return 21 | fi 22 | 23 | local ticket_key 24 | ticket_key=$(pr_ticket::key "$branch_name") 25 | local ticket_number 26 | ticket_number=$(pr_ticket::number "$branch_name") 27 | local with_link=false 28 | if [[ -n "${PR_TICKET_LINK_PREFIX:-}" && -n "${ticket_number}" ]]; then 29 | with_link=true 30 | fi 31 | 32 | # {{TICKET_LINK}} 33 | local ticket_link="Nope" 34 | if [[ "$with_link" == true ]]; then 35 | if [[ -z "$ticket_key" ]]; then 36 | ticket_link="${PR_TICKET_LINK_PREFIX}${ticket_number}" 37 | else 38 | ticket_link="${PR_TICKET_LINK_PREFIX}${ticket_key}-${ticket_number}" 39 | fi 40 | ticket_link="${PR_LINK_PREFIX_TEXT}${ticket_link}" 41 | fi 42 | 43 | local result 44 | result=$(perl -pe 's//{{ $1 }}/g' "$pr_template") 45 | result=$(echo "$result" | sed "s|{{[[:space:]]*TICKET_LINK[[:space:]]*}}|$ticket_link|g") 46 | 47 | # {{BACKGROUND}} 48 | local background_text="Provide some context to the reviewer before jumping in the code." 49 | if [[ "$with_link" == true ]]; then 50 | background_text="Details in the ticket." 51 | fi 52 | result=$(echo "$result" | sed "s|{{[[:space:]]*BACKGROUND[[:space:]]*}}|$background_text|g") 53 | 54 | # Trim leading and trailing whitespace from result 55 | result=$(echo "$result" | awk '{$1=$1};1') 56 | 57 | echo "${result:-Description is currently empty}" 58 | } 59 | -------------------------------------------------------------------------------- /src/pr_label.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o allexport 3 | 4 | # Find the default label based on the branch prefix 5 | function pr_label() { 6 | local branch_name=$1 7 | local mapping=${2:-"feat|feature:enhancement;\ 8 | fix|bug|bugfix:bug;\ 9 | docs|documentation:documentation;\ 10 | default:enhancement"} 11 | # Remove empty spaces due to indentation 12 | mapping=${mapping// /} 13 | # Extract the prefix (the part before the first slash or dash) 14 | local prefix 15 | prefix=$(echo "$branch_name" | sed -E 's@^([^/-]+).*@\1@') 16 | # Default label 17 | local default_label="enhancement" 18 | 19 | # Loop through the mapping string to find a match 20 | IFS=';' # Split mapping entries by semicolon 21 | for entry in $mapping; do 22 | # Split each entry into keys and value 23 | IFS=':' read -r keys value <<< "$entry" 24 | 25 | # Check if the prefix matches any of the keys 26 | IFS='|' # Split keys by pipe symbol 27 | for key in $keys; do 28 | if [[ "$prefix" == "$key" ]]; then 29 | echo "$value" 30 | return 31 | fi 32 | done 33 | 34 | # Set the default label if found 35 | if [[ "$keys" == "default" ]]; then 36 | default_label="$value" 37 | fi 38 | done 39 | 40 | # Return the default label if no match is found 41 | echo "$default_label" 42 | } 43 | -------------------------------------------------------------------------------- /src/pr_ticket.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o allexport 3 | 4 | # $1 = branch_name 5 | function pr_ticket::number() { 6 | branch_name=$1 7 | 8 | # Remove optional prefix and split the branch name by hyphens 9 | stripped_branch=${branch_name#*/} 10 | # shellcheck disable=SC2206 11 | parts=(${stripped_branch//-/ }) 12 | 13 | # Check if the first or second part contains a number and print it; otherwise, print an empty string 14 | if [[ ${parts[0]} =~ ^[0-9]+$ ]]; then 15 | echo "${parts[0]}" 16 | elif [[ ${parts[1]} =~ ^[0-9]+$ ]]; then 17 | echo "${parts[1]}" 18 | else 19 | echo "" 20 | fi 21 | } 22 | 23 | # $1 = branch_name 24 | function pr_ticket::key() { 25 | branch_name=$1 26 | 27 | # Check if the branch name contains a '/' 28 | if [[ "$branch_name" == *"/"* ]]; then 29 | # Extract the part after the first '/' and process it 30 | branch_suffix="${branch_name#*/}" 31 | # Try to extract the pattern "KEY-NUMBER" and stop after the first occurrence 32 | ticket_key=$(echo "$branch_suffix" | grep -oE "[A-Za-z]+-[0-9]+" | head -n 1 | sed 's/-[0-9]*$//') 33 | 34 | # If no ticket key is found, ensure there's no ticket-like pattern and use the prefix if it's uppercase 35 | if [[ -z "$ticket_key" ]]; then 36 | first_part=$(echo "$branch_name" | cut -d'/' -f2 | grep -oE "^[A-Z]+") 37 | if [[ -n "$first_part" ]]; then 38 | ticket_key="$first_part" 39 | fi 40 | fi 41 | else 42 | # For branch names without '/' 43 | ticket_key=$(echo "$branch_name" | grep -oE "^[A-Za-z]+" | head -n 1) 44 | fi 45 | 46 | # If no ticket key is found, ensure there's no ticket-like pattern and return empty 47 | if [[ -z "$ticket_key" ]]; then 48 | if ! echo "$branch_name" | grep -qE "[A-Za-z]+-[0-9]+"; then 49 | echo "" 50 | return 51 | fi 52 | fi 53 | 54 | echo "$ticket_key" | tr '[:lower:]' '[:upper:]' 55 | } 56 | -------------------------------------------------------------------------------- /src/pr_title.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o allexport 3 | 4 | _CURRENT_DIR="$(dirname "${BASH_SOURCE[0]}")" 5 | # shellcheck disable=SC1091 6 | [ -f "$_CURRENT_DIR/pr_ticket.sh" ] && source "$_CURRENT_DIR/pr_ticket.sh" 7 | 8 | function pr_title() { 9 | local branch_name="$1" 10 | branch_name="${branch_name#*/}" 11 | local ticket_key 12 | ticket_key=$(pr_ticket::key "$branch_name") 13 | 14 | local ticket_number 15 | ticket_number=$(pr_ticket::number "$branch_name") 16 | 17 | if [[ -z "$ticket_key" || -z "$ticket_number" ]]; then 18 | pr_title::without_ticket "$branch_name" 19 | return 20 | fi 21 | 22 | local title 23 | title=$(echo "$branch_name" | cut -d'-' -f3- | tr '-' ' '| tr '_' ' ') 24 | title="$(echo "${title:0:1}" | tr '[:lower:]' '[:upper:]')${title:1}" 25 | 26 | # Normalize the template by removing spaces around placeholders 27 | local normalized_template 28 | normalized_template=$(echo "$PR_TITLE_TEMPLATE" | sed -E 's/\{\{[[:space:]]*([^[:space:]]+)[[:space:]]*\}\}/{{\1}}/g') 29 | 30 | # Replace placeholders with actual values 31 | local formatted 32 | formatted="${normalized_template//\{\{TICKET_KEY\}\}/$ticket_key}" 33 | formatted="${formatted//\{\{TICKET_NUMBER\}\}/$ticket_number}" 34 | 35 | local new_title="$title" 36 | 37 | if [[ -n "$PR_TITLE_REMOVE_PREFIX" ]]; then 38 | # Split PR_TITLE_REMOVE_PREFIX into an array 39 | IFS=',' read -ra prefixes <<< "$PR_TITLE_REMOVE_PREFIX" 40 | # Loop through each prefix and remove it from the start if it matches 41 | for prefix in "${prefixes[@]}"; do 42 | # shellcheck disable=SC2001 43 | new_title="$(echo "$new_title" | sed -e "s/^${prefix}//I")" 44 | done 45 | 46 | # Trim leading whitespace and capitalize the first letter 47 | new_title=$(echo "$new_title" \ 48 | | sed 's/^ *//' \ 49 | | awk '{ print toupper(substr($0,1,1)) tolower(substr($0,2)) }') 50 | fi 51 | 52 | formatted="${formatted//\{\{PR_TITLE\}\}/$new_title}" 53 | 54 | echo "$formatted" 55 | } 56 | 57 | function pr_title::without_ticket() { 58 | input="$1" 59 | # Remove leading digits followed by a hyphen (e.g., "27-") 60 | input="${input#[0-9]*-}" 61 | 62 | result=$(echo "$input" | awk ' 63 | { 64 | gsub(/_/, " ", $0) # Replace underscores with spaces 65 | for (i = 1; i <= NF; i++) { 66 | # Capitalize first letter and lowercase the rest 67 | $i = toupper(substr($i, 1, 1)) tolower(substr($i, 2)) 68 | } 69 | gsub(/-/, " ", $0) # Replace hyphens with spaces 70 | print 71 | }' | sed 's/[[:space:]]*$//') 72 | 73 | echo "$result" 74 | } 75 | -------------------------------------------------------------------------------- /src/validate.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -o allexport 3 | 4 | GH_CLI_INSTALLATION_URL="https://cli.github.com/" 5 | GLAB_CLI_INSTALLATION_URL="https://gitlab.com/gitlab-org/cli/" 6 | 7 | function error_and_exit() { 8 | echo "Error: $1" >&2 9 | exit 1 10 | } 11 | 12 | function validate::target_branch_exists() { 13 | if ! git show-ref --verify --quiet "refs/heads/$TARGET_BRANCH"; then 14 | error_and_exit "Base branch '$TARGET_BRANCH' does not exist. Check the base branch name or create it." 15 | fi 16 | } 17 | 18 | function validate::branch_has_commits() { 19 | if [ "$(git rev-list --count "$CURRENT_BRANCH")" -eq 0 ]; then 20 | error_and_exit "The current branch has no commits. Make sure the branch is not empty." 21 | fi 22 | } 23 | 24 | function validate::current_branch_is_not_target() { 25 | if [ "$CURRENT_BRANCH" = "$TARGET_BRANCH" ]; then 26 | error_and_exit "You are on the same branch as target -> $CURRENT_BRANCH" 27 | fi 28 | } 29 | 30 | function validate::gh_cli_is_installed() { 31 | if ! command -v gh &> /dev/null; then 32 | error_and_exit "gh CLI is not installed. Please install it from $GH_CLI_INSTALLATION_URL and try again." 33 | fi 34 | } 35 | 36 | function validate::glab_cli_is_installed() { 37 | if ! command -v glab &> /dev/null; then 38 | error_and_exit "glab CLI is not installed. Please install it from $GLAB_CLI_INSTALLATION_URL and try again." 39 | fi 40 | } 41 | -------------------------------------------------------------------------------- /tests/bootstrap.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | function current_dir() { 5 | dirname "${BASH_SOURCE[1]}" 6 | } 7 | 8 | function current_filename() { 9 | basename "${BASH_SOURCE[1]}" 10 | } 11 | 12 | export CREATE_PR_ROOT_DIR=. 13 | -------------------------------------------------------------------------------- /tests/e2e/create-pr_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function set_up() { 4 | export APP_CREATE_PR_ROOT_DIR=. 5 | export REMOTE_URL="git@github.com:Chemaclass/create-pr.git" 6 | export TARGET_BRANCH="main" 7 | export CURRENT_BRANCH="feat/ticket-123-my_branch-name" 8 | export PR_TEMPLATE_PATH=".github/PULL_REQUEST_TEMPLATE.md" 9 | export PR_TICKET_LINK_PREFIX="https://github.com/Chemaclass/create-pr/issues/" 10 | export PR_LINK_PREFIX_TEXT="Closes: " 11 | export PR_TITLE_TEMPLATE="{{TICKET_KEY}}-{{TICKET_NUMBER}} {{PR_TITLE}}" 12 | export PR_LABEL_MAPPING="default:enhancement;" 13 | 14 | SCRIPT="$CREATE_PR_ROOT_DIR/create-pr" 15 | } 16 | 17 | function test_success() { 18 | spy git 19 | spy gh 20 | 21 | assert_match_snapshot "$($SCRIPT)" 22 | } 23 | 24 | function test_script_with_dry_run() { 25 | spy git 26 | spy gh 27 | 28 | assert_match_snapshot "$($SCRIPT --dry-run)" 29 | } 30 | 31 | function test_script_with_dry_run_and_extra_args() { 32 | spy git 33 | spy gh 34 | export TARGET_BRANCH=prod 35 | 36 | assert_match_snapshot "$($SCRIPT --dry-run --draft "--title \"Pull request title\"")" 37 | } 38 | -------------------------------------------------------------------------------- /tests/e2e/snapshots/create_pr_test_sh.test_script_with_dry_run.snapshot: -------------------------------------------------------------------------------- 1 | EXTRA_ARGS: empty 2 | REMOTE_URL: git@github.com:Chemaclass/create-pr.git 3 | TARGET_BRANCH: main 4 | CURRENT_BRANCH: feat/ticket-123-my_branch-name 5 | PR_USING_CLIENT: github 6 | PR_TEMPLATE: ./.github/PULL_REQUEST_TEMPLATE.md 7 | PR_LABEL: enhancement 8 | PR_TITLE: TICKET-123 My branch name 9 | PR_BODY: 10 | ### 🔗 Ticket 11 | 12 | Closes: https://github.com/Chemaclass/create-pr/issues/TICKET-123 13 | 14 | ## 🤔 Background 15 | 16 | Details in the ticket. 17 | 18 | ## 💡 Goal 19 | 20 | 21 | 22 | ## 🔖 Changes 23 | 24 | 25 | 26 | ## 🖼️ Screenshots 27 | 28 | 29 | 30 | #### BEFORE 31 | 32 | #### AFTER 33 | -------------------------------------------------------------------------------- /tests/e2e/snapshots/create_pr_test_sh.test_script_with_dry_run_and_extra_args.snapshot: -------------------------------------------------------------------------------- 1 | EXTRA_ARGS: --draft 2 | EXTRA_ARGS: --title "Pull request title" 3 | REMOTE_URL: git@github.com:Chemaclass/create-pr.git 4 | TARGET_BRANCH: prod 5 | CURRENT_BRANCH: feat/ticket-123-my_branch-name 6 | PR_USING_CLIENT: github 7 | PR_TEMPLATE: ./.github/PULL_REQUEST_TEMPLATE.md 8 | PR_LABEL: enhancement 9 | PR_TITLE: TICKET-123 My branch name 10 | PR_BODY: 11 | ### 🔗 Ticket 12 | 13 | Closes: https://github.com/Chemaclass/create-pr/issues/TICKET-123 14 | 15 | ## 🤔 Background 16 | 17 | Details in the ticket. 18 | 19 | ## 💡 Goal 20 | 21 | 22 | 23 | ## 🔖 Changes 24 | 25 | 26 | 27 | ## 🖼️ Screenshots 28 | 29 | 30 | 31 | #### BEFORE 32 | 33 | #### AFTER 34 | -------------------------------------------------------------------------------- /tests/e2e/snapshots/create_pr_test_sh.test_success.snapshot: -------------------------------------------------------------------------------- 1 | Script finished successfully. 2 | -------------------------------------------------------------------------------- /tests/unit/helpers_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function set_up() { 4 | source "$CREATE_PR_ROOT_DIR/src/helpers.sh" 5 | } 6 | 7 | function test_helpers_generate_branch_name_default_prefix() { 8 | actual=$(helpers::generate_branch_name "Add new feature") 9 | 10 | assert_same "feat/add-new-feature" "$actual" 11 | } 12 | 13 | function test_helpers_generate_branch_name_with_prefix() { 14 | actual=$(helpers::generate_branch_name "Broken feature" "bug") 15 | 16 | assert_same "bug/broken-feature" "$actual" 17 | } 18 | -------------------------------------------------------------------------------- /tests/unit/pr_body_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=SC2155 4 | 5 | function set_up() { 6 | PR_LINK_PREFIX_TEXT="" 7 | 8 | source "$CREATE_PR_ROOT_DIR/src/pr_body.sh" 9 | } 10 | 11 | function test_pr_body_link_with_PR_TICKET_LINK_PREFIX() { 12 | export PR_TICKET_LINK_PREFIX=https://your-ticket-system.com/ 13 | 14 | local actual=$(pr_body "TICKET-123-my_branch-with-1-number"\ 15 | "$CREATE_PR_ROOT_DIR/.github/PULL_REQUEST_TEMPLATE.md") 16 | 17 | assert_match_snapshot "$actual" 18 | } 19 | 20 | function test_pr_body_link_without_ticket_link_prefix() { 21 | export PR_TICKET_LINK_PREFIX= 22 | 23 | local actual=$(pr_body "TICKET-123-my_branch"\ 24 | "$CREATE_PR_ROOT_DIR/.github/PULL_REQUEST_TEMPLATE.md") 25 | 26 | assert_match_snapshot "$actual" 27 | } 28 | 29 | function test_pr_body_link_without_ticket_key() { 30 | export PR_TICKET_LINK_PREFIX=https://your-ticket-system.com/ 31 | 32 | local actual=$(pr_body "123-my_branch"\ 33 | "$CREATE_PR_ROOT_DIR/.github/PULL_REQUEST_TEMPLATE.md") 34 | 35 | assert_match_snapshot "$actual" 36 | } 37 | 38 | function test_pr_body_link_with_pr_ticket_prefix_text() { 39 | export PR_TICKET_LINK_PREFIX=https://your-ticket-system.com/ 40 | export PR_LINK_PREFIX_TEXT="Fixes: " 41 | 42 | local actual=$(pr_body "123-my_branch"\ 43 | "$CREATE_PR_ROOT_DIR/.github/PULL_REQUEST_TEMPLATE.md") 44 | 45 | assert_match_snapshot "$actual" 46 | } 47 | 48 | function test_pr_body_link_without_ticket_number() { 49 | export PR_TICKET_LINK_PREFIX=https://your-ticket-system.com/ 50 | 51 | local actual=$(pr_body "TICKET-my_branch"\ 52 | "$CREATE_PR_ROOT_DIR/.github/PULL_REQUEST_TEMPLATE.md") 53 | 54 | assert_match_snapshot "$actual" 55 | } 56 | 57 | function test_pr_body_link_without_pr_template() { 58 | export PR_TICKET_LINK_PREFIX=https://your-ticket-system.com/ 59 | 60 | local actual=$(pr_body "TICKET-123-my_branch" "") 61 | 62 | assert_same "PR_TEMPLATE is empty; therefore not a valid path." "$actual" 63 | } 64 | 65 | function test_pr_body_link_without_valid_pr_template_path() { 66 | export PR_TICKET_LINK_PREFIX=https://your-ticket-system.com/ 67 | 68 | local actual=$(pr_body "TICKET-123-my_branch" "/non-existing-path") 69 | 70 | assert_same "/non-existing-path is not a valid template path." "$actual" 71 | } 72 | 73 | function test_pr_body_link_with_branch_with_numbers_no_prefix() { 74 | export PR_TICKET_LINK_PREFIX=https://your-ticket-system.com/ 75 | 76 | local actual=$(pr_body "TICKET-123-my-4-th-branch"\ 77 | "$CREATE_PR_ROOT_DIR/.github/PULL_REQUEST_TEMPLATE.md") 78 | 79 | assert_match_snapshot "$actual" 80 | } 81 | 82 | function test_pr_body_link_with_branch_with_numbers_and_prefix() { 83 | export PR_TICKET_LINK_PREFIX=https://your-ticket-system.com/ 84 | 85 | local actual=$(pr_body "feat/TICKET-123-my-4-th-branch"\ 86 | "$CREATE_PR_ROOT_DIR/.github/PULL_REQUEST_TEMPLATE.md") 87 | 88 | assert_match_snapshot "$actual" 89 | } 90 | 91 | function test_pr_body_background_without_link() { 92 | export PR_TICKET_LINK_PREFIX= 93 | 94 | local actual=$(pr_body "feat/TICKET-123-my-4-th-branch"\ 95 | "$CREATE_PR_ROOT_DIR/.github/PULL_REQUEST_TEMPLATE.md") 96 | 97 | assert_match_snapshot "$actual" 98 | } 99 | -------------------------------------------------------------------------------- /tests/unit/pr_label_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=SC2046 4 | # shellcheck disable=SC2155 5 | 6 | function set_up() { 7 | source "$CREATE_PR_ROOT_DIR/src/pr_label.sh" 8 | } 9 | 10 | function test_pr_label_default() { 11 | assert_same "enhancement" $(pr_label "TICKET-123-my-branch_name") 12 | } 13 | 14 | function test_pr_label_default_enhancement() { 15 | assert_same "enhancement" $(pr_label "feat/TICKET-123-my-branch_name") 16 | assert_same "enhancement" $(pr_label "feature/TICKET-123-my-branch_name") 17 | } 18 | 19 | function test_pr_label_default_bug() { 20 | assert_same "bug" $(pr_label "fix/TICKET-123-my-branch_name") 21 | assert_same "bug" $(pr_label "bug/TICKET-123-my-branch_name") 22 | assert_same "bug" $(pr_label "bugfix/TICKET-123-my-branch_name") 23 | } 24 | 25 | function test_pr_label_default_documentation() { 26 | assert_same "documentation" $(pr_label "docs/TICKET-123-my-branch_name") 27 | assert_same "documentation" $(pr_label "documentation/TICKET-123-my-branch_name") 28 | } 29 | 30 | function test_pr_label_default_custom_mapping() { 31 | local mapping="default:extra; feat|feature:enhancement; fix|bug|bugfix:bug" 32 | 33 | assert_same "enhancement" $(pr_label "feat/TICKET-123-my-branch_name" "$mapping") 34 | assert_same "enhancement" $(pr_label "feature/TICKET-123-my-branch_name" "$mapping") 35 | 36 | assert_same "bug" $(pr_label "fix/TICKET-123-my-branch_name" "$mapping") 37 | assert_same "bug" $(pr_label "bug/TICKET-123-my-branch_name" "$mapping") 38 | assert_same "bug" $(pr_label "bugfix/TICKET-123-my-branch_name" "$mapping") 39 | 40 | assert_same "extra" $(pr_label "unknown/TICKET-123-my-branch_name" "$mapping") 41 | } 42 | -------------------------------------------------------------------------------- /tests/unit/pr_ticket_key_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # shellcheck disable=SC2046 4 | # shellcheck disable=SC2155 5 | 6 | function set_up() { 7 | source "$CREATE_PR_ROOT_DIR/src/pr_ticket.sh" 8 | } 9 | 10 | function test_pr_ticket_key_default() { 11 | assert_same "TICKET" "$(pr_ticket::key "TICKET-123-my-branch_name")" 12 | } 13 | 14 | function test_pr_ticket_key_with_prefix() { 15 | assert_same "TICKET" "$(pr_ticket::key "feat/TICKET-123-my-branch_name")" 16 | } 17 | 18 | function test_pr_ticket_key_with_prefix_and_number_in_branch_name() { 19 | assert_same "TICKET" "$(pr_ticket::key "feat/TICKET-123-my-5-th-branch_name")" 20 | } 21 | 22 | function test_pr_ticket_key_lower_upper_case() { 23 | assert_same "TICKET" "$(pr_ticket::key "Ticket-123-my-branch_name")" 24 | } 25 | 26 | function test_pr_ticket_key_without_number_and_no_branch_prefix() { 27 | assert_same "TICKET" "$(pr_ticket::key "TICKET-my-branch_name")" 28 | } 29 | 30 | function test_pr_ticket_key_with_numbers_in_branch_name() { 31 | assert_same "TICKET" "$(pr_ticket::key "TICKET-my-1-st-branch_name")" 32 | } 33 | 34 | function test_pr_ticket_key_without_number_but_branch_prefix() { 35 | assert_same "TICKET" "$(pr_ticket::key "feat/TICKET-my-branch_name")" 36 | } 37 | -------------------------------------------------------------------------------- /tests/unit/pr_ticket_number_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2034 3 | 4 | function set_up() { 5 | source "$CREATE_PR_ROOT_DIR/src/pr_ticket.sh" 6 | } 7 | 8 | function test_pr_ticket_number_start_with_number() { 9 | assert_same "123" "$(pr_ticket::number "123-TICKET-my-branch_name")" 10 | } 11 | 12 | function test_pr_ticket_number_after_ticket_key() { 13 | assert_same "123" "$(pr_ticket::number "TICKET-123-my-branch_name")" 14 | } 15 | 16 | function test_pr_ticket_number_with_prefix() { 17 | assert_same "123" "$(pr_ticket::number "feat/TICKET-123-my-branch_name")" 18 | } 19 | 20 | function test_pr_ticket_number_without_prefix_but_prefix() { 21 | assert_same "123" "$(pr_ticket::number "feat/123-my-branch_name")" 22 | } 23 | 24 | function test_pr_ticket_number_with_prefix_and_number_in_branch_name() { 25 | assert_same "123" "$(pr_ticket::number "feat/TICKET-123-my-4-th-branch_name")" 26 | } 27 | 28 | function test_pr_ticket_number_lower_upper_case() { 29 | assert_same "123" "$(pr_ticket::number "Ticket-123-my-branch_name")" 30 | } 31 | 32 | function test_pr_ticket_number_with_numbers_in_branch_name() { 33 | assert_same "123" "$(pr_ticket::number "Ticket-123-my-2-nd-branch_name")" 34 | } 35 | 36 | function test_pr_ticket_number_without_number() { 37 | assert_empty "$(pr_ticket::number "creating-my-branch_name")" 38 | } 39 | 40 | function test_pr_ticket_number_with_number_not_in_first_nor_second_pos() { 41 | assert_empty "$(pr_ticket::number "creating-my-2-nd-branch_name")" 42 | } 43 | 44 | function test_pr_ticket_number_with_prefix_and_number_not_in_first_nor_second_pos() { 45 | assert_empty "$(pr_ticket::number "feat/creating-my-2-nd-branch_name")" 46 | } 47 | -------------------------------------------------------------------------------- /tests/unit/pr_title_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # shellcheck disable=SC2034 3 | 4 | function set_up() { 5 | export PR_TITLE_TEMPLATE="{{TICKET_KEY}}-{{TICKET_NUMBER}} {{PR_TITLE}}" 6 | 7 | source "$CREATE_PR_ROOT_DIR/src/pr_title.sh" 8 | } 9 | 10 | # data_provider provider_remove_custom_title_prefix 11 | function test_pr_title_with_ticket_key_remove_custom_prefix() { 12 | local remove_prefix="$1" 13 | local branch_name="$2" 14 | 15 | export PR_TITLE_REMOVE_PREFIX="$remove_prefix" 16 | actual=$(pr_title "$branch_name") 17 | 18 | assert_same "TICKET-0000 My new 4th feature" "$actual" 19 | } 20 | 21 | function provider_remove_custom_title_prefix() { 22 | echo "be" "feat/TICKET-0000-be-my-new-4th-feature" 23 | echo "BE" "feat/TICKET-0000-be-my-new-4th-feature" 24 | echo "fe" "feat/TICKET-0000-fe-my-new-4th-feature" 25 | echo "Fe" "feat/TICKET-0000-fe-my-new-4th-feature" 26 | echo "fe,be" "feat/TICKET-0000-fe-my-new-4th-feature" 27 | echo "fe,be" "feat/TICKET-0000-be-my-new-4th-feature" 28 | } 29 | 30 | function test_pr_title_no_template() { 31 | export PR_TITLE_TEMPLATE= 32 | actual=$(pr_title "feat/TICKET-0000-my-new-1st-feature") 33 | 34 | assert_same "" "$actual" 35 | } 36 | 37 | function test_pr_title_custom_template_with_ticket_key_number_title() { 38 | export PR_TITLE_TEMPLATE='[{{TICKET_NUMBER}}-{{ TICKET_KEY }}]: {{ PR_TITLE }} 🏗️' 39 | actual=$(pr_title "feat/TICKET-0000-my-new-2nd-feature") 40 | 41 | assert_same "[0000-TICKET]: My new 2nd feature 🏗️" "$actual" 42 | } 43 | 44 | function test_pr_title_custom_template_with_ticket_number_title() { 45 | skip 46 | export PR_TITLE_TEMPLATE='[{{TICKET_NUMBER}}]: {{ PR_TITLE }} 🏗️' 47 | actual=$(pr_title "feat/123-my-new-2nd-feature") 48 | # assert_same "[123]: My new 2nd feature 🏗️" "$actual" 49 | } 50 | 51 | function test_pr_title_custom_template_with_ticket_key_title() { 52 | skip 53 | export PR_TITLE_TEMPLATE='[{{TICKET_KEY}}]: {{ PR_TITLE }} 🏗️' 54 | actual=$(pr_title "feat/KEY-my-new-2nd-feature") 55 | # assert_same "[KEY]: My new 2nd feature 🏗️" "$actual" 56 | } 57 | 58 | function test_pr_title_with_underscores_no_prefix() { 59 | actual=$(pr_title "add_pr_create_script") 60 | 61 | assert_same "Add Pr Create Script" "$actual" 62 | } 63 | 64 | function test_pr_title_with_underscores_with_prefix() { 65 | actual=$(pr_title "prefix/add_pr_create_script") 66 | 67 | assert_same "Add Pr Create Script" "$actual" 68 | } 69 | 70 | function test_pr_title_with_prefix_and_ticket_number() { 71 | actual=$(pr_title "prefix/27-add-pr-3-create_script") 72 | 73 | assert_same "Add pr 3 create Script" "$actual" 74 | } 75 | 76 | function test_pr_title_ticket_number_only_at_the_beginning() { 77 | actual=$(pr_title "prefix/add-pr-3-create_script") 78 | 79 | assert_same "Add pr 3 create Script" "$actual" 80 | } 81 | 82 | function test_pr_title_with_prefix_and_ticket_key() { 83 | actual="$(pr_title "feat/TICKET-my-branch_name")" 84 | 85 | assert_same "Ticket my branch Name" "$actual" 86 | } 87 | 88 | function test_pr_title_without_prefix_but_ticket() { 89 | actual=$(pr_title "TICKET-0000-add_pr_create_script") 90 | 91 | assert_same "TICKET-0000 Add pr create script" "$actual" 92 | } 93 | 94 | function test_pr_title_without_prefix_but_ticket_number() { 95 | actual=$(pr_title "0000-add_pr_create_script") 96 | 97 | assert_same "Add Pr Create Script" "$actual" 98 | } 99 | 100 | function test_pr_title_without_ticket() { 101 | actual=$(pr_title "add-pr-create_script") 102 | 103 | assert_same "Add pr create Script" "$actual" 104 | } 105 | 106 | # data_provider provider_no_prefix 107 | function test_pr_title_remove_prefix() { 108 | local prefix=$1 109 | actual=$(pr_title "$prefix/TICKET-0000-my-new-3-feature") 110 | 111 | assert_same "TICKET-0000 My new 3 feature" "$actual" 112 | } 113 | 114 | function provider_no_prefix() { 115 | echo "feat" 116 | echo "feature" 117 | echo "bug" 118 | } 119 | -------------------------------------------------------------------------------- /tests/unit/snapshots/pr_body_test_sh.test_pr_body_background_without_link.snapshot: -------------------------------------------------------------------------------- 1 | ### 🔗 Ticket 2 | 3 | Nope 4 | 5 | ## 🤔 Background 6 | 7 | Provide some context to the reviewer before jumping in the code. 8 | 9 | ## 💡 Goal 10 | 11 | 12 | 13 | ## 🔖 Changes 14 | 15 | 16 | 17 | ## 🖼️ Screenshots 18 | 19 | 20 | 21 | #### BEFORE 22 | 23 | #### AFTER 24 | -------------------------------------------------------------------------------- /tests/unit/snapshots/pr_body_test_sh.test_pr_body_link_with_PR_TICKET_LINK_PREFIX.snapshot: -------------------------------------------------------------------------------- 1 | ### 🔗 Ticket 2 | 3 | https://your-ticket-system.com/TICKET-123 4 | 5 | ## 🤔 Background 6 | 7 | Details in the ticket. 8 | 9 | ## 💡 Goal 10 | 11 | 12 | 13 | ## 🔖 Changes 14 | 15 | 16 | 17 | ## 🖼️ Screenshots 18 | 19 | 20 | 21 | #### BEFORE 22 | 23 | #### AFTER 24 | -------------------------------------------------------------------------------- /tests/unit/snapshots/pr_body_test_sh.test_pr_body_link_with_branch_with_numbers_and_prefix.snapshot: -------------------------------------------------------------------------------- 1 | ### 🔗 Ticket 2 | 3 | https://your-ticket-system.com/TICKET-123 4 | 5 | ## 🤔 Background 6 | 7 | Details in the ticket. 8 | 9 | ## 💡 Goal 10 | 11 | 12 | 13 | ## 🔖 Changes 14 | 15 | 16 | 17 | ## 🖼️ Screenshots 18 | 19 | 20 | 21 | #### BEFORE 22 | 23 | #### AFTER 24 | -------------------------------------------------------------------------------- /tests/unit/snapshots/pr_body_test_sh.test_pr_body_link_with_branch_with_numbers_no_prefix.snapshot: -------------------------------------------------------------------------------- 1 | ### 🔗 Ticket 2 | 3 | https://your-ticket-system.com/TICKET-123 4 | 5 | ## 🤔 Background 6 | 7 | Details in the ticket. 8 | 9 | ## 💡 Goal 10 | 11 | 12 | 13 | ## 🔖 Changes 14 | 15 | 16 | 17 | ## 🖼️ Screenshots 18 | 19 | 20 | 21 | #### BEFORE 22 | 23 | #### AFTER 24 | -------------------------------------------------------------------------------- /tests/unit/snapshots/pr_body_test_sh.test_pr_body_link_with_pr_ticket_prefix_text.snapshot: -------------------------------------------------------------------------------- 1 | ### 🔗 Ticket 2 | 3 | Fixes: https://your-ticket-system.com/123 4 | 5 | ## 🤔 Background 6 | 7 | Details in the ticket. 8 | 9 | ## 💡 Goal 10 | 11 | 12 | 13 | ## 🔖 Changes 14 | 15 | 16 | 17 | ## 🖼️ Screenshots 18 | 19 | 20 | 21 | #### BEFORE 22 | 23 | #### AFTER 24 | -------------------------------------------------------------------------------- /tests/unit/snapshots/pr_body_test_sh.test_pr_body_link_without_ticket_key.snapshot: -------------------------------------------------------------------------------- 1 | ### 🔗 Ticket 2 | 3 | https://your-ticket-system.com/123 4 | 5 | ## 🤔 Background 6 | 7 | Details in the ticket. 8 | 9 | ## 💡 Goal 10 | 11 | 12 | 13 | ## 🔖 Changes 14 | 15 | 16 | 17 | ## 🖼️ Screenshots 18 | 19 | 20 | 21 | #### BEFORE 22 | 23 | #### AFTER 24 | -------------------------------------------------------------------------------- /tests/unit/snapshots/pr_body_test_sh.test_pr_body_link_without_ticket_link_prefix.snapshot: -------------------------------------------------------------------------------- 1 | ### 🔗 Ticket 2 | 3 | Nope 4 | 5 | ## 🤔 Background 6 | 7 | Provide some context to the reviewer before jumping in the code. 8 | 9 | ## 💡 Goal 10 | 11 | 12 | 13 | ## 🔖 Changes 14 | 15 | 16 | 17 | ## 🖼️ Screenshots 18 | 19 | 20 | 21 | #### BEFORE 22 | 23 | #### AFTER 24 | -------------------------------------------------------------------------------- /tests/unit/snapshots/pr_body_test_sh.test_pr_body_link_without_ticket_number.snapshot: -------------------------------------------------------------------------------- 1 | ### 🔗 Ticket 2 | 3 | Nope 4 | 5 | ## 🤔 Background 6 | 7 | Provide some context to the reviewer before jumping in the code. 8 | 9 | ## 💡 Goal 10 | 11 | 12 | 13 | ## 🔖 Changes 14 | 15 | 16 | 17 | ## 🖼️ Screenshots 18 | 19 | 20 | 21 | #### BEFORE 22 | 23 | #### AFTER 24 | -------------------------------------------------------------------------------- /tests/unit/validate_test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function set_up() { 4 | source "$CREATE_PR_ROOT_DIR/src/validate.sh" 5 | } 6 | 7 | function test_validate_target_branch_exists_uses_target_branch_variable() { 8 | export TARGET_BRANCH="non-existent-branch" 9 | export CURRENT_BRANCH="some-branch" 10 | local output 11 | output=$(validate::target_branch_exists 2>&1 >/dev/null || true) 12 | assert_same "Error: Base branch 'non-existent-branch' does not exist. Check the base branch name or create it."\ 13 | "$output" 14 | } 15 | --------------------------------------------------------------------------------