Skip to content

Commit c4ae210

Browse files
committed
feat: input to force update for an existing ref
1 parent 6d622e3 commit c4ae210

File tree

6 files changed

+92
-7
lines changed

6 files changed

+92
-7
lines changed

.github/workflows/integration-tests.yml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ jobs:
2323
id: checkout
2424
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
2525

26+
- name: Make note of the branch name
27+
id: branch-name
28+
run: echo "BRANCH=$(git rev-parse --abbrev-ref HEAD)" >> "$GITHUB_OUTPUT"
29+
2630
- name: Generate GitHub App Token
2731
uses: electron/github-app-auth-action@384fd19694fe7b6dcc9a684746c6976ad78228ae # v1.1.1
2832
id: generate-token
@@ -154,6 +158,7 @@ jobs:
154158
run: |
155159
git pull
156160
git clean -fdx
161+
git restore .
157162
158163
- name: Optionally don't fail if no changes to commit
159164
uses: ./
@@ -163,9 +168,48 @@ jobs:
163168
ref: integration-test-playground-${{ github.run_id }}-${{ github.run_number }}
164169
token: ${{ steps.generate-token.outputs.token }}
165170

171+
- name: Switch back to base branch
172+
run: git switch ${{ steps.branch-name.outputs.BRANCH }}
173+
174+
- name: Make changes to commit and stage them
175+
run: |
176+
date +%s%3N > current-date.txt
177+
git add current-date.txt
178+
179+
- name: Update existing ref (force)
180+
uses: ./
181+
id: force-update-existing-ref
182+
with:
183+
force: true
184+
message: Test updating existing ref (force)
185+
ref: integration-test-playground-${{ github.run_id }}-${{ github.run_number }}
186+
token: ${{ steps.generate-token.outputs.token }}
187+
188+
- name: Confirm forced commit
189+
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
190+
with:
191+
github-token: ${{ steps.generate-token.outputs.token }}
192+
script: |
193+
const assert = require('node:assert');
194+
195+
const refs = ['integration-test-playground-${{ github.run_id }}-${{ github.run_number }}', '${{ steps.force-update-existing-ref.outputs.sha }}'];
196+
197+
// Fetch the commit by both ref and sha
198+
for (const ref of refs) {
199+
const { data } = await github.rest.repos.getCommit({
200+
owner: context.repo.owner,
201+
repo: context.repo.repo,
202+
ref: 'integration-test-playground-${{ github.run_id }}-${{ github.run_number }}',
203+
});
204+
205+
assert.strictEqual(data.sha, '${{ steps.force-update-existing-ref.outputs.sha }}', 'Expected sha for commit to match');
206+
assert.strictEqual(data.commit.message, 'Test updating existing ref (force)', 'Expected commit message to match');
207+
assert.strictEqual(data.commit.verification.verified, true, 'Expected commit to be verified');
208+
}
209+
166210
- name: Clean up new ref
167211
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
168-
if: ${{ steps.generate-token.outputs.token }}
212+
if: ${{ always() && steps.generate-token.outputs.token }}
169213
with:
170214
github-token: ${{ steps.generate-token.outputs.token }}
171215
script: |

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ instead of running `git commit`. The changes to commit will be detected automati
2323
### Updating Existing Ref
2424

2525
If you want to update an existing ref, you should ensure that ref is checked out in
26-
the current git checkout (you can use the `ref` input for `actions/checkout`).
26+
the current git checkout (you can use the `ref` input for `actions/checkout`). You
27+
can force the update using the `force` input.
2728

2829
### Multiple Commits
2930

@@ -60,6 +61,7 @@ jobs:
6061
### Inputs
6162
6263
- `fail-on-no-changes` - *(optional)* Whether or not to set action failure if there are no changes to commit (default: `true`)
64+
- `force` - *(optional)* Whether to force the update or to make sure the update is a fast-forward update when updating an existing ref (default: `false`)
6365
- `message` - **(required)** The commit message
6466
- `ref` - *(optional)* Git reference to associate the commit with (e.g. `main`). If it does not exist it will be created. Defaults to the the current checkout ref.
6567
- `token` - **(required)** GitHub App installation access token

