feat(dev-infra): save invalid commit message attempts to be restored on next commit attempt (#38304)
When a commit message fails validation, rather than throwing out the commit message entirely the commit message is saved into a draft file and restored on the next commit attempt. PR Close #38304
This commit is contained in:
parent
8366effeec
commit
f4ced74e3a
|
@ -5,8 +5,10 @@ ts_library(
|
|||
name = "commit-message",
|
||||
srcs = [
|
||||
"cli.ts",
|
||||
"commit-message-draft.ts",
|
||||
"config.ts",
|
||||
"parse.ts",
|
||||
"restore-commit-message.ts",
|
||||
"validate.ts",
|
||||
"validate-file.ts",
|
||||
"validate-range.ts",
|
||||
|
|
|
@ -9,6 +9,7 @@ import * as yargs from 'yargs';
|
|||
|
||||
import {info} from '../utils/console';
|
||||
|
||||
import {restoreCommitMessage} from './restore-commit-message';
|
||||
import {validateFile} from './validate-file';
|
||||
import {validateCommitRange} from './validate-range';
|
||||
|
||||
|
@ -16,6 +17,28 @@ import {validateCommitRange} from './validate-range';
|
|||
export function buildCommitMessageParser(localYargs: yargs.Argv) {
|
||||
return localYargs.help()
|
||||
.strict()
|
||||
.command(
|
||||
'restore-commit-message-draft', false, {
|
||||
'file-env-variable': {
|
||||
type: 'string',
|
||||
conflicts: ['file'],
|
||||
required: true,
|
||||
description:
|
||||
'The key for the environment variable which holds the arguments for the ' +
|
||||
'prepare-commit-msg hook as described here: ' +
|
||||
'https://git-scm.com/docs/githooks#_prepare_commit_msg',
|
||||
coerce: arg => {
|
||||
const [file, source] = (process.env[arg] || '').split(' ');
|
||||
if (!file) {
|
||||
throw new Error(`Provided environment variable "${arg}" was not found.`);
|
||||
}
|
||||
return [file, source];
|
||||
},
|
||||
}
|
||||
},
|
||||
args => {
|
||||
restoreCommitMessage(args.fileEnvVariable[0], args.fileEnvVariable[1]);
|
||||
})
|
||||
.command(
|
||||
'pre-commit-validate', 'Validate the most recent commit message', {
|
||||
'file': {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {existsSync, readFileSync, unlinkSync, writeFileSync} from 'fs';
|
||||
|
||||
/** Load the commit message draft from the file system if it exists. */
|
||||
export function loadCommitMessageDraft(basePath: string) {
|
||||
const commitMessageDraftPath = `${basePath}.ngDevSave`;
|
||||
if (existsSync(commitMessageDraftPath)) {
|
||||
return readFileSync(commitMessageDraftPath).toString();
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/** Remove the commit message draft from the file system. */
|
||||
export function deleteCommitMessageDraft(basePath: string) {
|
||||
const commitMessageDraftPath = `${basePath}.ngDevSave`;
|
||||
if (existsSync(commitMessageDraftPath)) {
|
||||
unlinkSync(commitMessageDraftPath);
|
||||
}
|
||||
}
|
||||
|
||||
/** Save the commit message draft to the file system for later retrieval. */
|
||||
export function saveCommitMessageDraft(basePath: string, commitMessage: string) {
|
||||
writeFileSync(`${basePath}.ngDevSave`, commitMessage);
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {info} from 'console';
|
||||
import {writeFileSync} from 'fs';
|
||||
|
||||
import {loadCommitMessageDraft} from './commit-message-draft';
|
||||
|
||||
/**
|
||||
* Restore the commit message draft to the git to be used as the default commit message.
|
||||
*
|
||||
* The source provided may be one of the sources described in
|
||||
* https://git-scm.com/docs/githooks#_prepare_commit_msg
|
||||
*/
|
||||
export function restoreCommitMessage(
|
||||
filePath: string, source?: 'message'|'template'|'squash'|'commit') {
|
||||
if (!!source) {
|
||||
info('Skipping commit message restoration attempt');
|
||||
if (source === 'message') {
|
||||
info('A commit message was already provided via the command with a -m or -F flag');
|
||||
}
|
||||
if (source === 'template') {
|
||||
info('A commit message was already provided via the -t flag or config.template setting');
|
||||
}
|
||||
if (source === 'squash') {
|
||||
info('A commit message was already provided as a merge action or via .git/MERGE_MSG');
|
||||
}
|
||||
if (source === 'commit') {
|
||||
info('A commit message was already provided through a revision specified via --fixup, -c,');
|
||||
info('-C or --amend flag');
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
/** A draft of a commit message. */
|
||||
const commitMessage = loadCommitMessageDraft(filePath);
|
||||
|
||||
// If the commit message draft has content, restore it into the provided filepath.
|
||||
if (commitMessage) {
|
||||
writeFileSync(filePath, commitMessage);
|
||||
}
|
||||
// Exit the process
|
||||
process.exit(0);
|
||||
}
|
|
@ -11,6 +11,7 @@ import {resolve} from 'path';
|
|||
import {getRepoBaseDir} from '../utils/config';
|
||||
import {info} from '../utils/console';
|
||||
|
||||
import {deleteCommitMessageDraft, saveCommitMessageDraft} from './commit-message-draft';
|
||||
import {validateCommitMessage} from './validate';
|
||||
|
||||
/** Validate commit message at the provided file path. */
|
||||
|
@ -18,8 +19,12 @@ export function validateFile(filePath: string) {
|
|||
const commitMessage = readFileSync(resolve(getRepoBaseDir(), filePath), 'utf8');
|
||||
if (validateCommitMessage(commitMessage)) {
|
||||
info('√ Valid commit message');
|
||||
deleteCommitMessageDraft(filePath);
|
||||
return;
|
||||
}
|
||||
// On all invalid commit messages, the commit message should be saved as a draft to be
|
||||
// restored on the next commit attempt.
|
||||
saveCommitMessageDraft(filePath, commitMessage);
|
||||
// If the validation did not return true, exit as a failure.
|
||||
process.exit(1);
|
||||
}
|
||||
|
|
|
@ -210,7 +210,8 @@
|
|||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "yarn -s ng-dev format staged",
|
||||
"commit-msg": "yarn -s ng-dev commit-message pre-commit-validate --file-env-variable HUSKY_GIT_PARAMS"
|
||||
"commit-msg": "yarn -s ng-dev commit-message pre-commit-validate --file-env-variable HUSKY_GIT_PARAMS",
|
||||
"prepare-commit-msg": "yarn -s ng-dev commit-message restore-commit-message-draft --file-env-variable HUSKY_GIT_PARAMS"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue