Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 43 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ For Kotlin DSL builds, pass the generated outputs into Gradle as project propert

```yaml
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Bump version
id: bump_version
Expand All @@ -41,6 +43,36 @@ Use `version_name` for Android's user-facing version, `version_code` for Android

Pin to a version tag instead of `master` if you want fully repeatable workflow runs.

#### Commit range

By default, the action reads commit messages from git history between the previous matching tag and `HEAD`.
Use `fetch-depth: 0` with `actions/checkout` so the runner has enough history and tags to find that range.
If no matching previous tag exists, the action reads all reachable commits.
If the git range cannot be read, the action falls back to the GitHub event payload for compatibility.

| Range | Behavior |
|--------------|-------------------------------------------------------------------------------|
| previous-tag | Reads commits from the previous tag matching `commit_tag_pattern` to `HEAD`. |
| base-ref | Reads commits from `commit_base_ref` to `HEAD`. |
| payload | Reads commit messages from the GitHub event payload, matching older behavior. |

For pull request or protected-branch workflows, set an explicit base ref:

```yaml
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Bump version
id: bump_version
uses: oflynned/android-version-bump@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
commit_range: base-ref
commit_base_ref: origin/main
```

#### Private repos

To use this action with `${{ secrets.GITHUB_TOKEN }}` in a private repo, set `contents: write` so the token can push the version commit and tag.
Expand Down Expand Up @@ -86,7 +118,7 @@ buildNumber=
```

| Backend | File | Behavior |
| ------------------ | -------------------- | ----------------------------------------------------------------- |
|--------------------|----------------------|-------------------------------------------------------------------|
| version-properties | `version.properties` | Compatibility default. The action writes the version keys file. |
| gradle-properties | `gradle.properties` | The action updates the version keys and preserves unrelated keys. |

Expand Down Expand Up @@ -275,13 +307,16 @@ Enable this field by passing a build number/string/SHA as an input to the action

Pass these in the `with:` block

| Tag | Effect | Example | Default value |
|-----------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|--------------------------|
| version_storage | Selects where version metadata is read from and written to. Supported values are `version-properties` and `gradle-properties`. | `version_storage: gradle-properties` updates `gradle.properties` | `version-properties` |
| tag_prefix | Prefix used in the generated release commit message. The git tag, `git_tag`, and `new_tag` outputs remain the unprefixed version. | `tag_prefix: 'release-'` makes the default commit message `release: release-1.0.0` | `v` |
| skip_ci | Affixes `[skip-ci]` to the end of the commit message, even if you provide a custom message | `skip_ci: false` | true |
| build_number | Sets the build run number in the version | `build_number: ${{ github.run_number }}` generates `1.0.0.5` | '' |
| commit_message | Sets the commit message when a release bump is performed. Can optionally use `{{ version }}` to insert the generated version bump with the tag prefix into the commit message. | `ci: {{ version }} was just released into the wild! :tada: :partying_face:` | `release: {{ version }}` |
| Tag | Effect | Example | Default value |
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|--------------------------|
| commit_range | Selects where version bump commit messages come from. Supported values are `previous-tag`, `base-ref`, and `payload`. | `commit_range: base-ref` reads from `commit_base_ref` to `HEAD` | `previous-tag` |
| commit_base_ref | Base ref used when `commit_range` is `base-ref`. If omitted, pull request workflows use `origin/${{ github.base_ref }}` when available. | `commit_base_ref: origin/main` | '' |
| commit_tag_pattern | Tag glob used when `commit_range` is `previous-tag`. | `commit_tag_pattern: 'v*'` | `*` |
| version_storage | Selects where version metadata is read from and written to. Supported values are `version-properties` and `gradle-properties`. | `version_storage: gradle-properties` updates `gradle.properties` | `version-properties` |
| tag_prefix | Prefix used in the generated release commit message. The git tag, `git_tag`, and `new_tag` outputs remain the unprefixed version. | `tag_prefix: 'release-'` makes the default commit message `release: release-1.0.0` | `v` |
| skip_ci | Affixes `[skip-ci]` to the end of the commit message, even if you provide a custom message | `skip_ci: false` | true |
| build_number | Sets the build run number in the version | `build_number: ${{ github.run_number }}` generates `1.0.0.5` | '' |
| commit_message | Sets the commit message when a release bump is performed. Can optionally use `{{ version }}` to insert the generated version bump with the tag prefix into the commit message. | `ci: {{ version }} was just released into the wild! :tada: :partying_face:` | `release: {{ version }}` |

