From 22f70bb44c4a3bd08cab97ad4cf434b476051a93 Mon Sep 17 00:00:00 2001 From: BLACKBOX Agent Date: Fri, 7 Nov 2025 23:41:12 +0000 Subject: [PATCH] feat(pr): allow branch name derivation from base branch --- README.md | 48 +++++++++++++++++++++++++++++++++++++- action.yml | 5 +++- dist/index.js | 8 +++++++ src/create-pull-request.ts | 10 ++++++++ 4 files changed, 69 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 396c35d418..9ebae5bcc6 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ All inputs are **optional**. If not set, sensible defaults will be used. | `committer` | The committer name and email address in the format `Display Name `. Defaults to the GitHub Actions bot user on github.com. | `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>` | | `author` | The author name and email address in the format `Display Name `. Defaults to the user who triggered the workflow run. | `${{ github.actor }} <${{ github.actor_id }}+${{ github.actor }}@users.noreply.github.com>` | | `signoff` | Add [`Signed-off-by`](https://git-scm.com/docs/git-commit#Documentation/git-commit.txt---signoff) line by the committer at the end of the commit log message. | `false` | -| `branch` | The pull request branch name. | `create-pull-request/patch` | +| `branch` | The pull request branch name. Supports `${base}` placeholder. See [branch](#branch). | `create-pull-request/patch` | | `delete-branch` | Delete the `branch` if it doesn't have an active pull request associated with it. See [delete-branch](#delete-branch). | `false` | | `branch-suffix` | The branch suffix type when using the alternative branching strategy. Valid values are `random`, `timestamp` and `short-commit-hash`. See [Alternative strategy](#alternative-strategy---always-create-a-new-pull-request-branch) for details. | | | `base` | Sets the pull request base branch. | Defaults to the branch checked out in the workflow. | @@ -102,6 +102,52 @@ The action first creates a branch, and then creates a pull request for the branc For some rare use cases it can be useful, or even necessary, to use different tokens for these operations. It is not advisable to use this input unless you know you need to. +#### branch + +The `branch` input supports a placeholder `${base}` that will be replaced with the base branch name. This is useful for "namespacing" pull requests to the branch they target. + +For example, if you want to create separate pull request branches for different base branches: + +```yml + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + branch: ${base}/create-pull-request/patch +``` + +When the base branch is `main`, the pull request branch will be `main/create-pull-request/patch`. +When the base branch is `develop`, the pull request branch will be `develop/create-pull-request/patch`. + +This is particularly useful for code generation workflows that run on multiple branches: + +```yml +name: Code Generation +on: + push: + branches: + - main + - develop + - 'feature/**' + +jobs: + generate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run code generation + run: npm run generate + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + branch: ${base}/codegen + title: '[Codegen] Update generated code for ${base}' + body: Automated code generation for the ${base} branch +``` + +**Note:** If the base branch name contains forward slashes (e.g., `feature/new-feature`), they will be replaced with hyphens in the branch name (e.g., `feature-new-feature/codegen`) to avoid issues with nested directory structures. + #### commit-message In addition to a message, the `commit-message` input can also be used to populate the commit description. Leave a single blank line between the message and description. diff --git a/action.yml b/action.yml index 9d28570cb5..ed30512564 100644 --- a/action.yml +++ b/action.yml @@ -34,7 +34,10 @@ inputs: description: 'Add `Signed-off-by` line by the committer at the end of the commit log message.' default: false branch: - description: 'The pull request branch name.' + description: > + The pull request branch name. + Supports the placeholder `${base}` which will be replaced with the base branch name. + For example, `${base}/patch` becomes `main/patch` when base is `main`. default: 'create-pull-request/patch' delete-branch: description: > diff --git a/dist/index.js b/dist/index.js index 75ae3c2f07..03f8a14d70 100644 --- a/dist/index.js +++ b/dist/index.js @@ -446,6 +446,14 @@ function createPullRequest(inputs) { } // If the base is not specified it is assumed to be the working base. const base = inputs.base ? inputs.base : workingBase; + // Substitute ${base} placeholder in branch name + if (inputs.branch.includes('${base}')) { + // Sanitize base branch name for use in branch name + // Replace forward slashes with hyphens to avoid nested directory issues + const sanitizedBase = base.replace(/\//g, '-'); + inputs.branch = inputs.branch.replace(/\$\{base\}/g, sanitizedBase); + core.info(`Branch name derived from base: '${inputs.branch}'`); + } // Throw an error if the base and branch are not different branches // of the 'origin' remote. An identically named branch in the `fork` // remote is perfectly fine. diff --git a/src/create-pull-request.ts b/src/create-pull-request.ts index 0ab4f8cc17..a14c3cebf1 100644 --- a/src/create-pull-request.ts +++ b/src/create-pull-request.ts @@ -113,6 +113,16 @@ export async function createPullRequest(inputs: Inputs): Promise { } // If the base is not specified it is assumed to be the working base. const base = inputs.base ? inputs.base : workingBase + + // Substitute ${base} placeholder in branch name + if (inputs.branch.includes('${base}')) { + // Sanitize base branch name for use in branch name + // Replace forward slashes with hyphens to avoid nested directory issues + const sanitizedBase = base.replace(/\//g, '-') + inputs.branch = inputs.branch.replace(/\$\{base\}/g, sanitizedBase) + core.info(`Branch name derived from base: '${inputs.branch}'`) + } + // Throw an error if the base and branch are not different branches // of the 'origin' remote. An identically named branch in the `fork` // remote is perfectly fine.