Merge pull request #31929 from joaopedromgoulart/feat-gitlab-push-repo

Add auto commitAction to pushRepo action
This commit is contained in:
Fredrik Adelöw
2025-12-03 14:17:23 +01:00
committed by GitHub
5 changed files with 107 additions and 60 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-gitlab': minor
---
In the `gitlabRepoPush` action, add 'auto' possibility for `commitAction` input.
@@ -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' | 'delete' | 'create' | undefined;
},
{
projectid: string;
@@ -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<typeof Gitlab>,
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<typeof Gitlab>,
@@ -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,15 +129,50 @@ export const createGitlabRepoPushAction = (options: {
gitignore: true,
});
const actions: CommitAction[] = fileContents.map(file => ({
action: commitAction ?? 'create',
filePath: targetPath
? path.posix.join(targetPath, file.path)
: file.path,
encoding: 'base64',
content: file.content.toString('base64'),
execute_filemode: file.executable,
}));
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 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}`,
@@ -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<typeof Gitlab>,
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;
}