diff --git a/EXAMPLE_WORKFLOW.yml b/EXAMPLE_WORKFLOW.yml new file mode 100644 index 0000000000..8db261b07c --- /dev/null +++ b/EXAMPLE_WORKFLOW.yml @@ -0,0 +1,89 @@ +# Example workflow demonstrating the new feature from Issue #4111 +# This workflow shows how PR title and body are automatically defaulted from commit message + +name: Example - Auto PR Title/Body + +on: + workflow_dispatch: + +jobs: + create-pr-with-auto-defaults: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Make some changes + - name: Update files + run: | + echo "Updated content" > example.txt + date > timestamp.txt + + # Create PR with automatic title/body from commit + # The PR will have: + # Title: "Update AGP sources to the latest" + # Body: "_Auto-generated by `dump-sources` Github workflow._" + - name: Create Pull Request (Auto Title/Body) + uses: peter-evans/create-pull-request@v7 + with: + commit-message: | + Update AGP sources to the latest + + _Auto-generated by `dump-sources` Github workflow._ + branch: actions/dump-sources + delete-branch: true + # Note: title and body are NOT specified, so they will be + # automatically set from the commit message above + + create-pr-with-custom-values: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Make some changes + - name: Update files + run: | + echo "Updated content" > example.txt + + # Create PR with custom title/body (overrides auto-default) + # The PR will have the custom values below, NOT the commit message + - name: Create Pull Request (Custom Title/Body) + uses: peter-evans/create-pull-request@v7 + with: + commit-message: | + Update AGP sources to the latest + + _Auto-generated by `dump-sources` Github workflow._ + title: Custom PR Title + body: Custom PR Description + branch: actions/custom-pr + delete-branch: true + + create-pr-multiple-commits: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + # Make multiple commits + - name: Create multiple commits + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + + echo "First change" > file1.txt + git add file1.txt + git commit -m "First commit" + + echo "Second change" > file2.txt + git add file2.txt + git commit -m "Second commit" + + # Create PR - will use default title/body since there are multiple commits + # The PR will have: + # Title: "Changes by create-pull-request action" + # Body: "Automated changes by [create-pull-request](...) GitHub action" + - name: Create Pull Request (Multiple Commits) + uses: peter-evans/create-pull-request@v7 + with: + branch: actions/multiple-commits + delete-branch: true + # Note: Multiple commits means auto-default doesn't apply diff --git a/__test__/create-pull-request.unit.test.ts b/__test__/create-pull-request.unit.test.ts new file mode 100644 index 0000000000..5e16ed197b --- /dev/null +++ b/__test__/create-pull-request.unit.test.ts @@ -0,0 +1,218 @@ +import {Commit} from '../lib/git-command-manager' + +describe('create-pull-request PR title and body defaulting', () => { + const defaultTitle = 'Changes by create-pull-request action' + const defaultBody = + 'Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action' + + test('should use commit subject as title for single commit when title is default', () => { + const commit: Commit = { + sha: 'abc123', + tree: 'tree123', + parents: ['parent123'], + signed: false, + subject: 'Update AGP sources to the latest', + body: '_Auto-generated by `dump-sources` Github workflow._', + changes: [], + unparsedChanges: [] + } + + const branchCommits = [commit] + let title = defaultTitle + let body = defaultBody + + // Simulate the logic from create-pull-request.ts + if (branchCommits.length === 1) { + const singleCommit = branchCommits[0] + if (title === defaultTitle) { + title = singleCommit.subject + } + if (body === defaultBody) { + if (singleCommit.body && singleCommit.body.trim().length > 0) { + body = singleCommit.body + } + } + } + + expect(title).toEqual('Update AGP sources to the latest') + expect(body).toEqual('_Auto-generated by `dump-sources` Github workflow._') + }) + + test('should not override custom title and body', () => { + const commit: Commit = { + sha: 'abc123', + tree: 'tree123', + parents: ['parent123'], + signed: false, + subject: 'Update AGP sources to the latest', + body: '_Auto-generated by `dump-sources` Github workflow._', + changes: [], + unparsedChanges: [] + } + + const branchCommits = [commit] + let title = 'Custom Title' + let body = 'Custom Body' + + // Simulate the logic from create-pull-request.ts + if (branchCommits.length === 1) { + const singleCommit = branchCommits[0] + if (title === defaultTitle) { + title = singleCommit.subject + } + if (body === defaultBody) { + if (singleCommit.body && singleCommit.body.trim().length > 0) { + body = singleCommit.body + } + } + } + + expect(title).toEqual('Custom Title') + expect(body).toEqual('Custom Body') + }) + + test('should not default for multiple commits', () => { + const commit1: Commit = { + sha: 'abc123', + tree: 'tree123', + parents: ['parent123'], + signed: false, + subject: 'First commit', + body: 'First commit body', + changes: [], + unparsedChanges: [] + } + + const commit2: Commit = { + sha: 'def456', + tree: 'tree456', + parents: ['abc123'], + signed: false, + subject: 'Second commit', + body: 'Second commit body', + changes: [], + unparsedChanges: [] + } + + const branchCommits = [commit1, commit2] + let title = defaultTitle + let body = defaultBody + + // Simulate the logic from create-pull-request.ts + if (branchCommits.length === 1) { + const singleCommit = branchCommits[0] + if (title === defaultTitle) { + title = singleCommit.subject + } + if (body === defaultBody) { + if (singleCommit.body && singleCommit.body.trim().length > 0) { + body = singleCommit.body + } + } + } + + expect(title).toEqual(defaultTitle) + expect(body).toEqual(defaultBody) + }) + + test('should handle commit with empty body', () => { + const commit: Commit = { + sha: 'abc123', + tree: 'tree123', + parents: ['parent123'], + signed: false, + subject: 'Update files', + body: '', + changes: [], + unparsedChanges: [] + } + + const branchCommits = [commit] + let title = defaultTitle + let body = defaultBody + + // Simulate the logic from create-pull-request.ts + if (branchCommits.length === 1) { + const singleCommit = branchCommits[0] + if (title === defaultTitle) { + title = singleCommit.subject + } + if (body === defaultBody) { + if (singleCommit.body && singleCommit.body.trim().length > 0) { + body = singleCommit.body + } + } + } + + expect(title).toEqual('Update files') + expect(body).toEqual(defaultBody) // Should keep default when commit body is empty + }) + + test('should handle commit with multi-line body', () => { + const commit: Commit = { + sha: 'abc123', + tree: 'tree123', + parents: ['parent123'], + signed: false, + subject: 'Update documentation', + body: 'This is a detailed commit message.\n\nIt has multiple paragraphs.\n\n- Item 1\n- Item 2', + changes: [], + unparsedChanges: [] + } + + const branchCommits = [commit] + let title = defaultTitle + let body = defaultBody + + // Simulate the logic from create-pull-request.ts + if (branchCommits.length === 1) { + const singleCommit = branchCommits[0] + if (title === defaultTitle) { + title = singleCommit.subject + } + if (body === defaultBody) { + if (singleCommit.body && singleCommit.body.trim().length > 0) { + body = singleCommit.body + } + } + } + + expect(title).toEqual('Update documentation') + expect(body).toEqual( + 'This is a detailed commit message.\n\nIt has multiple paragraphs.\n\n- Item 1\n- Item 2' + ) + }) + + test('should handle commit with whitespace-only body', () => { + const commit: Commit = { + sha: 'abc123', + tree: 'tree123', + parents: ['parent123'], + signed: false, + subject: 'Update files', + body: ' \n\n ', + changes: [], + unparsedChanges: [] + } + + const branchCommits = [commit] + let title = defaultTitle + let body = defaultBody + + // Simulate the logic from create-pull-request.ts + if (branchCommits.length === 1) { + const singleCommit = branchCommits[0] + if (title === defaultTitle) { + title = singleCommit.subject + } + if (body === defaultBody) { + if (singleCommit.body && singleCommit.body.trim().length > 0) { + body = singleCommit.body + } + } + } + + expect(title).toEqual('Update files') + expect(body).toEqual(defaultBody) // Should keep default when commit body is only whitespace + }) +}) diff --git a/dist/index.js b/dist/index.js index 75ae3c2f07..b17a428a35 100644 --- a/dist/index.js +++ b/dist/index.js @@ -535,6 +535,26 @@ function createPullRequest(inputs) { core.endGroup(); } if (result.hasDiffWithBase) { + // Default PR title and body from commit message for single-commit PRs + // This mimics GitHub's web interface behavior + const defaultTitle = 'Changes by create-pull-request action'; + const defaultBody = 'Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action'; + if (result.branchCommits.length === 1) { + const commit = result.branchCommits[0]; + // Use commit subject as title if title is at default value + if (inputs.title === defaultTitle) { + inputs.title = commit.subject; + core.info(`Defaulting pull request title to commit subject: '${inputs.title}'`); + } + // Use commit body as PR body if body is at default value + if (inputs.body === defaultBody) { + // Use commit body if it exists, otherwise keep the default + if (commit.body && commit.body.trim().length > 0) { + inputs.body = commit.body; + core.info(`Defaulting pull request body to commit body (${commit.body.length} characters)`); + } + } + } core.startGroup('Create or update the pull request'); const pull = yield ghPull.createOrUpdatePullRequest(inputs, baseRemote.repository, branchRepository); outputs.set('pull-request-number', pull.number.toString()); diff --git a/docs/default-pr-title-body.md b/docs/default-pr-title-body.md new file mode 100644 index 0000000000..6e30db03f0 --- /dev/null +++ b/docs/default-pr-title-body.md @@ -0,0 +1,128 @@ +# Default PR Title and Body from Commit Message + +When creating a pull request with a single commit, the action will automatically default the PR title and body to match the commit message, similar to how GitHub's web interface behaves. + +## Behavior + +### Single Commit PRs + +When the following conditions are met: +- The pull request branch contains exactly **one commit** +- The `title` input is not provided (uses default value) +- The `body` input is not provided (uses default value) + +The action will: +- Use the commit's **subject line** as the PR title +- Use the commit's **body** as the PR description (if the commit has a body) + +### Example + +Given a commit with this message: +``` +Update AGP sources to the latest + +_Auto-generated by `dump-sources` Github workflow._ +``` + +The following workflow configuration: +```yml +- uses: peter-evans/create-pull-request@v7 + with: + commit-message: | + Update AGP sources to the latest + + _Auto-generated by `dump-sources` Github workflow._ + branch: actions/dump-sources + delete-branch: true +``` + +Will create a pull request with: +- **Title**: `Update AGP sources to the latest` +- **Body**: `_Auto-generated by `dump-sources` Github workflow._` + +### Multiple Commits + +When the pull request branch contains **multiple commits**, the action will use the default values: +- **Title**: `Changes by create-pull-request action` +- **Body**: `Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action` + +### Custom Title and Body + +If you explicitly provide `title` or `body` inputs, those values will always be used regardless of the number of commits: + +```yml +- uses: peter-evans/create-pull-request@v7 + with: + commit-message: | + Update AGP sources to the latest + + _Auto-generated by `dump-sources` Github workflow._ + title: Custom PR Title + body: Custom PR Description + branch: actions/dump-sources +``` + +This will create a PR with your custom title and body, ignoring the commit message. + +## Benefits + +This feature eliminates the need to duplicate commit messages in the workflow configuration: + +### Before (Duplicated Content) +```yml +- uses: peter-evans/create-pull-request@v7 + with: + commit-message: | + Update AGP sources to the latest + + _Auto-generated by `dump-sources` Github workflow._ + title: Update AGP sources to the latest + body: _Auto-generated by `dump-sources` Github workflow._ + branch: actions/dump-sources +``` + +### After (Simplified) +```yml +- uses: peter-evans/create-pull-request@v7 + with: + commit-message: | + Update AGP sources to the latest + + _Auto-generated by `dump-sources` Github workflow._ + branch: actions/dump-sources +``` + +## Edge Cases + +### Commit with Empty Body + +If a commit has only a subject line (no body), the PR will use: +- **Title**: The commit subject +- **Body**: The default body text + +Example commit: +``` +Update files +``` + +Results in: +- **Title**: `Update files` +- **Body**: `Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action` + +### Commit with Multi-line Body + +Multi-line commit bodies are fully preserved in the PR description: + +Example commit: +``` +Update documentation + +This is a detailed commit message. + +It has multiple paragraphs. + +- Item 1 +- Item 2 +``` + +Results in a PR with the full multi-line body as the description. diff --git a/src/create-pull-request.ts b/src/create-pull-request.ts index 0ab4f8cc17..cbe999ac42 100644 --- a/src/create-pull-request.ts +++ b/src/create-pull-request.ts @@ -238,6 +238,33 @@ export async function createPullRequest(inputs: Inputs): Promise { } if (result.hasDiffWithBase) { + // Default PR title and body from commit message for single-commit PRs + // This mimics GitHub's web interface behavior + const defaultTitle = 'Changes by create-pull-request action' + const defaultBody = + 'Automated changes by [create-pull-request](https://github.com/peter-evans/create-pull-request) GitHub action' + + if (result.branchCommits.length === 1) { + const commit = result.branchCommits[0] + // Use commit subject as title if title is at default value + if (inputs.title === defaultTitle) { + inputs.title = commit.subject + core.info( + `Defaulting pull request title to commit subject: '${inputs.title}'` + ) + } + // Use commit body as PR body if body is at default value + if (inputs.body === defaultBody) { + // Use commit body if it exists, otherwise keep the default + if (commit.body && commit.body.trim().length > 0) { + inputs.body = commit.body + core.info( + `Defaulting pull request body to commit body (${commit.body.length} characters)` + ) + } + } + } + core.startGroup('Create or update the pull request') const pull = await ghPull.createOrUpdatePullRequest( inputs,