## Outputs

Expand Down
11 changes: 11 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,17 @@ branding:
icon: chevron-up
color: blue
inputs:
commit_range:
required: false
description: 'Commit range source for version bumping: previous-tag, base-ref, or payload'
default: 'previous-tag'
commit_base_ref:
required: false
description: 'Base ref used when commit_range is base-ref, such as origin/main'
commit_tag_pattern:
required: false
description: 'Git tag pattern used when commit_range is previous-tag'
default: '*'
version_storage:
required: false
description: 'Version metadata storage backend: version-properties or gradle-properties'
Expand Down
14 changes: 7 additions & 7 deletions dist/index.js

Large diffs are not rendered by default.

47 changes: 45 additions & 2 deletions e2e/action.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ describe('packaged action with local git repositories', () => {
commits: ['feat: add login'],
headRef: 'feature/login',
inputs: {
commit_range: 'base-ref',
commit_base_ref: 'origin/main',
tag_prefix: 'release-',
skip_ci: 'false',
build_number: '42',
Expand Down Expand Up @@ -168,6 +170,44 @@ describe('packaged action with local git repositories', () => {
);
});

it('uses squash-style git history instead of payload commit details', () => {
const fixture = run({
version: '1.2.3',
commits: ['feat: squash login branch (#42)'],
eventCommits: [
{ id: 'fixture-1', message: 'fix: payload detail one' },
{ id: 'fixture-2', message: 'fix: payload detail two' },
],
});

expect(fixture.result.status).toBe(0);
expect(gitInRemote(fixture, 'show', 'main:version.properties')).toContain(
'minorVersion=3',
);
});

it('uses the previous matching tag to HEAD range', () => {
const fixture = run({
version: '1.2.3',
previousTag: '1.2.3',
preTagCommits: ['feat: already released login'],
commits: ['fix: repair launch'],
});

expect(fixture.result.status).toBe(0);
expect(gitInRemote(fixture, 'show', 'main:version.properties')).toBe(
[
'majorVersion=1',
'minorVersion=2',
'patchVersion=4',
'buildNumber=',
].join('\n'),
);
expect(gitInRemote(fixture, 'rev-parse', 'refs/tags/1.2.4')).toBe(
gitInRemote(fixture, 'rev-parse', 'refs/heads/main'),
);
});

it('reports a rejected push and leaves the remote unchanged', () => {
const fixture = run({
version: '1.2.3',
Expand All @@ -192,10 +232,13 @@ describe('packaged action with local git repositories', () => {
);
});

it('reads commit messages from real GitHub push payload objects (#122)', () => {
it('can use commit messages from real GitHub push payload objects (#122)', () => {
const fixture = run({
version: '1.2.3',
commits: ['feat: add login'],
commits: ['fix: repair launch'],
inputs: {
commit_range: 'payload',
},
eventCommits: [{ id: 'fixture', message: 'feat: add login' }],
});

Expand Down
19 changes: 19 additions & 0 deletions e2e/harness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ type VersionStorage = 'version-properties' | 'gradle-properties';
type FixtureOptions = {
version?: string;
versionStorage?: VersionStorage;
previousTag?: string;
preTagCommits?: string[];
commits?: string[];
eventCommits?: EventCommit[];
inputs?: Record<string, string>;
Expand Down Expand Up @@ -108,8 +110,25 @@ export const runActionFixture = (
}
git(workspace, ['add', '.']);
git(workspace, ['commit', '-m', 'chore: initial fixture']);

for (const [index, message] of (options.preTagCommits ?? []).entries()) {
fs.appendFileSync(
path.join(workspace, 'README.md'),
`pre-tag-${index}:${message}\n`,
);
git(workspace, ['add', 'README.md']);
git(workspace, ['commit', '-m', message]);
}

if (options.previousTag) {
git(workspace, ['tag', options.previousTag]);
}

git(workspace, ['remote', 'add', 'origin', remote]);
git(workspace, ['push', '-u', 'origin', 'main']);
if (options.previousTag) {
git(workspace, ['push', 'origin', options.previousTag]);
}

if (branch !== 'main') {
git(workspace, ['checkout', '-b', branch]);
Expand Down
126 changes: 126 additions & 0 deletions src/commits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
CommitRange,
getCommitBaseRef,
getCommitRange,
getCommitTagPattern,
} from './env';
import { runCommandOutput } from './run';
import { Toolkit } from './toolkit';
import { Commit } from './version';

const commitSeparator = '\0';

const parseGitLog = (output: string): string[] => {
return output
.split(commitSeparator)
.map((message) => message.trim())
.filter((message) => message.length > 0);
};

const getPayloadCommits = (toolkit: Toolkit): Commit[] => {
return toolkit.context.payload.commits ?? [];
};

const getDefaultBaseRef = (): string => {
const baseRef = process.env.GITHUB_BASE_REF;

if (baseRef) {
return `origin/${baseRef}`;
}

return '';
};

const resolveGitRange = async (
toolkit: Toolkit,
commitRange: Exclude<CommitRange, 'payload'>,
): Promise<string> => {
if (commitRange === 'base-ref') {
const baseRef = getCommitBaseRef(toolkit) || getDefaultBaseRef();

if (!baseRef) {
throw new Error(
'commit_range base-ref requires commit_base_ref or GITHUB_BASE_REF',
);
}

return `${baseRef}..HEAD`;
}

const tagPattern = getCommitTagPattern(toolkit);
const previousTag = (
await runCommandOutput('git', [
'describe',
'--tags',
'--abbrev=0',
'--match',
tagPattern,
])
).trim();

return `${previousTag}..HEAD`;
};

const getGitCommits = async (
toolkit: Toolkit,
commitRange: Exclude<CommitRange, 'payload'>,
): Promise<string[]> => {
let range: string | undefined;

try {
range = await resolveGitRange(toolkit, commitRange);
} catch (error) {
if (commitRange === 'previous-tag') {
toolkit.log.warn(
`No previous tag matched ${getCommitTagPattern(
toolkit,
)}; reading all reachable commits`,
);
} else {
throw error;
}
}

const args = ['log', '--format=%B%x00'];

if (range) {
args.push(range);
}

return parseGitLog(await runCommandOutput('git', args));
};

export const getCommitsForVersionBump = async (
toolkit: Toolkit,
): Promise<Commit[]> => {
const commitRange = getCommitRange(toolkit);

if (commitRange === 'payload') {
toolkit.log.log('Reading version bump commits from GitHub event payload');

return getPayloadCommits(toolkit);
}

try {
const commits = await getGitCommits(toolkit, commitRange);

if (commits.length > 0) {
toolkit.log.log(
`Reading version bump commits from git ${commitRange} range`,
);

return commits;
}

toolkit.log.warn(
`Git ${commitRange} range did not contain commits; falling back to GitHub event payload`,
);
} catch (error) {
toolkit.log.warn(
`Could not read git ${commitRange} range; falling back to GitHub event payload`,
);
toolkit.log.warn(error);
}

return getPayloadCommits(toolkit);
};
Loading