From a38e03e5d15327ce6e19384579cf3b2ce2ed498e Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Thu, 26 Mar 2026 20:58:03 +0100 Subject: [PATCH 1/8] feat: allow empty commits Signed-off-by: ElaineDeMattosSilvaB --- .../actions/gitlabRepoPush.examples.test.ts | 21 +++++++++++++++++++ .../src/actions/gitlabRepoPush.examples.ts | 19 +++++++++++++++++ .../src/actions/gitlabRepoPush.ts | 8 +++++++ 3 files changed, 48 insertions(+) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts index 25b6c89ae4..2a50ee9411 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.test.ts @@ -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', + ); + }); + }); }); diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.ts index 56a59f25bc..e43dbddb42 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.examples.ts @@ -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, + }, + }, + ], + }), + }, ]; diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index 24f2907041..1bf817aac8 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -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); @@ -217,6 +224,7 @@ export const createGitlabRepoPushAction = (options: { branchName, ctx.input.commitMessage, actions, + ...(allowEmpty !== undefined ? [{ allowEmpty } as object] : []), ); return commit.id; }, From 8474da5694aa3cb86713a4b3df4815c2b0c82f31 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Thu, 26 Mar 2026 21:07:18 +0100 Subject: [PATCH 2/8] feat: add api report and changeset Signed-off-by: ElaineDeMattosSilvaB --- .changeset/deep-kings-strive.md | 5 +++++ plugins/scaffolder-backend-module-gitlab/report.api.md | 9 +++++---- 2 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 .changeset/deep-kings-strive.md diff --git a/.changeset/deep-kings-strive.md b/.changeset/deep-kings-strive.md new file mode 100644 index 0000000000..afe7415ec5 --- /dev/null +++ b/.changeset/deep-kings-strive.md @@ -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. diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index a5a4de0f8b..0ab38c52d8 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -69,7 +69,7 @@ export const createGitlabIssueAction: (options: { discussionToResolve?: string | undefined; epicId?: number | undefined; labels?: string | undefined; - issueType?: 'issue' | 'task' | 'incident' | 'test_case' | undefined; + issueType?: 'issue' | 'incident' | 'test_case' | 'task' | undefined; mergeRequestToResolveDiscussionsOf?: number | undefined; milestoneId?: number | undefined; weight?: number | undefined; @@ -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; @@ -211,7 +212,7 @@ export function createPublishGitlabAction(options: { visibility?: 'internal' | 'private' | 'public' | undefined; path?: string | undefined; description?: string | undefined; - merge_method?: 'merge' | 'rebase_merge' | 'ff' | undefined; + merge_method?: 'merge' | 'ff' | 'rebase_merge' | undefined; topics?: string[] | undefined; auto_devops_enabled?: boolean | undefined; only_allow_merge_if_pipeline_succeeds?: boolean | undefined; @@ -273,7 +274,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'auto' | 'update' | 'create' | 'delete' | 'skip' | undefined; + commitAction?: 'auto' | 'update' | 'skip' | 'create' | 'delete' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -324,7 +325,7 @@ export const editGitlabIssueAction: (options: { discussionLocked?: boolean | undefined; dueDate?: string | undefined; epicId?: number | undefined; - issueType?: 'issue' | 'task' | 'incident' | 'test_case' | undefined; + issueType?: 'issue' | 'incident' | 'test_case' | 'task' | undefined; labels?: string | undefined; milestoneId?: number | undefined; removeLabels?: string | undefined; From 74fb37f72966d71d313a6734c4fadaf96226a375 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Thu, 26 Mar 2026 21:40:44 +0100 Subject: [PATCH 3/8] fix: pass argument explicitly Signed-off-by: ElaineDeMattosSilvaB --- .../src/actions/gitlabRepoPush.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index 1bf817aac8..a4ecdaa564 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -224,7 +224,7 @@ export const createGitlabRepoPushAction = (options: { branchName, ctx.input.commitMessage, actions, - ...(allowEmpty !== undefined ? [{ allowEmpty } as object] : []), + allowEmpty !== undefined ? ({ allowEmpty } as any) : undefined, ); return commit.id; }, From b07ce3da626419200c2ea6ba8af15b2bf769a9ff Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Thu, 26 Mar 2026 21:58:08 +0100 Subject: [PATCH 4/8] fix: tests Signed-off-by: ElaineDeMattosSilvaB --- .../src/actions/gitlabRepoPush.test.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts index 8005c0791b..559f41ddcd 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts @@ -107,6 +107,7 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], + undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -146,6 +147,7 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], + undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -183,6 +185,7 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], + undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -220,6 +223,7 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], + undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -263,6 +267,7 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], + undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -305,6 +310,7 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], + undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -369,6 +375,7 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], + undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', From d7b27f68599378171e9397389fd6b2dfb521ec70 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Thu, 26 Mar 2026 22:06:40 +0100 Subject: [PATCH 5/8] fix: tests again Signed-off-by: ElaineDeMattosSilvaB --- .../src/actions/gitlabRepoPush.test.ts | 103 ++++++++++++++++-- 1 file changed, 96 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts index 559f41ddcd..c0fef779cd 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.test.ts @@ -57,6 +57,11 @@ describe('createGitLabCommit', () => { beforeEach(() => { mockDir.clear(); + jest.clearAllMocks(); + + mockGitlabClient.Commits.create.mockResolvedValue({ + id: 'bb6bce457ed069a38ef8d16ef38602972c7735c5', + }); const config = new ConfigReader({ integrations: { @@ -107,7 +112,6 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], - undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -147,7 +151,6 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], - undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -185,7 +188,6 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], - undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -223,7 +225,6 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], - undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -267,7 +268,6 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], - undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -310,7 +310,6 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], - undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -375,7 +374,6 @@ describe('createGitLabCommit', () => { execute_filemode: false, }, ], - undefined, ); expect(ctx.output).toHaveBeenCalledWith( 'commitHash', @@ -454,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 }, + ); + }); + }); }); From a99c5abb0a8bec7654ef1f0fd09b86efe33359e1 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Fri, 27 Mar 2026 09:40:11 +0100 Subject: [PATCH 6/8] fix: avoid compatibility issues when allowEmpty not set Signed-off-by: ElaineDeMattosSilvaB --- .../src/actions/gitlabRepoPush.ts | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index a4ecdaa564..51ca19d1d0 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -219,13 +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, - allowEmpty !== undefined ? ({ allowEmpty } as any) : undefined, - ); + 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; }, }); From edb652db89d921180866db1ef698f3322c35e75f Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Fri, 27 Mar 2026 10:03:37 +0100 Subject: [PATCH 7/8] fix: add api reports again Signed-off-by: ElaineDeMattosSilvaB --- plugins/scaffolder-backend-module-gitlab/report.api.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index 0ab38c52d8..e634b76221 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -69,7 +69,7 @@ export const createGitlabIssueAction: (options: { discussionToResolve?: string | undefined; epicId?: number | undefined; labels?: string | undefined; - issueType?: 'issue' | 'incident' | 'test_case' | 'task' | undefined; + issueType?: 'issue' | 'task' | 'incident' | 'test_case' | undefined; mergeRequestToResolveDiscussionsOf?: number | undefined; milestoneId?: number | undefined; weight?: number | undefined; @@ -274,7 +274,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'auto' | 'update' | 'skip' | 'create' | 'delete' | undefined; + commitAction?: 'auto' | 'update' | 'create' | 'delete' | 'skip' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -325,7 +325,7 @@ export const editGitlabIssueAction: (options: { discussionLocked?: boolean | undefined; dueDate?: string | undefined; epicId?: number | undefined; - issueType?: 'issue' | 'incident' | 'test_case' | 'task' | undefined; + issueType?: 'issue' | 'task' | 'incident' | 'test_case' | undefined; labels?: string | undefined; milestoneId?: number | undefined; removeLabels?: string | undefined; From 4df1dc5ce1ac8b9083bfc6196f26998f28a74ae0 Mon Sep 17 00:00:00 2001 From: ElaineDeMattosSilvaB Date: Fri, 27 Mar 2026 10:56:04 +0100 Subject: [PATCH 8/8] fix: try and fix report api Signed-off-by: ElaineDeMattosSilvaB --- plugins/scaffolder-backend-module-gitlab/report.api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index e634b76221..e05d0af2f7 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -212,7 +212,7 @@ export function createPublishGitlabAction(options: { visibility?: 'internal' | 'private' | 'public' | undefined; path?: string | undefined; description?: string | undefined; - merge_method?: 'merge' | 'ff' | 'rebase_merge' | undefined; + merge_method?: 'merge' | 'rebase_merge' | 'ff' | undefined; topics?: string[] | undefined; auto_devops_enabled?: boolean | undefined; only_allow_merge_if_pipeline_succeeds?: boolean | undefined;