Skip to content

Commit

Permalink
The "commitAndTag()" task does not commit files if a tag for the spec…
Browse files Browse the repository at this point in the history
…ified version is already created.

Replaced git commands called from a shell with a library (simple-git).
  • Loading branch information
pomek committed Oct 9, 2024
1 parent caa0fce commit 4fed602
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 72 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"lint-staged": "^15.0.0",
"listr2": "^8.0.0",
"minimist": "^1.2.8",
"simple-git": "^3.27.0",
"semver": "^7.6.3",
"upath": "^2.0.1"
},
Expand Down
29 changes: 12 additions & 17 deletions packages/ckeditor5-dev-release-tools/lib/tasks/commitandtag.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@
*/

import upath from 'upath';
import { tools } from '@ckeditor/ckeditor5-dev-utils';
import { glob } from 'glob';
import shellEscape from 'shell-escape';
import { simpleGit } from 'simple-git';

const { toUnix } = upath;

Expand All @@ -27,22 +26,18 @@ export default async function commitAndTag( { version, files, cwd = process.cwd(
return;
}

const shExecOptions = {
cwd: normalizedCwd,
async: true,
verbosity: 'silent'
};
const git = simpleGit( {
baseDir: normalizedCwd
} );

// Run the command separately for each file to avoid exceeding the maximum command length on Windows, which is 32767 characters.
for ( const filePath of filePathsToAdd ) {
await tools.shExec( `git add ${ shellEscape( [ filePath ] ) }`, shExecOptions );
}
const { all: availableTags } = await git.tags();
const tagForVersion = availableTags.find( tag => tag.endsWith( version ) );

const escapedVersion = {
commit: shellEscape( [ `Release: v${ version }.` ] ),
tag: shellEscape( [ `v${ version }` ] )
};
// Do not commit and create tags if a tag is already taken. It might happen when a release job is restarted.
if ( tagForVersion ) {
return;
}

await tools.shExec( `git commit --message ${ escapedVersion.commit } --no-verify`, shExecOptions );
await tools.shExec( `git tag ${ escapedVersion.tag }`, shExecOptions );
await git.commit( `Release: v${ version }.`, filePathsToAdd );
await git.addTag( `v${ version }` );
}
125 changes: 70 additions & 55 deletions packages/ckeditor5-dev-release-tools/tests/tasks/commitandtag.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,92 +4,107 @@
*/

import { beforeEach, describe, expect, it, vi } from 'vitest';
import shellEscape from 'shell-escape';
import { tools } from '@ckeditor/ckeditor5-dev-utils';
import { glob } from 'glob';
import commitAndTag from '../../lib/tasks/commitandtag.js';
import { simpleGit } from 'simple-git';

vi.mock( 'simple-git' );
vi.mock( 'glob' );
vi.mock( 'shell-escape' );
vi.mock( '@ckeditor/ckeditor5-dev-utils' );

describe( 'commitAndTag()', () => {
let stubs;

beforeEach( () => {
stubs = {
git: {
tags: vi.fn(),
commit: vi.fn(),
addTag: vi.fn()
}
};

vi.spyOn( process, 'cwd' ).mockReturnValue( '/home/ckeditor' );

vi.mocked( simpleGit ).mockReturnValue( stubs.git );

vi.mocked( glob ).mockResolvedValue( [] );
vi.mocked( shellEscape ).mockImplementation( v => `'${ v[ 0 ] }'` );

stubs.git.tags.mockResolvedValue( {
all: []
} );
} );

it( 'should not create a commit and tag if there are no files modified', async () => {
await commitAndTag( {} );
await commitAndTag( { files: [] } );

expect( vi.mocked( tools.shExec ) ).not.toHaveBeenCalled();
expect( stubs.git.commit ).not.toHaveBeenCalled();
expect( stubs.git.addTag ).not.toHaveBeenCalled();
} );

it( 'should not create a commit and tag if the specified version is already tagged', async () => {
stubs.git.tags.mockResolvedValue( {
all: [
'v1.0.0'
]
} );
await commitAndTag( { files: [ 'package.json' ], version: '1.0.0' } );

expect( stubs.git.commit ).not.toHaveBeenCalled();
expect( stubs.git.addTag ).not.toHaveBeenCalled();
} );

it( 'should allow to specify custom cwd', async () => {
vi.mocked( glob ).mockResolvedValue( [ 'package.json' ] );

await commitAndTag( { version: '1.0.0', cwd: 'my-cwd' } );

expect( vi.mocked( tools.shExec ).mock.calls[ 0 ][ 1 ].cwd ).to.deep.equal( 'my-cwd' );
expect( vi.mocked( tools.shExec ).mock.calls[ 1 ][ 1 ].cwd ).to.deep.equal( 'my-cwd' );
expect( vi.mocked( tools.shExec ).mock.calls[ 2 ][ 1 ].cwd ).to.deep.equal( 'my-cwd' );
} );
await commitAndTag( { version: '1.0.0', cwd: 'my-cwd', files: [] } );

it( 'should add provided files to git one by one', async () => {
vi.mocked( glob ).mockResolvedValue( [
'package.json',
'README.md',
'packages/custom-package/package.json',
'packages/custom-package/README.md'
] );

await commitAndTag( {
version: '1.0.0',
files: [ 'package.json', 'README.md', 'packages/*/package.json', 'packages/*/README.md' ]
expect( vi.mocked( simpleGit ) ).toHaveBeenCalledExactlyOnceWith( {
baseDir: 'my-cwd'
} );

expect( vi.mocked( tools.shExec ) ).toHaveBeenCalledTimes( 6 );
expect( vi.mocked( tools.shExec ).mock.calls[ 0 ][ 0 ] ).to.equal( 'git add \'package.json\'' );
expect( vi.mocked( tools.shExec ).mock.calls[ 1 ][ 0 ] ).to.equal( 'git add \'README.md\'' );
expect( vi.mocked( tools.shExec ).mock.calls[ 2 ][ 0 ] ).to.equal( 'git add \'packages/custom-package/package.json\'' );
expect( vi.mocked( tools.shExec ).mock.calls[ 3 ][ 0 ] ).to.equal( 'git add \'packages/custom-package/README.md\'' );
expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( expect.anything(), expect.objectContaining( {
cwd: 'my-cwd'
} ) );
} );

it( 'should set correct commit message', async () => {
it( 'should use the default cwd when not specified', async () => {
vi.mocked( glob ).mockResolvedValue( [ 'package.json' ] );

await commitAndTag( { version: '1.0.0', packagesDirectory: 'packages' } );
await commitAndTag( { version: '1.0.0', files: [] } );

expect( vi.mocked( simpleGit ) ).toHaveBeenCalledExactlyOnceWith( {
baseDir: '/home/ckeditor'
} );

expect( vi.mocked( tools.shExec ).mock.calls[ 1 ][ 0 ] ).to.equal( 'git commit --message \'Release: v1.0.0.\' --no-verify' );
expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( expect.anything(), expect.objectContaining( {
cwd: '/home/ckeditor'
} ) );
} );

it( 'should set correct tag', async () => {
vi.mocked( glob ).mockResolvedValue( [ 'package.json' ] );
it( 'should commit given files with a release message', async () => {
vi.mocked( glob ).mockResolvedValue( [ 'package.json', 'packages/ckeditor5-foo/package.json' ] );

await commitAndTag( { version: '1.0.0', packagesDirectory: 'packages' } );
await commitAndTag( { version: '1.0.0', packagesDirectory: 'packages', files: [ '**/package.json' ] } );

expect( vi.mocked( tools.shExec ).mock.calls[ 2 ][ 0 ] ).to.equal( 'git tag \'v1.0.0\'' );
expect( vi.mocked( glob ) ).toHaveBeenCalledExactlyOnceWith( expect.anything(), expect.objectContaining( {
absolute: true,
nodir: true
} ) );

expect( stubs.git.commit ).toHaveBeenCalledExactlyOnceWith(
'Release: v1.0.0.',
[
'package.json',
'packages/ckeditor5-foo/package.json'
]
);
} );

it( 'should escape arguments passed to a shell command', async () => {
vi.mocked( glob ).mockResolvedValue( [
'package.json',
'README.md',
'packages/custom-package/package.json',
'packages/custom-package/README.md'
] );

await commitAndTag( {
version: '1.0.0',
files: [ 'package.json', 'README.md', 'packages/*/package.json', 'packages/*/README.md' ]
} );
it( 'should add a tag to the created commit', async () => {
vi.mocked( glob ).mockResolvedValue( [ 'package.json' ] );

await commitAndTag( { version: '1.0.0', packagesDirectory: 'packages' } );

expect( vi.mocked( shellEscape ) ).toHaveBeenCalledTimes( 6 );
expect( vi.mocked( shellEscape ).mock.calls[ 0 ][ 0 ] ).to.deep.equal( [ 'package.json' ] );
expect( vi.mocked( shellEscape ).mock.calls[ 1 ][ 0 ] ).to.deep.equal( [ 'README.md' ] );
expect( vi.mocked( shellEscape ).mock.calls[ 2 ][ 0 ] ).to.deep.equal( [ 'packages/custom-package/package.json' ] );
expect( vi.mocked( shellEscape ).mock.calls[ 3 ][ 0 ] ).to.deep.equal( [ 'packages/custom-package/README.md' ] );
expect( vi.mocked( shellEscape ).mock.calls[ 4 ][ 0 ] ).to.deep.equal( [ 'Release: v1.0.0.' ] );
expect( vi.mocked( shellEscape ).mock.calls[ 5 ][ 0 ] ).to.deep.equal( [ 'v1.0.0' ] );
expect( stubs.git.addTag ).toHaveBeenCalledExactlyOnceWith( 'v1.0.0' );
} );
} );

0 comments on commit 4fed602

Please sign in to comment.