forked from dequelabs/axe-core
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcherry-pick.mjs
More file actions
executable file
·155 lines (130 loc) · 5.07 KB
/
Copy pathcherry-pick.mjs
File metadata and controls
executable file
·155 lines (130 loc) · 5.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
#!/usr/bin/env node
import { execSync } from 'node:child_process';
import { createRequire } from 'node:module';
const require = createRequire(import.meta.url);
const conventionalCommitsParser = require('conventional-commits-parser');
const chalk = require('chalk');
const { version } = require('../package.json');
// Node's default execSync buffer (~1 MiB) is too small for `git log` on long histories.
const GIT_LOG_EXEC_OPTS = { maxBuffer: 50 * 1024 * 1024 };
const releaseType = process.argv[2];
let ignoreCommits = process.argv[3];
if (!releaseType) {
console.error(
'Must specify a release type:\n$ node build/cherry-pick.mjs [<releaseType> | minor | patch] [<ignoreCommits>]'
);
process.exit(1);
}
// doing a major release should just be merging develop into master,
// no cherry-picking required
if (!['minor', 'patch'].includes(releaseType)) {
console.error('Release type not supported:', releaseType);
process.exit(1);
}
if (ignoreCommits) {
// use the short hash if a long hash is provided
ignoreCommits = ignoreCommits.split(',').map(hash => hash.substring(0, 8));
console.log(chalk.yellow('Ignoring commits:'), ignoreCommits.join(','), '\n');
} else {
ignoreCommits = [];
}
// don't run on master or develop branch
const currentBranch = execSync('git branch --show-current').toString().trim();
if (['develop', 'master'].includes(currentBranch)) {
console.error(
`Please run the script on a release branch and not the ${currentBranch} branch`
);
process.exit(1);
}
// find the version we should start pulling commits from
// e.g. if we are currently on version 3.4.5 and need to do a minor release we pull commits starting from 3.4.0
const [major, minor] = version.split('.').map(Number);
const targetVersion = releaseType === 'patch' ? version : `${major}.${minor}.0`;
// get all commits from a branch
function getCommits(branch) {
// all commits are too large for execSync buffer size so we'll just get since the last 3 years
const date = new Date(new Date().setFullYear(new Date().getFullYear() - 3));
const stdout = execSync(
`git log ${branch || ''} --no-merges --abbrev-commit --since=${date.getFullYear()}`,
GIT_LOG_EXEC_OPTS
).toString();
const allCommits = stdout
.split(/commit (?=[\w\d]{8}[\n\r])/)
.filter(commit => !!commit);
// parse commits
const commits = [];
for (let i = 0; i < allCommits.length; i++) {
const commit = allCommits[i];
const hash = commit.substring(0, 8);
const msg = commit.substring(commit.indexOf('\n\n')).trim();
const { type, scope, subject, merge, notes } =
conventionalCommitsParser.sync(msg, {
// parse merge commits
mergePattern: /^Merge pull request #(\d+) from (.*)$/,
mergeCorrespondence: ['id', 'source'],
// allow comma in scope
headerPattern: /^(\w*)(?:\(([\w\$\.\-\*, ]*)\))?\: (.*)$/
});
const isBreakingChange = notes.some(
note => note.title === 'BREAKING CHANGE'
);
const isFeat = type === 'feat';
const commitType = {
minor: !isBreakingChange,
patch: !isFeat && !isBreakingChange
};
// only get commits since the target version
// example commit message generated by running the release script:
// chore(release): 3.4.5
if (scope === 'release' && subject === targetVersion) {
break;
}
// filter merge commits and any types that don't match the release type
if (
!merge &&
subject &&
!subject.startsWith('merge branch') &&
scope !== 'release' &&
commitType[releaseType] &&
!ignoreCommits.includes(hash)
) {
// add in reverse order (order commited)
commits.unshift({ hash, msg, type, scope, subject });
}
}
return commits;
}
// only cherry-pick commits that have not been added to the current branch already
const currentCommits = getCommits();
const commitsToCherryPick = getCommits('develop').filter(commit => {
return !currentCommits.find(
currentCommit => currentCommit.subject === commit.subject
);
});
if (!commitsToCherryPick.length) {
console.log(chalk.yellow('No commits to cherry-pick'));
process.exit(1);
}
// cherry-pick all commits and accept whatever is in develop to avoid merge conflicts
commitsToCherryPick.forEach(({ hash, type, scope, subject }) => {
console.log(
`${chalk.yellow('Cherry-picking')} ${hash} ${type}${
scope ? `(${scope})` : ''
}: ${subject}`
);
try {
execSync(`git cherry-pick ${hash} -X theirs`);
} catch {
console.error(
chalk.red.bold('\nAborting cherry-pick and reseting to master')
);
console.error(
'\nCannot auto-resolve cherry-pick commit. This can be caused by the commit already being applied or a file being edited in the cherry-pick branch that does not yet exist (the commit it depends on was not cherry-picked). Please review the commits being cherry-picked and either manually resolve or ignore the commit.'
);
console.error(
`$ node build/cherry-pick.mjs ${releaseType} [commitSHA1,commitSHA2,...]`
);
execSync('git cherry-pick --abort; git reset --hard origin/master');
process.exit(1);
}
});