__tests__/main.test.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ describe('action', () => {
9797
const headTreeHash = 'head-tree-hash';
9898

9999
mockGetInput({ message, token });
100+
mockGetBooleanInput({});
100101
jest.mocked(lib.getHeadRef).mockResolvedValue(ref);
101102
jest.mocked(lib.getHeadSha).mockResolvedValue('head-sha');
102103
jest.mocked(lib.getHeadTreeHash).mockResolvedValue(headTreeHash);
@@ -123,7 +124,8 @@ describe('action', () => {
123124
expect(updateRef).toHaveBeenCalledWith(
124125
expect.objectContaining({
125126
sha: commitSha,
126-
ref: `heads/${ref}`
127+
ref: `heads/${ref}`,
128+
force: false
127129
})
128130
);
129131

@@ -137,6 +139,7 @@ describe('action', () => {
137139
const headTreeHash = 'head-tree-hash';
138140

139141
mockGetInput({ message, token, ref });
142+
mockGetBooleanInput({});
140143
jest.mocked(lib.getHeadSha).mockResolvedValue('head-sha');
141144
jest.mocked(lib.getHeadTreeHash).mockResolvedValue(headTreeHash);
142145
jest.mocked(lib.getStagedFiles).mockResolvedValue(stagedFiles);
@@ -162,7 +165,8 @@ describe('action', () => {
162165
expect(updateRef).toHaveBeenCalledWith(
163166
expect.objectContaining({
164167
sha: commitSha,
165-
ref: `heads/${ref}`
168+
ref: `heads/${ref}`,
169+
force: false
166170
})
167171
);
168172

@@ -218,7 +222,8 @@ describe('action', () => {
218222
expect(updateRef).toHaveBeenCalledWith(
219223
expect.objectContaining({
220224
sha: commitSha,
221-
ref: `heads/${ref}`
225+
ref: `heads/${ref}`,
226+
force: false
222227
})
223228
);
224229
expect(createRef).toHaveBeenCalledWith(
@@ -282,6 +287,32 @@ describe('action', () => {
282287
);
283288
});
284289

290+
it('can force an update', async () => {
291+
const ref = 'main';
292+
const commitSha = 'commit-sha';
293+
294+
mockGetInput({ message, token });
295+
mockGetBooleanInput({ force: true });
296+
jest.mocked(lib.getHeadRef).mockResolvedValue(ref);
297+
jest.mocked(lib.getHeadSha).mockResolvedValue('head-sha');
298+
jest.mocked(lib.getHeadTreeHash).mockResolvedValue('head-tree-hash');
299+
jest.mocked(lib.getStagedFiles).mockResolvedValue(stagedFiles);
300+
jest.mocked(createTree).mockResolvedValue({ data: { sha: 'tree-sha' } });
301+
jest.mocked(createCommit).mockResolvedValue({ data: { sha: commitSha } });
302+
jest.mocked(updateRef).mockResolvedValue({ data: {} });
303+
304+
await main.run();
305+
expect(runSpy).toHaveReturned();
306+
307+
expect(lib.getHeadRef).toHaveBeenCalled();
308+
expect(updateRef).toHaveBeenCalledWith(
309+
expect.objectContaining({
310+
force: true
311+
})
312+
);
313+
expect(createRef).not.toHaveBeenCalled();
314+
});
315+
285316
it('handles generic errors', async () => {
286317
mockGetInput({ message, token });
287318
jest.mocked(lib.getStagedFiles).mockImplementation(() => {

action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ inputs:
1111
description: Whether or not to set action failure if there are no changes to commit
1212
required: false
1313
default: true
14+
force:
15+
description: Whether to force the update or to make sure the update is a fast-forward update when updating an existing ref
16+
required: false
17+
default: false
1418
message:
1519
description: The commit message
1620
required: true

dist/index.js

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/main.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export async function run(): Promise<void> {
6767
// Optional inputs
6868
const ref = `heads/${core.getInput('ref') || (await getHeadRef())}`;
6969
const failOnNoChanges = core.getBooleanInput('fail-on-no-changes');
70+
const force = core.getBooleanInput('force');
7071

7172
const tree = await populateTree();
7273

@@ -105,7 +106,8 @@ export async function run(): Promise<void> {
105106
owner,
106107
repo,
107108
ref,
108-
sha: newCommit.data.sha
109+
sha: newCommit.data.sha,
110+
force
109111
});
110112
core.debug(`Updated ref: ${ref} to ${newCommit.data.sha}`);
111113
} catch (err) {

0 commit comments

Comments
 (0)