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:
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user