From f2d034b0ef00b7a6aceefc84a4bdc8f3a5f17f1e Mon Sep 17 00:00:00 2001 From: bi003731 Date: Mon, 1 Dec 2025 20:53:36 -0300 Subject: [PATCH 1/6] Add auto commitAction to pushRepo action Signed-off-by: Joao Pedro Goulart Signed-off-by: bi003731 --- .changeset/all-socks-taste.md | 5 ++ .../src/actions/gitlabMergeRequest.ts | 50 +----------------- .../src/actions/gitlabRepoPush.ts | 45 ++++++++++++++-- .../src/util.ts | 52 ++++++++++++++++++- 4 files changed, 100 insertions(+), 52 deletions(-) create mode 100644 .changeset/all-socks-taste.md diff --git a/.changeset/all-socks-taste.md b/.changeset/all-socks-taste.md new file mode 100644 index 0000000000..7f3fd8541c --- /dev/null +++ b/.changeset/all-socks-taste.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-gitlab': minor +--- + +In the gitlabRepoPush action, add 'auto' possibility for commitAction input. diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts index 66c72b4c46..b345b33ee1 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabMergeRequest.ts @@ -31,57 +31,11 @@ import { import path from 'path'; import { ScmIntegrationRegistry } from '@backstage/integration'; import { InputError } from '@backstage/errors'; -import { - LoggerService, - resolveSafeChildPath, -} from '@backstage/backend-plugin-api'; +import { resolveSafeChildPath } from '@backstage/backend-plugin-api'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabMergeRequest.examples'; -import { createHash } from 'crypto'; -function computeSha256(file: SerializedFile): string { - const hash = createHash('sha256'); - hash.update(file.content); - return hash.digest('hex'); -} - -async function getFileAction( - fileInfo: { file: SerializedFile; targetPath?: string }, - target: { repoID: string; branch: string }, - api: InstanceType, - logger: LoggerService, - remoteFiles: RepositoryTreeSchema[], - defaultCommitAction: - | 'create' - | 'delete' - | 'update' - | 'skip' - | 'auto' = 'auto', -): Promise<'create' | 'delete' | 'update' | 'skip'> { - if (defaultCommitAction === 'auto') { - const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); - - if (remoteFiles?.some(remoteFile => remoteFile.path === filePath)) { - try { - const targetFile = await api.RepositoryFiles.show( - target.repoID, - filePath, - target.branch, - ); - if (computeSha256(fileInfo.file) === targetFile.content_sha256) { - return 'skip'; - } - } catch (error) { - logger.warn( - `Unable to retrieve detailed information for remote file ${filePath}`, - ); - } - return 'update'; - } - return 'create'; - } - return defaultCommitAction; -} +import { getFileAction } from '../util'; async function getReviewersFromApprovalRules( api: InstanceType, diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index a3ae415704..b1a88de869 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -26,6 +26,9 @@ import { import { CommitAction } from '@gitbeaker/rest'; import { createGitlabApi, getErrorMessage } from './helpers'; import { examples } from './gitlabRepoPush.examples'; +import { getFileAction } from '../util'; +import { SerializedFile } from '@backstage/plugin-scaffolder-node'; +import { RepositoryTreeSchema } from '@gitbeaker/rest'; /** * Create a new action that commits into a gitlab repository. @@ -75,7 +78,7 @@ export const createGitlabRepoPushAction = (options: { .optional(), commitAction: z => z - .enum(['create', 'update', 'delete'], { + .enum(['create', 'update', 'delete', 'auto'], { description: 'The action to be used for git commit. Defaults to create, but can be set to update or delete', }) @@ -126,8 +129,44 @@ export const createGitlabRepoPushAction = (options: { gitignore: true, }); - const actions: CommitAction[] = fileContents.map(file => ({ - action: commitAction ?? 'create', + let remoteFiles: RepositoryTreeSchema[] = []; + if ((ctx.input.commitAction ?? 'auto') === 'auto') { + try { + remoteFiles = await api.Repositories.allRepositoryTrees(repoID, { + ref: branchName, + recursive: true, + path: targetPath ?? undefined, + }); + } catch (e) { + ctx.logger.warn( + `Could not retrieve the list of files for ${repoID} (branch: ${branchName}) : ${getErrorMessage( + e, + )}`, + ); + } + } + + const actions: CommitAction[] = ( + ( + await Promise.all( + fileContents.map(async file => { + const action = await getFileAction( + { file, targetPath }, + { repoID, branch: branchName }, + api, + ctx.logger, + remoteFiles, + ctx.input.commitAction, + ); + return { file, action }; + }), + ) + ).filter(o => o.action !== 'skip') as { + file: SerializedFile; + action: CommitAction['action']; + }[] + ).map(({ file, action }) => ({ + action, filePath: targetPath ? path.posix.join(targetPath, file.path) : file.path, diff --git a/plugins/scaffolder-backend-module-gitlab/src/util.ts b/plugins/scaffolder-backend-module-gitlab/src/util.ts index db1468b180..4c40a36acc 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/util.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/util.ts @@ -14,15 +14,21 @@ * limitations under the License. */ +import { LoggerService } from '@backstage/backend-plugin-api'; import { InputError } from '@backstage/errors'; import { GitLabIntegration, ScmIntegrationRegistry, } from '@backstage/integration'; -import { Gitlab, GroupSchema } from '@gitbeaker/rest'; +import { Gitlab, GroupSchema, RepositoryTreeSchema } from '@gitbeaker/rest'; import { z } from 'zod'; import commonGitlabConfig from './commonGitlabConfig'; +import { SerializedFile } from '@backstage/plugin-scaffolder-node'; + +import { createHash } from 'crypto'; +import path from 'path'; + export const parseRepoHost = (repoUrl: string): string => { let parsed; try { @@ -184,3 +190,47 @@ export async function checkEpicScope( throw new InputError(`Could not find epic scope: ${error.message}`); } } + +function computeSha256(file: SerializedFile): string { + const hash = createHash('sha256'); + hash.update(file.content); + return hash.digest('hex'); +} + +export async function getFileAction( + fileInfo: { file: SerializedFile; targetPath?: string }, + target: { repoID: string; branch: string }, + api: InstanceType, + logger: LoggerService, + remoteFiles: RepositoryTreeSchema[], + defaultCommitAction: + | 'create' + | 'delete' + | 'update' + | 'skip' + | 'auto' = 'auto', +): Promise<'create' | 'delete' | 'update' | 'skip'> { + if (defaultCommitAction === 'auto') { + const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path); + + if (remoteFiles?.some(remoteFile => remoteFile.path === filePath)) { + try { + const targetFile = await api.RepositoryFiles.show( + target.repoID, + filePath, + target.branch, + ); + if (computeSha256(fileInfo.file) === targetFile.content_sha256) { + return 'skip'; + } + } catch (error) { + logger.warn( + `Unable to retrieve detailed information for remote file ${filePath}`, + ); + } + return 'update'; + } + return 'create'; + } + return defaultCommitAction; +} From b3a055de27f8405b5c4bbffb90c36cdba5b4f392 Mon Sep 17 00:00:00 2001 From: bi003731 Date: Mon, 1 Dec 2025 21:26:11 -0300 Subject: [PATCH 2/6] Update API Report for changes Signed-off-by: Joao Pedro Goulart Signed-off-by: bi003731 --- plugins/scaffolder-backend-module-gitlab/report.api.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index 1da4c704b1..2646d2e356 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -46,7 +46,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; @@ -130,7 +130,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'update' | 'delete' | 'create' | undefined; + commitAction?: 'auto' | 'update' | 'create' | 'delete' | undefined; }, { projectid: string; @@ -163,7 +163,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; @@ -224,7 +224,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'auto' | 'update' | 'delete' | 'create' | 'skip' | undefined; + commitAction?: 'auto' | 'update' | 'skip' | 'create' | 'delete' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -275,7 +275,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 a416ee3fa928f19a26a21cdeb1715233bd226568 Mon Sep 17 00:00:00 2001 From: bi003731 Date: Mon, 1 Dec 2025 21:48:51 -0300 Subject: [PATCH 3/6] API Reports Signed-off-by: bi003731 --- plugins/scaffolder-backend-module-gitlab/report.api.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/report.api.md b/plugins/scaffolder-backend-module-gitlab/report.api.md index 2646d2e356..2ff099d469 100644 --- a/plugins/scaffolder-backend-module-gitlab/report.api.md +++ b/plugins/scaffolder-backend-module-gitlab/report.api.md @@ -46,7 +46,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; @@ -130,7 +130,7 @@ export const createGitlabRepoPushAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'auto' | 'update' | 'create' | 'delete' | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined; }, { projectid: string; @@ -163,7 +163,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; @@ -224,7 +224,7 @@ export const createPublishGitlabMergeRequestAction: (options: { sourcePath?: string | undefined; targetPath?: string | undefined; token?: string | undefined; - commitAction?: 'auto' | 'update' | 'skip' | 'create' | 'delete' | undefined; + commitAction?: 'auto' | 'update' | 'delete' | 'create' | 'skip' | undefined; projectid?: string | undefined; removeSourceBranch?: boolean | undefined; assignee?: string | undefined; @@ -275,7 +275,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 f6122abbf1aa8a6866ee9ac9806ae675a0321fab Mon Sep 17 00:00:00 2001 From: bi003731 Date: Tue, 2 Dec 2025 08:55:46 -0300 Subject: [PATCH 4/6] sequential calls to glab Signed-off-by: bi003731 --- .../src/actions/gitlabRepoPush.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index b1a88de869..bdd6321911 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -148,8 +148,9 @@ export const createGitlabRepoPushAction = (options: { const actions: CommitAction[] = ( ( - await Promise.all( - fileContents.map(async file => { + await (async () => { + const results = []; + for (const file of fileContents) { const action = await getFileAction( { file, targetPath }, { repoID, branch: branchName }, @@ -158,9 +159,10 @@ export const createGitlabRepoPushAction = (options: { remoteFiles, ctx.input.commitAction, ); - return { file, action }; - }), - ) + results.push({ file, action }); + } + return results; + })() ).filter(o => o.action !== 'skip') as { file: SerializedFile; action: CommitAction['action']; From 7c1aafca6ca6b80882e53f61cd572aa9a60549e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Pedro=20Mendes=20Goulart?= <34889468+joaopedromgoulart@users.noreply.github.com> Date: Tue, 2 Dec 2025 12:55:41 -0300 Subject: [PATCH 5/6] Update .changeset/all-socks-taste.md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw Signed-off-by: João Pedro Mendes Goulart <34889468+joaopedromgoulart@users.noreply.github.com> --- .changeset/all-socks-taste.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/all-socks-taste.md b/.changeset/all-socks-taste.md index 7f3fd8541c..377782c2f9 100644 --- a/.changeset/all-socks-taste.md +++ b/.changeset/all-socks-taste.md @@ -2,4 +2,4 @@ '@backstage/plugin-scaffolder-backend-module-gitlab': minor --- -In the gitlabRepoPush action, add 'auto' possibility for commitAction input. +In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input. From 73615f4b9f646bda7eac5bd143dece3a7fb2cca5 Mon Sep 17 00:00:00 2001 From: bi003731 Date: Tue, 2 Dec 2025 13:31:17 -0300 Subject: [PATCH 6/6] improve readablility Signed-off-by: bi003731 --- .../src/actions/gitlabRepoPush.ts | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts index bdd6321911..dd8e28d87a 100644 --- a/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts +++ b/plugins/scaffolder-backend-module-gitlab/src/actions/gitlabRepoPush.ts @@ -146,36 +146,33 @@ export const createGitlabRepoPushAction = (options: { } } - const actions: CommitAction[] = ( - ( - await (async () => { - const results = []; - for (const file of fileContents) { - const action = await getFileAction( - { file, targetPath }, - { repoID, branch: branchName }, - api, - ctx.logger, - remoteFiles, - ctx.input.commitAction, - ); - results.push({ file, action }); - } - return results; - })() - ).filter(o => o.action !== 'skip') as { - file: SerializedFile; - action: CommitAction['action']; - }[] - ).map(({ file, action }) => ({ - action, - filePath: targetPath - ? path.posix.join(targetPath, file.path) - : file.path, - encoding: 'base64', - content: file.content.toString('base64'), - execute_filemode: file.executable, - })); + const fileActionMap: { + file: SerializedFile; + action: 'create' | 'delete' | 'update' | 'skip'; + }[] = []; + for (const file of fileContents) { + const action = await getFileAction( + { file, targetPath }, + { repoID, branch: branchName }, + api, + ctx.logger, + remoteFiles, + ctx.input.commitAction, + ); + fileActionMap.push({ file, action }); + } + + const actions: CommitAction[] = fileActionMap + .filter(o => o.action !== 'skip') + .map(({ file, action }) => ({ + action: action as CommitAction['action'], + filePath: targetPath + ? path.posix.join(targetPath, file.path) + : file.path, + encoding: 'base64', + content: file.content.toString('base64'), + execute_filemode: file.executable, + })); const branchExists = await ctx.checkpoint({ key: `branch.exists.${repoID}.${branchName}`,