Merge pull request #33602 from elaine-mattos/feat/allow-empty-commits

This commit is contained in:
Fredrik Adelöw
2026-05-16 10:13:59 +02:00
committed by GitHub
6 changed files with 164 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
---
Added `allowEmpty` input option to the `gitlab:repo:push` action, allowing empty commits. Required from GitLab 18.8 or later.
@@ -155,6 +155,7 @@ export const createGitlabRepoPushAction: (options: {
targetPath?: string | undefined;
token?: string | undefined;
commitAction?: 'auto' | 'update' | 'create' | 'delete' | undefined;
allowEmpty?: boolean | undefined;
},
{
projectid: string;
@@ -181,4 +181,25 @@ describe('gitlab:repo:push', () => {
);
});
});
describe('Push an empty commit to gitlab repository', () => {
it(`Should ${examples[3].description}`, async () => {
const input = yaml.parse(examples[3].example).steps[0].input;
mockDir.setContent({ [workspacePath]: {} });
const ctx = createMockActionContext({ input, workspacePath });
await instance.handler(ctx);
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
'owner/repo',
'feature-branch',
'Initial Commit',
[],
{ allowEmpty: true },
);
expect(ctx.output).toHaveBeenCalledWith(
'commitHash',
'f8a2c9bd4e2915b0792b43235c779e82ddad54af',
);
});
});
});
@@ -73,4 +73,23 @@ export const examples: TemplateExample[] = [
],
}),
},
{
description:
'Push an empty commit to gitlab repository (from GitLab 18.8+ on)',
example: yaml.stringify({
steps: [
{
id: 'pushChanges',
action: 'gitlab:repo:push',
name: 'Push empty commit to gitlab repository',
input: {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
commitMessage: 'Initial Commit',
branchName: 'feature-branch',
allowEmpty: true,
},
},
],
}),
},
];
@@ -57,6 +57,11 @@ describe('createGitLabCommit', () => {
beforeEach(() => {
mockDir.clear();
jest.clearAllMocks();
mockGitlabClient.Commits.create.mockResolvedValue({
id: 'bb6bce457ed069a38ef8d16ef38602972c7735c5',
});
const config = new ConfigReader({
integrations: {
@@ -447,4 +452,95 @@ describe('createGitLabCommit', () => {
);
});
});
describe('allowEmpty parameter', () => {
it('passes allowEmpty: true to Commits.create when specified', async () => {
const input = {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
commitMessage: 'Empty commit',
branchName: 'some-branch',
allowEmpty: true,
};
mockDir.setContent({
[workspacePath]: {},
});
const ctx = createMockActionContext({ input, workspacePath });
await instance.handler(ctx);
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
'owner/repo',
'some-branch',
'Empty commit',
[],
{ allowEmpty: true },
);
});
it('does not pass 5th argument when allowEmpty is not specified', async () => {
const input = {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
commitMessage: 'Normal commit',
branchName: 'some-branch',
};
mockDir.setContent({
[workspacePath]: {
'foo.txt': 'content',
},
});
const ctx = createMockActionContext({ input, workspacePath });
await instance.handler(ctx);
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
'owner/repo',
'some-branch',
'Normal commit',
[
{
action: 'create',
filePath: 'foo.txt',
content: 'Y29udGVudA==',
encoding: 'base64',
execute_filemode: false,
},
],
);
// Verify only 4 arguments were passed (no 5th)
expect(mockGitlabClient.Commits.create.mock.calls[0]).toHaveLength(4);
});
it('passes allowEmpty: false to Commits.create when explicitly set', async () => {
const input = {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
commitMessage: 'Commit with files',
branchName: 'some-branch',
allowEmpty: false,
};
mockDir.setContent({
[workspacePath]: {
'foo.txt': 'content',
},
});
const ctx = createMockActionContext({ input, workspacePath });
await instance.handler(ctx);
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
'owner/repo',
'some-branch',
'Commit with files',
[
{
action: 'create',
filePath: 'foo.txt',
content: 'Y29udGVudA==',
encoding: 'base64',
execute_filemode: false,
},
],
{ allowEmpty: false },
);
});
});
});
@@ -83,6 +83,12 @@ export const createGitlabRepoPushAction = (options: {
'The action to be used for git commit. Defaults to create, but can be set to update or delete',
})
.optional(),
allowEmpty: z =>
z
.boolean({
description: 'Allow an empty commit to be created.',
})
.optional(),
},
output: {
projectid: z =>
@@ -107,6 +113,7 @@ export const createGitlabRepoPushAction = (options: {
sourcePath,
token,
commitAction,
allowEmpty,
} = ctx.input;
const { owner, repo, project } = parseRepoUrl(repoUrl, integrations);
@@ -212,12 +219,21 @@ export const createGitlabRepoPushAction = (options: {
const commitId = await ctx.checkpoint({
key: `commit.create.${repoID}.${branchName}`,
fn: async () => {
const commit = await api.Commits.create(
repoID,
branchName,
ctx.input.commitMessage,
actions,
);
const commit =
allowEmpty !== undefined
? await api.Commits.create(
repoID,
branchName,
ctx.input.commitMessage,
actions,
{ allowEmpty } as any,
)
: await api.Commits.create(
repoID,
branchName,
ctx.input.commitMessage,
actions,
);
return commit.id;
},
});