Merge pull request #25548 from Yghore/feat/mr-gitlab-automatic-commit-action

feat: add automatic commited action for merge-request gitlab
This commit is contained in:
Ben Lambert
2024-07-23 12:48:48 +02:00
committed by GitHub
5 changed files with 173 additions and 17 deletions
+10
View File
@@ -0,0 +1,10 @@
---
'@backstage/plugin-scaffolder-backend-module-gitlab': patch
---
Add custom action for merge request: **auto**
The **Auto** action selects the committed action between _create_ and _update_.
The **Auto** action fetches files using the **/projects/repository/tree endpoint**.
After fetching, it checks if the file exists locally and in the repository. If it does, it chooses **update**; otherwise, it chooses **create**.
@@ -193,7 +193,7 @@ export const createPublishGitlabMergeRequestAction: (options: {
sourcePath?: string | undefined;
targetPath?: string | undefined;
token?: string | undefined;
commitAction?: 'update' | 'delete' | 'create' | undefined;
commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined;
projectid?: string | undefined;
removeSourceBranch?: boolean | undefined;
assignee?: string | undefined;
@@ -61,6 +61,28 @@ const mockGitlabClient = {
];
}),
},
Repositories: {
tree: jest.fn(
async (
repoID: string | number,
options: { ref: string; recursive: boolean; path: string | undefined },
) => {
if (repoID !== 'owner/repo') throw new Error('repo does not exist');
if (options.recursive === false) throw new Error('malformed options');
else {
return [
{
id: 'a1e8f8d745cc87e3a9248358d9352bb7f9a0aeba',
name: 'auto.txt',
type: 'blob',
path: 'source/auto.txt',
mode: '040000',
},
];
}
},
),
},
};
jest.mock('@gitbeaker/node', () => ({
@@ -407,7 +429,7 @@ describe('createGitLabMergeRequest', () => {
});
describe('createGitLabMergeRequestWithoutCommitAction', () => {
it('default commitAction is create', async () => {
it('default commitAction is auto', async () => {
const input = {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
title: 'Create my new MR',
@@ -417,7 +439,7 @@ describe('createGitLabMergeRequest', () => {
};
mockDir.setContent({
[workspacePath]: {
source: { 'foo.txt': 'Hello there!' },
source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' },
irrelevant: { 'bar.txt': 'Nothing to see here' },
},
});
@@ -429,6 +451,13 @@ describe('createGitLabMergeRequest', () => {
'new-mr',
'Create my new MR',
[
{
action: 'update',
filePath: 'source/auto.txt',
content: 'RmlsZSBleGlzdA==',
encoding: 'base64',
execute_filemode: false,
},
{
action: 'create',
filePath: 'source/foo.txt',
@@ -512,6 +541,88 @@ describe('createGitLabMergeRequest', () => {
);
});
it('commitAction is auto when auto is passed in options', async () => {
const input = {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
title: 'Create my new MR',
branchName: 'new-mr',
description: 'MR description',
commitAction: 'auto',
};
mockDir.setContent({
[workspacePath]: {
source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' },
},
});
const ctx = createMockActionContext({ input, workspacePath });
await instance.handler(ctx);
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
'owner/repo',
'new-mr',
'Create my new MR',
[
{
action: 'update',
filePath: 'source/auto.txt',
content: 'RmlsZSBleGlzdA==',
encoding: 'base64',
execute_filemode: false,
},
{
action: 'create',
filePath: 'source/foo.txt',
content: 'SGVsbG8gdGhlcmUh',
encoding: 'base64',
execute_filemode: false,
},
],
);
});
it('commitAction is auto when auto is passed in options with targetPath', async () => {
const input = {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
title: 'Create my new MR',
branchName: 'new-mr',
description: 'MR description',
commitAction: 'auto',
targetPath: 'source',
};
mockDir.setContent({
[workspacePath]: {
source: { 'foo.txt': 'Hello there!', 'auto.txt': 'File exist' },
irrevelant: {},
},
});
const ctx = createMockActionContext({ input, workspacePath });
await instance.handler(ctx);
expect(mockGitlabClient.Commits.create).toHaveBeenCalledWith(
'owner/repo',
'new-mr',
'Create my new MR',
[
{
action: 'update',
filePath: 'source/auto.txt',
content: 'RmlsZSBleGlzdA==',
encoding: 'base64',
execute_filemode: false,
},
{
action: 'create',
filePath: 'source/foo.txt',
content: 'SGVsbG8gdGhlcmUh',
encoding: 'base64',
execute_filemode: false,
},
],
);
});
it('commitAction is delete when delete is passed in options', async () => {
const input = {
repoUrl: 'gitlab.com?repo=repo&owner=owner',
@@ -17,6 +17,7 @@
import {
createTemplateAction,
parseRepoUrl,
SerializedFile,
serializeDirectoryContents,
} from '@backstage/plugin-scaffolder-node';
import { Types } from '@gitbeaker/core';
@@ -27,6 +28,21 @@ import { resolveSafeChildPath } from '@backstage/backend-plugin-api';
import { createGitlabApi } from './helpers';
import { examples } from './gitlabMergeRequest.examples';
function getFileAction(
fileInfo: { file: SerializedFile; targetPath: string | undefined },
remoteFiles: Types.RepositoryTreeSchema[],
defaultCommitAction: 'create' | 'delete' | 'update' | 'auto' | undefined,
): 'create' | 'delete' | 'update' {
if (!defaultCommitAction || defaultCommitAction === 'auto') {
const filePath = path.join(fileInfo.targetPath ?? '', fileInfo.file.path);
return remoteFiles &&
remoteFiles.some(remoteFile => remoteFile.path === filePath)
? 'update'
: 'create';
}
return defaultCommitAction;
}
/**
* Create a new action that creates a gitlab merge request.
*
@@ -46,7 +62,7 @@ export const createPublishGitlabMergeRequestAction = (options: {
sourcePath?: string;
targetPath?: string;
token?: string;
commitAction?: 'create' | 'delete' | 'update';
commitAction?: 'create' | 'delete' | 'update' | 'auto';
/** @deprecated projectID passed as query parameters in the repoUrl */
projectid?: string;
removeSourceBranch?: boolean;
@@ -109,9 +125,9 @@ export const createPublishGitlabMergeRequestAction = (options: {
commitAction: {
title: 'Commit action',
type: 'string',
enum: ['create', 'update', 'delete'],
enum: ['create', 'update', 'delete', 'auto'],
description:
'The action to be used for git commit. Defaults to create.',
'The action to be used for git commit. Defaults to auto. "auto" is custom action provide by backstage, (automatic assign create or update action) /!\\ Use more api calls /!\\ *',
},
removeSourceBranch: {
title: 'Delete source branch',
@@ -199,16 +215,6 @@ export const createPublishGitlabMergeRequestAction = (options: {
gitignore: true,
});
const actions: Types.CommitAction[] = fileContents.map(file => ({
action: ctx.input.commitAction ?? 'create',
filePath: targetPath
? path.posix.join(targetPath, file.path)
: file.path,
encoding: 'base64',
content: file.content.toString('base64'),
execute_filemode: file.executable,
}));
let targetBranch = targetBranchName;
if (!targetBranch) {
const projects = await api.Projects.show(repoID);
@@ -217,6 +223,35 @@ export const createPublishGitlabMergeRequestAction = (options: {
targetBranch = defaultBranch!;
}
let remoteFiles: Types.RepositoryTreeSchema[] = [];
if (!ctx.input.commitAction || ctx.input.commitAction === 'auto') {
try {
remoteFiles = await api.Repositories.tree(repoID, {
ref: targetBranch,
recursive: true,
path: targetPath ?? undefined,
});
} catch (e) {
ctx.logger.warn(
`Could not retrieve the list of files for ${repoID} (branch: ${targetBranch}) : ${e}`,
);
}
}
const actions: Types.CommitAction[] = fileContents.map(file => ({
action: getFileAction(
{ file, targetPath },
remoteFiles,
ctx.input.commitAction,
),
filePath: targetPath
? path.posix.join(targetPath, file.path)
: file.path,
encoding: 'base64',
content: file.content.toString('base64'),
execute_filemode: file.executable,
}));
try {
await api.Branches.create(repoID, branchName, String(targetBranch));
} catch (e) {
+1 -1
View File
@@ -312,7 +312,7 @@ export const createPublishGitlabMergeRequestAction: (options: {
sourcePath?: string | undefined;
targetPath?: string | undefined;
token?: string | undefined;
commitAction?: 'update' | 'delete' | 'create' | undefined;
commitAction?: 'auto' | 'update' | 'delete' | 'create' | undefined;
projectid?: string | undefined;
removeSourceBranch?: boolean | undefined;
assignee?: string | undefined;