diff --git a/.changeset/large-bears-wash.md b/.changeset/large-bears-wash.md new file mode 100644 index 0000000000..3255714dd6 --- /dev/null +++ b/.changeset/large-bears-wash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +Use more efficient approach to staging files in git during scaffolder actions diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.test.ts new file mode 100644 index 0000000000..78156a1075 --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.test.ts @@ -0,0 +1,148 @@ +/* + * Copyright 2021 The Backstage Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Git, getVoidLogger } from '@backstage/backend-common'; +import { initRepoAndPush } from './helpers'; + +jest.mock('@backstage/backend-common', () => ({ + Git: { + fromAuth: jest.fn().mockReturnValue({ + init: jest.fn(), + add: jest.fn(), + commit: jest.fn(), + addRemote: jest.fn(), + push: jest.fn(), + }), + }, + getVoidLogger: jest.requireActual('@backstage/backend-common').getVoidLogger, +})); + +const mockedGit = Git.fromAuth({ + username: 'test-user', + password: 'test-password', + logger: getVoidLogger(), +}); + +describe('initRepoAndPush', () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('with minimal parameters', () => { + beforeEach(async () => { + await initRepoAndPush({ + dir: '/test/repo/dir/', + remoteUrl: 'git@github.com:test/repo.git', + auth: { + username: 'test-user', + password: 'test-password', + }, + logger: getVoidLogger(), + }); + }); + + it('initializes the repo', () => { + expect(mockedGit.init).toHaveBeenCalledWith({ + dir: '/test/repo/dir/', + defaultBranch: 'master', + }); + }); + + it('stages all files in the repo', () => { + expect(mockedGit.add).toHaveBeenCalledWith({ + dir: '/test/repo/dir/', + filepath: '.', + }); + }); + + it('creates an initial commit', () => { + expect(mockedGit.commit).toHaveBeenCalledWith({ + dir: '/test/repo/dir/', + message: 'Initial commit', + author: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + committer: { + name: 'Scaffolder', + email: 'scaffolder@backstage.io', + }, + }); + }); + + it('adds the appropriate remote', () => { + expect(mockedGit.addRemote).toHaveBeenCalledWith({ + dir: '/test/repo/dir/', + url: 'git@github.com:test/repo.git', + remote: 'origin', + }); + }); + + it('pushes to the remote', () => { + expect(mockedGit.push).toHaveBeenCalledWith({ + dir: '/test/repo/dir/', + remote: 'origin', + }); + }); + }); + + it('allows overriding the default branch', async () => { + await initRepoAndPush({ + dir: '/test/repo/dir/', + defaultBranch: 'trunk', + remoteUrl: 'git@github.com:test/repo.git', + auth: { + username: 'test-user', + password: 'test-password', + }, + logger: getVoidLogger(), + }); + + expect(mockedGit.init).toHaveBeenCalledWith({ + dir: '/test/repo/dir/', + defaultBranch: 'trunk', + }); + }); + + it('allows overriding the author', async () => { + await initRepoAndPush({ + dir: '/test/repo/dir/', + gitAuthorInfo: { + name: 'Custom Scaffolder Author', + email: 'scaffolder@example.org', + }, + remoteUrl: 'git@github.com:test/repo.git', + auth: { + username: 'test-user', + password: 'test-password', + }, + logger: getVoidLogger(), + }); + + expect(mockedGit.commit).toHaveBeenCalledWith({ + dir: '/test/repo/dir/', + message: 'Initial commit', + author: { + name: 'Custom Scaffolder Author', + email: 'scaffolder@example.org', + }, + committer: { + name: 'Custom Scaffolder Author', + email: 'scaffolder@example.org', + }, + }); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts index c1ad3adf3e..3907bc1d4e 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/helpers.ts @@ -16,7 +16,6 @@ import { spawn } from 'child_process'; import { PassThrough, Writable } from 'stream'; -import globby from 'globby'; import { Logger } from 'winston'; import { Git } from '@backstage/backend-common'; import { Octokit } from '@octokit/rest'; @@ -84,15 +83,7 @@ export async function initRepoAndPush({ defaultBranch, }); - const paths = await globby(['./**', './**/.*', '!.git'], { - cwd: dir, - gitignore: true, - dot: true, - }); - - for (const filepath of paths) { - await git.add({ dir, filepath }); - } + await git.add({ dir, filepath: '.' }); // use provided info if possible, otherwise use fallbacks const authorInfo = {