mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
chore: revert adding Trop annotations to release notes (#24922)
This reverts commit e3cf1ab030.
This commit is contained in:
12
script/release/notes/index.js
Normal file → Executable file
12
script/release/notes/index.js
Normal file → Executable file
@@ -120,7 +120,7 @@ const getPreviousPoint = async (point) => {
|
||||
}
|
||||
};
|
||||
|
||||
async function getReleaseNotes (range, newVersion) {
|
||||
async function getReleaseNotes (range, newVersion, explicitLinks) {
|
||||
const rangeList = range.split('..') || ['HEAD'];
|
||||
const to = rangeList.pop();
|
||||
const from = rangeList.pop() || (await getPreviousPoint(to));
|
||||
@@ -129,9 +129,10 @@ async function getReleaseNotes (range, newVersion) {
|
||||
newVersion = to;
|
||||
}
|
||||
|
||||
console.log(`Generating release notes between ${from} and ${to} for version ${newVersion}`);
|
||||
const notes = await notesGenerator.get(from, to, newVersion);
|
||||
const ret = {
|
||||
text: notesGenerator.render(notes)
|
||||
text: notesGenerator.render(notes, explicitLinks)
|
||||
};
|
||||
|
||||
if (notes.unknown.length) {
|
||||
@@ -143,7 +144,7 @@ async function getReleaseNotes (range, newVersion) {
|
||||
|
||||
async function main () {
|
||||
const opts = minimist(process.argv.slice(2), {
|
||||
boolean: ['help'],
|
||||
boolean: ['explicit-links', 'help'],
|
||||
string: ['version']
|
||||
});
|
||||
opts.range = opts._.shift();
|
||||
@@ -152,13 +153,14 @@ async function main () {
|
||||
console.log(`
|
||||
easy usage: ${name} version
|
||||
|
||||
full usage: ${name} [begin..]end [--version version]
|
||||
full usage: ${name} [begin..]end [--version version] [--explicit-links]
|
||||
|
||||
* 'begin' and 'end' are two git references -- tags, branches, etc --
|
||||
from which the release notes are generated.
|
||||
* if omitted, 'begin' defaults to the previous tag in end's branch.
|
||||
* if omitted, 'version' defaults to 'end'. Specifying a version is
|
||||
useful if you're making notes on a new version that isn't tagged yet.
|
||||
* 'explicit-links' makes every note's issue, commit, or pull an MD link
|
||||
|
||||
For example, these invocations are equivalent:
|
||||
${process.argv[1]} v4.0.1
|
||||
@@ -167,7 +169,7 @@ For example, these invocations are equivalent:
|
||||
return 0;
|
||||
}
|
||||
|
||||
const notes = await getReleaseNotes(opts.range, opts.version);
|
||||
const notes = await getReleaseNotes(opts.range, opts.version, opts['explicit-links']);
|
||||
console.log(notes.text);
|
||||
if (notes.warning) {
|
||||
throw new Error(notes.warning);
|
||||
|
||||
@@ -2,22 +2,25 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const childProcess = require('child_process');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const { GitProcess } = require('dugite');
|
||||
const octokit = require('@octokit/rest')({
|
||||
auth: process.env.ELECTRON_GITHUB_TOKEN
|
||||
});
|
||||
const semver = require('semver');
|
||||
|
||||
const { SRC_DIR } = require('../../lib/utils');
|
||||
const { ELECTRON_VERSION, SRC_DIR } = require('../../lib/utils');
|
||||
|
||||
const MAX_FAIL_COUNT = 3;
|
||||
const CHECK_INTERVAL = 5000;
|
||||
|
||||
const TROP_LOGIN = 'trop[bot]';
|
||||
|
||||
const CACHE_DIR = path.resolve(__dirname, '.cache');
|
||||
const NO_NOTES = 'No notes';
|
||||
const FOLLOW_REPOS = ['electron/electron', 'electron/node'];
|
||||
|
||||
const docTypes = new Set(['doc', 'docs']);
|
||||
const featTypes = new Set(['feat', 'feature']);
|
||||
@@ -25,8 +28,6 @@ const fixTypes = new Set(['fix']);
|
||||
const otherTypes = new Set(['spec', 'build', 'test', 'chore', 'deps', 'refactor', 'tools', 'vendor', 'perf', 'style', 'ci']);
|
||||
const knownTypes = new Set([...docTypes.keys(), ...featTypes.keys(), ...fixTypes.keys(), ...otherTypes.keys()]);
|
||||
|
||||
const getCacheDir = () => process.env.NOTES_CACHE_PATH || path.resolve(__dirname, '.cache');
|
||||
|
||||
/**
|
||||
***
|
||||
**/
|
||||
@@ -38,13 +39,6 @@ class GHKey {
|
||||
this.repo = repo;
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
static NewFromPull (pull) {
|
||||
const owner = pull.base.repo.owner.login;
|
||||
const repo = pull.base.repo.name;
|
||||
const number = pull.number;
|
||||
return new GHKey(owner, repo, number);
|
||||
}
|
||||
}
|
||||
|
||||
class Commit {
|
||||
@@ -53,13 +47,10 @@ class Commit {
|
||||
this.owner = owner; // string
|
||||
this.repo = repo; // string
|
||||
|
||||
this.body = null; // string
|
||||
this.isBreakingChange = false;
|
||||
this.issueNumber = null; // number
|
||||
this.note = null; // string
|
||||
|
||||
// A set of branches to which this change has been merged.
|
||||
// '8-x-y' => GHKey { owner: 'electron', repo: 'electron', number: 23714 }
|
||||
this.trops = new Map(); // Map<string,GHKey>
|
||||
|
||||
this.prKeys = new Set(); // GHKey
|
||||
this.revertHash = null; // string
|
||||
this.semanticType = null; // string
|
||||
@@ -110,7 +101,7 @@ const getNoteFromClerk = async (ghKey) => {
|
||||
if (comment.body.startsWith(PERSIST_LEAD)) {
|
||||
return comment.body
|
||||
.slice(PERSIST_LEAD.length).trim() // remove PERSIST_LEAD
|
||||
.split(/\r?\n/) // split into lines
|
||||
.split('\r?\n') // break into lines
|
||||
.map(line => line.trim())
|
||||
.filter(line => line.startsWith(QUOTE_LEAD)) // notes are quoted
|
||||
.map(line => line.slice(QUOTE_LEAD.length)) // unquote the lines
|
||||
@@ -120,11 +111,53 @@ const getNoteFromClerk = async (ghKey) => {
|
||||
}
|
||||
};
|
||||
|
||||
// copied from https://github.com/electron/clerk/blob/master/src/index.ts#L4-L13
|
||||
const OMIT_FROM_RELEASE_NOTES_KEYS = [
|
||||
'no-notes',
|
||||
'no notes',
|
||||
'no_notes',
|
||||
'none',
|
||||
'no',
|
||||
'nothing',
|
||||
'empty',
|
||||
'blank'
|
||||
];
|
||||
|
||||
const getNoteFromBody = body => {
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const NOTE_PREFIX = 'Notes: ';
|
||||
const NOTE_HEADER = '#### Release Notes';
|
||||
|
||||
let note = body
|
||||
.split(/\r?\n\r?\n/) // split into paragraphs
|
||||
.map(paragraph => paragraph.trim())
|
||||
.map(paragraph => paragraph.startsWith(NOTE_HEADER) ? paragraph.slice(NOTE_HEADER.length).trim() : paragraph)
|
||||
.find(paragraph => paragraph.startsWith(NOTE_PREFIX));
|
||||
|
||||
if (note) {
|
||||
note = note
|
||||
.slice(NOTE_PREFIX.length)
|
||||
.replace(/<!--.*-->/, '') // '<!-- change summary here-->'
|
||||
.replace(/\r?\n/, ' ') // remove newlines
|
||||
.trim();
|
||||
}
|
||||
|
||||
if (note && OMIT_FROM_RELEASE_NOTES_KEYS.includes(note.toLowerCase())) {
|
||||
return NO_NOTES;
|
||||
}
|
||||
|
||||
return note;
|
||||
};
|
||||
|
||||
/**
|
||||
* Looks for our project's conventions in the commit message:
|
||||
*
|
||||
* 'semantic: some description' -- sets semanticType, subject
|
||||
* 'some description (#99999)' -- sets subject, pr
|
||||
* 'Fixes #3333' -- sets issueNumber
|
||||
* 'Merge pull request #99999 from ${branchname}' -- sets pr
|
||||
* 'This reverts commit ${sha}' -- sets revertHash
|
||||
* line starting with 'BREAKING CHANGE' in body -- sets isBreakingChange
|
||||
@@ -142,6 +175,13 @@ const parseCommitMessage = (commitMessage, commit) => {
|
||||
subject = subject.slice(0, pos).trim();
|
||||
}
|
||||
|
||||
if (body) {
|
||||
commit.body = body;
|
||||
|
||||
const note = getNoteFromBody(body);
|
||||
if (note) { commit.note = note; }
|
||||
}
|
||||
|
||||
// if the subject ends in ' (#dddd)', treat it as a pull request id
|
||||
let match;
|
||||
if ((match = subject.match(/^(.*)\s\(#(\d+)\)$/))) {
|
||||
@@ -173,6 +213,7 @@ const parseCommitMessage = (commitMessage, commit) => {
|
||||
|
||||
// https://help.github.com/articles/closing-issues-using-keywords/
|
||||
if ((match = body.match(/\b(?:close|closes|closed|fix|fixes|fixed|resolve|resolves|resolved|for)\s#(\d+)\b/i))) {
|
||||
commit.issueNumber = parseInt(match[1]);
|
||||
commit.semanticType = commit.semanticType || 'fix';
|
||||
}
|
||||
|
||||
@@ -196,36 +237,32 @@ const parseCommitMessage = (commitMessage, commit) => {
|
||||
const parsePullText = (pull, commit) => parseCommitMessage(`${pull.data.title}\n\n${pull.data.body}`, commit);
|
||||
|
||||
const getLocalCommitHashes = async (dir, ref) => {
|
||||
const args = ['log', '--format=%H', ref];
|
||||
return (await runGit(dir, args))
|
||||
.split(/\r?\n/) // split into lines
|
||||
.map(hash => hash.trim());
|
||||
const args = ['log', '-z', '--format=%H', ref];
|
||||
return (await runGit(dir, args)).split('\0').map(hash => hash.trim());
|
||||
};
|
||||
|
||||
// return an array of Commits
|
||||
const getLocalCommits = async (module, point1, point2) => {
|
||||
const { owner, repo, dir } = module;
|
||||
|
||||
const fieldSep = ',';
|
||||
const format = ['%H', '%s'].join(fieldSep);
|
||||
const args = ['log', '--cherry-pick', '--right-only', '--first-parent', `--format=${format}`, `${point1}..${point2}`];
|
||||
const logs = (await runGit(dir, args))
|
||||
.split(/\r?\n/) // split into lines
|
||||
.map(field => field.trim());
|
||||
const fieldSep = '||';
|
||||
const format = ['%H', '%B'].join(fieldSep);
|
||||
const args = ['log', '-z', '--cherry-pick', '--right-only', '--first-parent', `--format=${format}`, `${point1}..${point2}`];
|
||||
const logs = (await runGit(dir, args)).split('\0').map(field => field.trim());
|
||||
|
||||
const commits = [];
|
||||
for (const log of logs) {
|
||||
if (!log) {
|
||||
continue;
|
||||
}
|
||||
const [hash, subject] = log.split(fieldSep, 2).map(field => field.trim());
|
||||
commits.push(parseCommitMessage(subject, new Commit(hash, owner, repo)));
|
||||
const [hash, message] = log.split(fieldSep, 2).map(field => field.trim());
|
||||
commits.push(parseCommitMessage(message, new Commit(hash, owner, repo)));
|
||||
}
|
||||
return commits;
|
||||
};
|
||||
|
||||
const checkCache = async (name, operation) => {
|
||||
const filename = path.resolve(getCacheDir(), name);
|
||||
const filename = path.resolve(CACHE_DIR, name);
|
||||
if (fs.existsSync(filename)) {
|
||||
return JSON.parse(fs.readFileSync(filename, 'utf8'));
|
||||
}
|
||||
@@ -249,42 +286,12 @@ async function runRetryable (fn, maxRetries) {
|
||||
}
|
||||
}
|
||||
// Silently eat 404s.
|
||||
// Silently eat 422s, which come from "No commit found for SHA"
|
||||
if (lastError.status !== 404 && lastError.status !== 422) throw lastError;
|
||||
if (lastError.status !== 404) throw lastError;
|
||||
}
|
||||
|
||||
const getPullCacheFilename = ghKey => `${ghKey.owner}-${ghKey.repo}-pull-${ghKey.number}`;
|
||||
|
||||
const getCommitPulls = async (owner, repo, hash) => {
|
||||
const name = `${owner}-${repo}-commit-${hash}`;
|
||||
const retryableFunc = () => octokit.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: hash });
|
||||
let ret = await checkCache(name, () => runRetryable(retryableFunc, MAX_FAIL_COUNT));
|
||||
|
||||
// only merged pulls belong in release notes
|
||||
if (ret && ret.data) {
|
||||
ret.data = ret.data.filter(pull => pull.merged_at);
|
||||
}
|
||||
|
||||
// cache the pulls
|
||||
if (ret && ret.data) {
|
||||
for (const pull of ret.data) {
|
||||
const cachefile = getPullCacheFilename(GHKey.NewFromPull(pull));
|
||||
const payload = { ...ret, data: pull };
|
||||
await checkCache(cachefile, () => payload);
|
||||
}
|
||||
}
|
||||
|
||||
// ensure the return value has the expected structure, even on failure
|
||||
if (!ret || !ret.data) {
|
||||
ret = { data: [] };
|
||||
}
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
const getPullRequest = async (ghKey) => {
|
||||
const { number, owner, repo } = ghKey;
|
||||
const name = getPullCacheFilename(ghKey);
|
||||
const name = `${owner}-${repo}-pull-${number}`;
|
||||
const retryableFunc = () => octokit.pulls.get({ pull_number: number, owner, repo });
|
||||
return checkCache(name, () => runRetryable(retryableFunc, MAX_FAIL_COUNT));
|
||||
};
|
||||
@@ -299,20 +306,10 @@ const getComments = async (ghKey) => {
|
||||
const addRepoToPool = async (pool, repo, from, to) => {
|
||||
const commonAncestor = await getCommonAncestor(repo.dir, from, to);
|
||||
|
||||
// mark the old branch's commits as old news
|
||||
for (const oldHash of await getLocalCommitHashes(repo.dir, from)) {
|
||||
pool.processedHashes.add(oldHash);
|
||||
}
|
||||
|
||||
// get the new branch's commits and the pulls associated with them
|
||||
// add the commits
|
||||
const oldHashes = await getLocalCommitHashes(repo.dir, from);
|
||||
oldHashes.forEach(hash => { pool.processedHashes.add(hash); });
|
||||
const commits = await getLocalCommits(repo, commonAncestor, to);
|
||||
for (const commit of commits) {
|
||||
const { owner, repo, hash } = commit;
|
||||
for (const pull of (await getCommitPulls(owner, repo, hash)).data) {
|
||||
commit.prKeys.add(GHKey.NewFromPull(pull));
|
||||
}
|
||||
}
|
||||
|
||||
pool.commits.push(...commits);
|
||||
|
||||
// add the pulls
|
||||
@@ -320,59 +317,95 @@ const addRepoToPool = async (pool, repo, from, to) => {
|
||||
let prKey;
|
||||
for (prKey of commit.prKeys.values()) {
|
||||
const pull = await getPullRequest(prKey);
|
||||
if (!pull || !pull.data) continue; // couldn't get it
|
||||
if (!pull || !pull.data) break; // couldn't get it
|
||||
if (pool.pulls[prKey.number]) break; // already have it
|
||||
pool.pulls[prKey.number] = pull;
|
||||
parsePullText(pull, commit);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// @return Map<string,GHKey>
|
||||
// where the key is a branch name (e.g. '7-1-x' or '8-x-y')
|
||||
// and the value is a GHKey to the PR
|
||||
async function getMergedTrops (commit, pool) {
|
||||
const branches = new Map();
|
||||
/***
|
||||
**** Other Repos
|
||||
***/
|
||||
|
||||
for (const prKey of commit.prKeys.values()) {
|
||||
const pull = pool.pulls[prKey.number];
|
||||
const mergedBranches = new Set(
|
||||
((pull && pull.data && pull.data.labels) ? pull.data.labels : [])
|
||||
.map(label => ((label && label.name) ? label.name : '').match(/merged\/([0-9]+-[x0-9]-[xy0-9])/))
|
||||
.filter(match => match)
|
||||
.map(match => match[1])
|
||||
);
|
||||
// other repos - gn
|
||||
|
||||
if (mergedBranches.size > 0) {
|
||||
const isTropComment = (comment) => comment && comment.user && comment.user.login === TROP_LOGIN;
|
||||
const getDepsVariable = async (ref, key) => {
|
||||
// get a copy of that reference point's DEPS file
|
||||
const deps = await runGit(ELECTRON_VERSION, ['show', `${ref}:DEPS`]);
|
||||
const filename = path.resolve(os.tmpdir(), 'DEPS');
|
||||
fs.writeFileSync(filename, deps);
|
||||
|
||||
const ghKey = GHKey.NewFromPull(pull.data);
|
||||
const backportRegex = /backported this PR to "(.*)",\s+please check out #(\d+)/;
|
||||
const getBranchNameAndPullKey = (comment) => {
|
||||
const match = ((comment && comment.body) ? comment.body : '').match(backportRegex);
|
||||
return match ? [match[1], new GHKey(ghKey.owner, ghKey.repo, parseInt(match[2]))] : null;
|
||||
};
|
||||
// query the DEPS file
|
||||
const response = childProcess.spawnSync(
|
||||
'gclient',
|
||||
['getdep', '--deps-file', filename, '--var', key],
|
||||
{ encoding: 'utf8' }
|
||||
);
|
||||
|
||||
const comments = await getComments(ghKey);
|
||||
((comments && comments.data) ? comments.data : [])
|
||||
.filter(isTropComment)
|
||||
.map(getBranchNameAndPullKey)
|
||||
.filter(pair => pair)
|
||||
.filter(([branch]) => mergedBranches.has(branch))
|
||||
.forEach(([branch, key]) => branches.set(branch, key));
|
||||
}
|
||||
// cleanup
|
||||
fs.unlinkSync(filename);
|
||||
return response.stdout.trim();
|
||||
};
|
||||
|
||||
const getDependencyCommitsGN = async (pool, fromRef, toRef) => {
|
||||
const repos = [{ // just node
|
||||
owner: 'electron',
|
||||
repo: 'node',
|
||||
dir: path.resolve(SRC_DIR, 'third_party', 'electron_node'),
|
||||
deps_variable_name: 'node_version'
|
||||
}];
|
||||
|
||||
for (const repo of repos) {
|
||||
// the 'DEPS' file holds the dependency reference point
|
||||
const key = repo.deps_variable_name;
|
||||
const from = await getDepsVariable(fromRef, key);
|
||||
const to = await getDepsVariable(toRef, key);
|
||||
await addRepoToPool(pool, repo, from, to);
|
||||
}
|
||||
};
|
||||
|
||||
// Changes are interesting if they make a change relative to a previous
|
||||
// release in the same series. For example if you fix a Y.0.0 bug, that
|
||||
// should be included in the Y.0.1 notes even if it's also tropped back
|
||||
// to X.0.1.
|
||||
//
|
||||
// The phrase 'previous release' is important: if this is the first
|
||||
// prerelease or first stable release in a series, we omit previous
|
||||
// branches' changes. Otherwise we will have an overwhelmingly long
|
||||
// list of mostly-irrelevant changes.
|
||||
const shouldIncludeMultibranchChanges = (version) => {
|
||||
let show = true;
|
||||
|
||||
if (semver.valid(version)) {
|
||||
const prerelease = semver.prerelease(version);
|
||||
show = prerelease
|
||||
? parseInt(prerelease.pop()) > 1
|
||||
: semver.patch(version) > 0;
|
||||
}
|
||||
|
||||
return branches;
|
||||
return show;
|
||||
};
|
||||
|
||||
function getOldestMajorBranchOfPull (pull) {
|
||||
return pull.data.labels
|
||||
.map(label => label.name.match(/merged\/(\d+)-(\d+)-x/) || label.name.match(/merged\/(\d+)-x-y/))
|
||||
.filter(label => !!label)
|
||||
.map(label => parseInt(label[1]))
|
||||
.filter(major => !!major)
|
||||
.sort()
|
||||
.shift();
|
||||
}
|
||||
|
||||
// @return the shorthand name of the branch that `ref` is on,
|
||||
// e.g. a ref of '10.0.0-beta.1' will return '10-x-y'
|
||||
async function getBranchNameOfRef (ref, dir) {
|
||||
return (await runGit(dir, ['branch', '--all', '--contains', ref, '--sort', 'version:refname']))
|
||||
.split(/\r?\n/) // split into lines
|
||||
.shift() // we sorted by refname and want the first result
|
||||
.match(/(?:.*\/)?(.*)/)[1] // 'remote/origins/10-x-y' -> '10-x-y'
|
||||
.trim();
|
||||
function getOldestMajorBranchOfCommit (commit, pool) {
|
||||
return [ ...commit.prKeys.values() ]
|
||||
.map(prKey => pool.pulls[prKey.number])
|
||||
.filter(pull => !!pull)
|
||||
.map(pull => getOldestMajorBranchOfPull(pull))
|
||||
.filter(major => !!major)
|
||||
.sort()
|
||||
.shift();
|
||||
}
|
||||
|
||||
/***
|
||||
@@ -380,21 +413,26 @@ async function getBranchNameOfRef (ref, dir) {
|
||||
***/
|
||||
|
||||
const getNotes = async (fromRef, toRef, newVersion) => {
|
||||
const cacheDir = getCacheDir();
|
||||
if (!fs.existsSync(cacheDir)) {
|
||||
fs.mkdirSync(cacheDir);
|
||||
if (!fs.existsSync(CACHE_DIR)) {
|
||||
fs.mkdirSync(CACHE_DIR);
|
||||
}
|
||||
|
||||
const pool = new Pool();
|
||||
const electronDir = path.resolve(SRC_DIR, 'electron');
|
||||
const toBranch = await getBranchNameOfRef(toRef, electronDir);
|
||||
|
||||
console.log(`Generating release notes between ${fromRef} and ${toRef} for version ${newVersion} in branch ${toBranch}`);
|
||||
|
||||
// get the electron/electron commits
|
||||
const electron = { owner: 'electron', repo: 'electron', dir: electronDir };
|
||||
const electron = { owner: 'electron', repo: 'electron', dir: ELECTRON_VERSION };
|
||||
await addRepoToPool(pool, electron, fromRef, toRef);
|
||||
|
||||
// Don't include submodules if comparing across major versions;
|
||||
// there's just too much churn otherwise.
|
||||
const includeDeps = semver.valid(fromRef) &&
|
||||
semver.valid(toRef) &&
|
||||
semver.major(fromRef) === semver.major(toRef);
|
||||
|
||||
if (includeDeps) {
|
||||
await getDependencyCommitsGN(pool, fromRef, toRef);
|
||||
}
|
||||
|
||||
// remove any old commits
|
||||
pool.commits = pool.commits.filter(commit => !pool.processedHashes.has(commit.hash));
|
||||
|
||||
@@ -419,24 +457,27 @@ const getNotes = async (fromRef, toRef, newVersion) => {
|
||||
// ensure the commit has a note
|
||||
for (const commit of pool.commits) {
|
||||
for (const prKey of commit.prKeys.values()) {
|
||||
commit.note = commit.note || await getNoteFromClerk(prKey);
|
||||
if (commit.note) {
|
||||
break;
|
||||
}
|
||||
commit.note = await getNoteFromClerk(prKey);
|
||||
}
|
||||
// use a fallback note in case someone missed a 'Notes' comment
|
||||
commit.note = commit.note || commit.subject;
|
||||
}
|
||||
|
||||
// remove non-user-facing commits
|
||||
pool.commits = pool.commits
|
||||
.filter(commit => commit && commit.note)
|
||||
.filter(commit => commit.note !== NO_NOTES)
|
||||
.filter(commit => commit.note.match(/^[Bb]ump v\d+\.\d+\.\d+/) === null);
|
||||
.filter(commit => !((commit.note || commit.subject).match(/^[Bb]ump v\d+\.\d+\.\d+/)));
|
||||
|
||||
for (const commit of pool.commits) {
|
||||
commit.trops = await getMergedTrops(commit, pool);
|
||||
if (!shouldIncludeMultibranchChanges(newVersion)) {
|
||||
const currentMajor = semver.parse(newVersion).major;
|
||||
pool.commits = pool.commits
|
||||
.filter(commit => getOldestMajorBranchOfCommit(commit, pool) >= currentMajor);
|
||||
}
|
||||
|
||||
pool.commits = removeSupercededStackUpdates(pool.commits);
|
||||
pool.commits = removeSupercededChromiumUpdates(pool.commits);
|
||||
|
||||
const notes = {
|
||||
breaking: [],
|
||||
@@ -445,8 +486,7 @@ const getNotes = async (fromRef, toRef, newVersion) => {
|
||||
fix: [],
|
||||
other: [],
|
||||
unknown: [],
|
||||
name: newVersion,
|
||||
toBranch
|
||||
name: newVersion
|
||||
};
|
||||
|
||||
pool.commits.forEach(commit => {
|
||||
@@ -471,66 +511,49 @@ const getNotes = async (fromRef, toRef, newVersion) => {
|
||||
return notes;
|
||||
};
|
||||
|
||||
const removeSupercededStackUpdates = (commits) => {
|
||||
const updateRegex = /^Updated ([a-zA-Z.]+) to v?([\d.]+)/;
|
||||
const notupdates = [];
|
||||
const removeSupercededChromiumUpdates = (commits) => {
|
||||
const chromiumRegex = /^Updated Chromium to \d+\.\d+\.\d+\.\d+/;
|
||||
const updates = commits.filter(commit => (commit.note || commit.subject).match(chromiumRegex));
|
||||
const keepers = commits.filter(commit => !updates.includes(commit));
|
||||
|
||||
const newest = {};
|
||||
for (const commit of commits) {
|
||||
const match = (commit.note || commit.subject).match(updateRegex);
|
||||
if (!match) {
|
||||
notupdates.push(commit);
|
||||
continue;
|
||||
}
|
||||
const [, dep, version] = match;
|
||||
if (!newest[dep] || newest[dep].version < version) {
|
||||
newest[dep] = { commit, version };
|
||||
}
|
||||
// keep the newest update.
|
||||
if (updates.length) {
|
||||
const compare = (a, b) => (a.note || a.subject).localeCompare(b.note || b.subject);
|
||||
keepers.push(updates.sort(compare).pop());
|
||||
}
|
||||
|
||||
return [...notupdates, ...Object.values(newest).map(o => o.commit)];
|
||||
return keepers;
|
||||
};
|
||||
|
||||
/***
|
||||
**** Render
|
||||
***/
|
||||
|
||||
// @return the pull request's GitHub URL
|
||||
const buildPullURL = ghKey => `https://github.com/${ghKey.owner}/${ghKey.repo}/pull/${ghKey.number}`;
|
||||
const renderLink = (commit, explicitLinks) => {
|
||||
let link;
|
||||
const { owner, repo } = commit;
|
||||
const keyIt = commit.prKeys.values().next();
|
||||
if (keyIt.done) /* no PRs */ {
|
||||
const { hash } = commit;
|
||||
const url = `https://github.com/${owner}/${repo}/commit/${hash}`;
|
||||
const text = owner === 'electron' && repo === 'electron'
|
||||
? `${hash.slice(0, 8)}`
|
||||
: `${owner}/${repo}@${hash.slice(0, 8)}`;
|
||||
link = explicitLinks ? `[${text}](${url})` : text;
|
||||
} else {
|
||||
const { number } = keyIt.value;
|
||||
const url = `https://github.com/${owner}/${repo}/pull/${number}`;
|
||||
const text = owner === 'electron' && repo === 'electron'
|
||||
? `#${number}`
|
||||
: `${owner}/${repo}#${number}`;
|
||||
link = explicitLinks ? `[${text}](${url})` : text;
|
||||
}
|
||||
return link;
|
||||
};
|
||||
|
||||
const renderPull = ghKey => `[#${ghKey.number}](${buildPullURL(ghKey)})`;
|
||||
|
||||
// @return the commit's GitHub URL
|
||||
const buildCommitURL = commit => `https://github.com/${commit.owner}/${commit.repo}/commit/${commit.hash}`;
|
||||
|
||||
const renderCommit = commit => `[${commit.hash.slice(0, 8)}](${buildCommitURL(commit)})`;
|
||||
|
||||
// @return a markdown link to the PR if available; otherwise, the git commit
|
||||
function renderLink (commit) {
|
||||
const maybePull = commit.prKeys.values().next();
|
||||
return maybePull.value ? renderPull(maybePull.value) : renderCommit(commit);
|
||||
}
|
||||
|
||||
// @return a terser branch name,
|
||||
// e.g. '7-2-x' -> '7.2' and '8-x-y' -> '8'
|
||||
const renderBranchName = name => name.replace(/-[a-zA-Z]/g, '').replace('-', '.');
|
||||
|
||||
const renderTrop = (branch, ghKey) => `[${renderBranchName(branch)}](${buildPullURL(ghKey)})`;
|
||||
|
||||
// @return markdown-formatted links to other branches' trops,
|
||||
// e.g. "(Also in 7.2, 8, 9)"
|
||||
function renderTrops (commit, excludeBranch) {
|
||||
const body = [...commit.trops.entries()]
|
||||
.filter(([branch]) => branch !== excludeBranch)
|
||||
.sort(([branchA], [branchB]) => parseInt(branchA) - parseInt(branchB)) // sort by semver major
|
||||
.map(([branch, key]) => renderTrop(branch, key))
|
||||
.join(', ');
|
||||
return body ? `<span style="font-size:small;">(Also in ${body})</span>` : body;
|
||||
}
|
||||
|
||||
// @return a slightly cleaned-up human-readable change description
|
||||
function renderDescription (commit) {
|
||||
let note = commit.note || commit.subject || '';
|
||||
const renderCommit = (commit, explicitLinks) => {
|
||||
// clean up the note
|
||||
let note = commit.note || commit.subject;
|
||||
note = note.trim();
|
||||
if (note.length !== 0) {
|
||||
note = note[0].toUpperCase() + note.substr(1);
|
||||
@@ -567,24 +590,30 @@ function renderDescription (commit) {
|
||||
}
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
const link = renderLink(commit, explicitLinks);
|
||||
|
||||
// @return markdown-formatted release note line item,
|
||||
// e.g. '* Fixed a foo. #12345 (Also in 7.2, 8, 9)'
|
||||
const renderNote = (commit, excludeBranch) =>
|
||||
`* ${renderDescription(commit)} ${renderLink(commit)} ${renderTrops(commit, excludeBranch)}\n`;
|
||||
return { note, link };
|
||||
};
|
||||
|
||||
const renderNotes = (notes) => {
|
||||
const renderNotes = (notes, explicitLinks) => {
|
||||
const rendered = [`# Release Notes for ${notes.name}\n\n`];
|
||||
|
||||
const renderSection = (title, commits) => {
|
||||
if (commits.length > 0) {
|
||||
rendered.push(
|
||||
`## ${title}\n\n`,
|
||||
...(commits.map(commit => renderNote(commit, notes.toBranch)).sort())
|
||||
);
|
||||
if (commits.length === 0) {
|
||||
return;
|
||||
}
|
||||
const notes = new Map();
|
||||
for (const note of commits.map(commit => renderCommit(commit, explicitLinks))) {
|
||||
if (!notes.has(note.note)) {
|
||||
notes.set(note.note, [note.link]);
|
||||
} else {
|
||||
notes.get(note.note).push(note.link);
|
||||
}
|
||||
}
|
||||
rendered.push(`## ${title}\n\n`);
|
||||
const lines = [];
|
||||
notes.forEach((links, key) => lines.push(` * ${key} ${links.map(link => link.toString()).sort().join(', ')}\n`));
|
||||
rendered.push(...lines.sort(), '\n');
|
||||
};
|
||||
|
||||
renderSection('Breaking Changes', notes.breaking);
|
||||
@@ -593,7 +622,7 @@ const renderNotes = (notes) => {
|
||||
renderSection('Other Changes', notes.other);
|
||||
|
||||
if (notes.docs.length) {
|
||||
const docs = notes.docs.map(commit => renderLink(commit)).sort();
|
||||
const docs = notes.docs.map(commit => renderLink(commit, explicitLinks)).sort();
|
||||
rendered.push('## Documentation\n\n', ` * Documentation changes: ${docs.join(', ')}\n`, '\n');
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"status":200,"url":"https://api.github.com/repos/electron/electron/issues/21891/comments?per_page=100","headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset","cache-control":"private, max-age=60, s-maxage=60","connection":"close","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Tue, 26 May 2020 04:07:22 GMT","etag":"W/\"f3361c5c493edbcc6774be228131a636\"","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"GitHub.com","status":"200 OK","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With","x-accepted-oauth-scopes":"","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"8F4E:231E:52E8BF:8AF8CD:5ECC95F9","x-oauth-scopes":"repo","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4997","x-ratelimit-reset":"1590469446","x-xss-protection":"1; mode=block"},"data":[{"url":"https://api.github.com/repos/electron/electron/issues/comments/579570143","html_url":"https://github.com/electron/electron/pull/21891#issuecomment-579570143","issue_url":"https://api.github.com/repos/electron/electron/issues/21891","id":579570143,"node_id":"MDEyOklzc3VlQ29tbWVudDU3OTU3MDE0Mw==","user":{"login":"bitdisaster","id":5191943,"node_id":"MDQ6VXNlcjUxOTE5NDM=","avatar_url":"https://avatars3.githubusercontent.com/u/5191943?v=4","gravatar_id":"","url":"https://api.github.com/users/bitdisaster","html_url":"https://github.com/bitdisaster","followers_url":"https://api.github.com/users/bitdisaster/followers","following_url":"https://api.github.com/users/bitdisaster/following{/other_user}","gists_url":"https://api.github.com/users/bitdisaster/gists{/gist_id}","starred_url":"https://api.github.com/users/bitdisaster/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/bitdisaster/subscriptions","organizations_url":"https://api.github.com/users/bitdisaster/orgs","repos_url":"https://api.github.com/users/bitdisaster/repos","events_url":"https://api.github.com/users/bitdisaster/events{/privacy}","received_events_url":"https://api.github.com/users/bitdisaster/received_events","type":"User","site_admin":false},"created_at":"2020-01-29T02:58:25Z","updated_at":"2020-01-29T02:58:25Z","author_association":"MEMBER","body":"@zcbenz I solved the mac/linux problem a bit differently. @MarshallOfSound recommended the use of converter to me and I like the approach. Does the typedef via conditional compiling work for you to?"},{"url":"https://api.github.com/repos/electron/electron/issues/comments/580589854","html_url":"https://github.com/electron/electron/pull/21891#issuecomment-580589854","issue_url":"https://api.github.com/repos/electron/electron/issues/21891","id":580589854,"node_id":"MDEyOklzc3VlQ29tbWVudDU4MDU4OTg1NA==","user":{"login":"release-clerk[bot]","id":42386326,"node_id":"MDM6Qm90NDIzODYzMjY=","avatar_url":"https://avatars0.githubusercontent.com/in/16104?v=4","gravatar_id":"","url":"https://api.github.com/users/release-clerk%5Bbot%5D","html_url":"https://github.com/apps/release-clerk","followers_url":"https://api.github.com/users/release-clerk%5Bbot%5D/followers","following_url":"https://api.github.com/users/release-clerk%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/release-clerk%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/release-clerk%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/release-clerk%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/release-clerk%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/release-clerk%5Bbot%5D/repos","events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2020-01-31T05:37:07Z","updated_at":"2020-01-31T05:37:07Z","author_association":"NONE","body":"**Release Notes Persisted**\n\n> Added GUID parameter to Tray API to avoid system tray icon demotion on Windows "}]}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"status":200,"url":"https://api.github.com/repos/electron/electron/issues/22750/comments?per_page=100","headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset","cache-control":"private, max-age=60, s-maxage=60","connection":"close","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Tue, 26 May 2020 17:01:55 GMT","etag":"W/\"89f9bf1ce7fb984e50e64f9d80c482cb\"","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"GitHub.com","status":"200 OK","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With","x-accepted-oauth-scopes":"","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"B828:443A:D1E7FE:149D73A:5ECD4B7D","x-oauth-scopes":"repo","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4990","x-ratelimit-reset":"1590514321","x-xss-protection":"1; mode=block"},"data":[{"url":"https://api.github.com/repos/electron/electron/issues/comments/602930802","html_url":"https://github.com/electron/electron/pull/22750#issuecomment-602930802","issue_url":"https://api.github.com/repos/electron/electron/issues/22750","id":602930802,"node_id":"MDEyOklzc3VlQ29tbWVudDYwMjkzMDgwMg==","user":{"login":"loc","id":1815863,"node_id":"MDQ6VXNlcjE4MTU4NjM=","avatar_url":"https://avatars2.githubusercontent.com/u/1815863?v=4","gravatar_id":"","url":"https://api.github.com/users/loc","html_url":"https://github.com/loc","followers_url":"https://api.github.com/users/loc/followers","following_url":"https://api.github.com/users/loc/following{/other_user}","gists_url":"https://api.github.com/users/loc/gists{/gist_id}","starred_url":"https://api.github.com/users/loc/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/loc/subscriptions","organizations_url":"https://api.github.com/users/loc/orgs","repos_url":"https://api.github.com/users/loc/repos","events_url":"https://api.github.com/users/loc/events{/privacy}","received_events_url":"https://api.github.com/users/loc/received_events","type":"User","site_admin":false},"created_at":"2020-03-24T00:23:09Z","updated_at":"2020-03-24T00:23:09Z","author_association":"MEMBER","body":"@zcbenz okay, I believe this is good to go."},{"url":"https://api.github.com/repos/electron/electron/issues/comments/603592578","html_url":"https://github.com/electron/electron/pull/22750#issuecomment-603592578","issue_url":"https://api.github.com/repos/electron/electron/issues/22750","id":603592578,"node_id":"MDEyOklzc3VlQ29tbWVudDYwMzU5MjU3OA==","user":{"login":"release-clerk[bot]","id":42386326,"node_id":"MDM6Qm90NDIzODYzMjY=","avatar_url":"https://avatars0.githubusercontent.com/in/16104?v=4","gravatar_id":"","url":"https://api.github.com/users/release-clerk%5Bbot%5D","html_url":"https://github.com/apps/release-clerk","followers_url":"https://api.github.com/users/release-clerk%5Bbot%5D/followers","following_url":"https://api.github.com/users/release-clerk%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/release-clerk%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/release-clerk%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/release-clerk%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/release-clerk%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/release-clerk%5Bbot%5D/repos","events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2020-03-25T01:40:16Z","updated_at":"2020-03-25T01:40:16Z","author_association":"NONE","body":"**Release Notes Persisted**\n\n> Added workaround for nativeWindowOpen hang."}]}
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +0,0 @@
|
||||
{"status":200,"url":"https://api.github.com/repos/electron/electron/issues/22828/comments?per_page=100","headers":{"access-control-allow-origin":"*","access-control-expose-headers":"ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, Deprecation, Sunset","cache-control":"private, max-age=60, s-maxage=60","connection":"close","content-encoding":"gzip","content-security-policy":"default-src 'none'","content-type":"application/json; charset=utf-8","date":"Tue, 26 May 2020 16:42:42 GMT","etag":"W/\"9da63627de4ed4f8b7929f66f9b6aa2b\"","referrer-policy":"origin-when-cross-origin, strict-origin-when-cross-origin","server":"GitHub.com","status":"200 OK","strict-transport-security":"max-age=31536000; includeSubdomains; preload","transfer-encoding":"chunked","vary":"Accept, Authorization, Cookie, X-GitHub-OTP, Accept-Encoding, Accept, X-Requested-With","x-accepted-oauth-scopes":"","x-content-type-options":"nosniff","x-frame-options":"deny","x-github-media-type":"github.v3; format=json","x-github-request-id":"C9A8:3A3F:B1FB:13DF6:5ECD46FB","x-oauth-scopes":"repo","x-ratelimit-limit":"5000","x-ratelimit-remaining":"4994","x-ratelimit-reset":"1590514322","x-xss-protection":"1; mode=block"},"data":[{"url":"https://api.github.com/repos/electron/electron/issues/comments/603916187","html_url":"https://github.com/electron/electron/pull/22828#issuecomment-603916187","issue_url":"https://api.github.com/repos/electron/electron/issues/22828","id":603916187,"node_id":"MDEyOklzc3VlQ29tbWVudDYwMzkxNjE4Nw==","user":{"login":"release-clerk[bot]","id":42386326,"node_id":"MDM6Qm90NDIzODYzMjY=","avatar_url":"https://avatars0.githubusercontent.com/in/16104?v=4","gravatar_id":"","url":"https://api.github.com/users/release-clerk%5Bbot%5D","html_url":"https://github.com/apps/release-clerk","followers_url":"https://api.github.com/users/release-clerk%5Bbot%5D/followers","following_url":"https://api.github.com/users/release-clerk%5Bbot%5D/following{/other_user}","gists_url":"https://api.github.com/users/release-clerk%5Bbot%5D/gists{/gist_id}","starred_url":"https://api.github.com/users/release-clerk%5Bbot%5D/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/release-clerk%5Bbot%5D/subscriptions","organizations_url":"https://api.github.com/users/release-clerk%5Bbot%5D/orgs","repos_url":"https://api.github.com/users/release-clerk%5Bbot%5D/repos","events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/events{/privacy}","received_events_url":"https://api.github.com/users/release-clerk%5Bbot%5D/received_events","type":"Bot","site_admin":false},"created_at":"2020-03-25T15:45:36Z","updated_at":"2020-03-25T15:45:36Z","author_association":"NONE","body":"**Release Notes Persisted**\n\n> don't allow window to go behind menu bar on mac"}]}
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -4,12 +4,10 @@
|
||||
"main": "index.js",
|
||||
"version": "0.1.0",
|
||||
"devDependencies": {
|
||||
"@types/sinon": "^9.0.4",
|
||||
"@types/ws": "^7.2.0",
|
||||
"busboy": "^0.3.1",
|
||||
"echo": "file:fixtures/native-addon/echo",
|
||||
"q": "^1.5.1",
|
||||
"sinon": "^9.0.1",
|
||||
"ws": "^7.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,183 +0,0 @@
|
||||
import { GitProcess, IGitExecutionOptions, IGitResult } from 'dugite';
|
||||
import { expect } from 'chai';
|
||||
import * as notes from '../script/release/notes/notes.js';
|
||||
import * as path from 'path';
|
||||
import * as sinon from 'sinon';
|
||||
|
||||
/* Fake a Dugite GitProcess that only returns the specific
|
||||
commits that we want to test */
|
||||
|
||||
class Commit {
|
||||
sha1: string;
|
||||
subject: string;
|
||||
constructor (sha1: string, subject: string) {
|
||||
this.sha1 = sha1;
|
||||
this.subject = subject;
|
||||
}
|
||||
}
|
||||
|
||||
class GitFake {
|
||||
branches: {
|
||||
[key: string]: Commit[],
|
||||
};
|
||||
|
||||
constructor () {
|
||||
this.branches = {};
|
||||
}
|
||||
|
||||
setBranch (name: string, commits: Array<Commit>): void {
|
||||
this.branches[name] = commits;
|
||||
}
|
||||
|
||||
// find the newest shared commit between branches a and b
|
||||
mergeBase (a: string, b:string): string {
|
||||
for (const commit of [...this.branches[a].reverse()]) {
|
||||
if (this.branches[b].map((commit: Commit) => commit.sha1).includes(commit.sha1)) {
|
||||
return commit.sha1;
|
||||
}
|
||||
}
|
||||
console.error('test error: branches not related');
|
||||
return '';
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
exec (args: string[], path: string, options?: IGitExecutionOptions | undefined): Promise<IGitResult> {
|
||||
let stdout = '';
|
||||
const stderr = '';
|
||||
const exitCode = 0;
|
||||
|
||||
if (args.length === 3 && args[0] === 'merge-base') {
|
||||
// expected form: `git merge-base branchName1 branchName2`
|
||||
const a: string = args[1]!;
|
||||
const b: string = args[2]!;
|
||||
stdout = this.mergeBase(a, b);
|
||||
} else if (args.length === 3 && args[0] === 'log' && args[1] === '--format=%H') {
|
||||
// exepcted form: `git log --format=%H branchName
|
||||
const branch: string = args[2]!;
|
||||
stdout = this.branches[branch].map((commit: Commit) => commit.sha1).join('\n');
|
||||
} else if (args.length > 1 && args[0] === 'log' && args.includes('--format=%H,%s')) {
|
||||
// expected form: `git log --format=%H,%s sha1..branchName
|
||||
const [start, branch] = args[args.length - 1].split('..');
|
||||
const lines : string[] = [];
|
||||
let started = false;
|
||||
for (const commit of this.branches[branch]) {
|
||||
started = started || commit.sha1 === start;
|
||||
if (started) {
|
||||
lines.push(`${commit.sha1},${commit.subject}` /* %H,%s */);
|
||||
}
|
||||
}
|
||||
stdout = lines.join('\n');
|
||||
} else if (args.length === 6 &&
|
||||
args[0] === 'branch' &&
|
||||
args[1] === '--all' &&
|
||||
args[2] === '--contains' &&
|
||||
args[3].endsWith('-x-y')) {
|
||||
// "what branch is this tag in?"
|
||||
// git branch --all --contains ${ref} --sort version:refname
|
||||
stdout = args[3];
|
||||
} else {
|
||||
console.error('unhandled GitProcess.exec():', args);
|
||||
}
|
||||
|
||||
return Promise.resolve({ exitCode, stdout, stderr });
|
||||
}
|
||||
}
|
||||
|
||||
describe('release notes', () => {
|
||||
const sandbox = sinon.createSandbox();
|
||||
const gitFake = new GitFake();
|
||||
|
||||
const oldBranch = '8-x-y';
|
||||
const newBranch = '9-x-y';
|
||||
|
||||
// commits shared by both oldBranch and newBranch
|
||||
const sharedHistory = [
|
||||
new Commit('2abea22b4bffa1626a521711bacec7cd51425818', "fix: explicitly cancel redirects when mode is 'error' (#20686)"),
|
||||
new Commit('467409458e716c68b35fa935d556050ca6bed1c4', 'build: add support for automated minor releases (#20620)') // merge-base
|
||||
];
|
||||
|
||||
// these commits came after newBranch was created
|
||||
const newBreaking = new Commit('2fad53e66b1a2cb6f7dad88fe9bb62d7a461fe98', 'refactor: use v8 serialization for ipc (#20214)');
|
||||
const newFeat = new Commit('89eb309d0b22bd4aec058ffaf983e81e56a5c378', 'feat: allow GUID parameter to avoid systray demotion on Windows (#21891)');
|
||||
const newFix = new Commit('0600420bac25439fc2067d51c6aaa4ee11770577', "fix: don't allow window to go behind menu bar on mac (#22828)");
|
||||
const oldFix = new Commit('f77bd19a70ac2d708d17ddbe4dc12745ca3a8577', 'fix: prevent menu gc during popup (#20785)');
|
||||
|
||||
// a bug that's fixed in both branches by separate PRs
|
||||
const newTropFix = new Commit('a6ff42c190cb5caf8f3e217748e49183a951491b', 'fix: workaround for hang when preventDefault-ing nativeWindowOpen (#22750)');
|
||||
const oldTropFix = new Commit('8751f485c5a6c8c78990bfd55a4350700f81f8cd', 'fix: workaround for hang when preventDefault-ing nativeWindowOpen (#22749)');
|
||||
|
||||
before(() => {
|
||||
// location of relase-notes' octokit reply cache
|
||||
const fixtureDir = path.resolve(__dirname, 'fixtures', 'release-notes');
|
||||
process.env.NOTES_CACHE_PATH = path.resolve(fixtureDir, 'cache');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const wrapper = (args: string[], path: string, options?: IGitExecutionOptions | undefined) => gitFake.exec(args, path, options);
|
||||
sandbox.replace(GitProcess, 'exec', wrapper);
|
||||
|
||||
gitFake.setBranch(oldBranch, [...sharedHistory, oldFix]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore();
|
||||
});
|
||||
|
||||
describe('trop annotations', () => {
|
||||
it('shows sibling branches', async function () {
|
||||
const version = 'v9.0.0';
|
||||
gitFake.setBranch(oldBranch, [...sharedHistory, oldTropFix]);
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, newTropFix]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.fix).to.have.lengthOf(1);
|
||||
console.log(results.fix);
|
||||
expect(results.fix[0].trops).to.have.keys('8-x-y', '9-x-y');
|
||||
});
|
||||
});
|
||||
|
||||
// use case: A malicious contributor could edit the text of their 'Notes:'
|
||||
// in the PR body after a PR's been merged and the maintainers have moved on.
|
||||
// So instead always use the release-clerk PR comment
|
||||
it('uses the release-clerk text', async function () {
|
||||
// realText source: ${fixtureDir}/electron-electron-issue-21891-comments
|
||||
const realText = 'Added GUID parameter to Tray API to avoid system tray icon demotion on Windows';
|
||||
const testCommit = new Commit('89eb309d0b22bd4aec058ffaf983e81e56a5c378', 'feat: lole u got troled hard (#21891)');
|
||||
const version = 'v9.0.0';
|
||||
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.feat).to.have.lengthOf(1);
|
||||
expect(results.feat[0].hash).to.equal(testCommit.sha1);
|
||||
expect(results.feat[0].note).to.equal(realText);
|
||||
});
|
||||
|
||||
// test that when you feed in different semantic commit types,
|
||||
// the parser returns them in the results' correct category
|
||||
describe('semantic commit', () => {
|
||||
const version = 'v9.0.0';
|
||||
|
||||
it("honors 'feat' type", async function () {
|
||||
const testCommit = newFeat;
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.feat).to.have.lengthOf(1);
|
||||
expect(results.feat[0].hash).to.equal(testCommit.sha1);
|
||||
});
|
||||
|
||||
it("honors 'fix' type", async function () {
|
||||
const testCommit = newFix;
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.fix).to.have.lengthOf(1);
|
||||
expect(results.fix[0].hash).to.equal(testCommit.sha1);
|
||||
});
|
||||
|
||||
it("honors 'BREAKING CHANGE' message", async function () {
|
||||
const testCommit = newBreaking;
|
||||
gitFake.setBranch(newBranch, [...sharedHistory, testCommit]);
|
||||
const results: any = await notes.get(oldBranch, newBranch, version);
|
||||
expect(results.breaking).to.have.lengthOf(1);
|
||||
expect(results.breaking[0].hash).to.equal(testCommit.sha1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -2,59 +2,11 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0", "@sinonjs/commons@^1.7.2":
|
||||
version "1.8.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217"
|
||||
integrity sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==
|
||||
dependencies:
|
||||
type-detect "4.0.8"
|
||||
|
||||
"@sinonjs/fake-timers@^6.0.0", "@sinonjs/fake-timers@^6.0.1":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz#293674fccb3262ac782c7aadfdeca86b10c75c40"
|
||||
integrity sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
|
||||
"@sinonjs/formatio@^5.0.1":
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/formatio/-/formatio-5.0.1.tgz#f13e713cb3313b1ab965901b01b0828ea6b77089"
|
||||
integrity sha512-KaiQ5pBf1MpS09MuA0kp6KBQt2JUOQycqVG1NZXvzeaXe5LGFqAKueIS0bw4w0P9r7KuBSVdUk5QjXsUdu2CxQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1"
|
||||
"@sinonjs/samsam" "^5.0.2"
|
||||
|
||||
"@sinonjs/samsam@^5.0.2", "@sinonjs/samsam@^5.0.3":
|
||||
version "5.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/samsam/-/samsam-5.0.3.tgz#86f21bdb3d52480faf0892a480c9906aa5a52938"
|
||||
integrity sha512-QucHkc2uMJ0pFGjJUDP3F9dq5dx8QIaqISl9QgwLOh6P9yv877uONPGXh/OH/0zmM3tW1JjuJltAZV2l7zU+uQ==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.6.0"
|
||||
lodash.get "^4.4.2"
|
||||
type-detect "^4.0.8"
|
||||
|
||||
"@sinonjs/text-encoding@^0.7.1":
|
||||
version "0.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz#8da5c6530915653f3a1f38fd5f101d8c3f8079c5"
|
||||
integrity sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==
|
||||
|
||||
"@types/node@*":
|
||||
version "13.7.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.7.0.tgz#b417deda18cf8400f278733499ad5547ed1abec4"
|
||||
integrity sha512-GnZbirvmqZUzMgkFn70c74OQpTTUcCzlhQliTzYjQMqg+hVKcDnxdL19Ne3UdYzdMA/+W3eb646FWn/ZaT1NfQ==
|
||||
|
||||
"@types/sinon@^9.0.4":
|
||||
version "9.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-9.0.4.tgz#e934f904606632287a6e7f7ab0ce3f08a0dad4b1"
|
||||
integrity sha512-sJmb32asJZY6Z2u09bl0G2wglSxDlROlAejCjsnor+LzBMz17gu8IU7vKC/vWDnv9zEq2wqADHVXFjf4eE8Gdw==
|
||||
dependencies:
|
||||
"@types/sinonjs__fake-timers" "*"
|
||||
|
||||
"@types/sinonjs__fake-timers@*":
|
||||
version "6.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.1.tgz#681df970358c82836b42f989188d133e218c458e"
|
||||
integrity sha512-yYezQwGWty8ziyYLdZjwxyMb0CZR49h8JALHGrxjQHWlqGgc8kLdHEgWrgL0uZ29DMvEVBDnHU2Wg36zKSIUtA==
|
||||
|
||||
"@types/ws@^7.2.0":
|
||||
version "7.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/ws/-/ws-7.2.1.tgz#b800f2b8aee694e2b581113643e20d79dd3b8556"
|
||||
@@ -108,11 +60,6 @@ dicer@0.3.0:
|
||||
dependencies:
|
||||
streamsearch "0.1.2"
|
||||
|
||||
diff@^4.0.2:
|
||||
version "4.0.2"
|
||||
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
|
||||
integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==
|
||||
|
||||
dirty-chai@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/dirty-chai/-/dirty-chai-2.0.1.tgz#6b2162ef17f7943589da840abc96e75bda01aff3"
|
||||
@@ -136,16 +83,6 @@ fast-json-stable-stringify@^2.0.0:
|
||||
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
|
||||
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
|
||||
|
||||
has-flag@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
|
||||
integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
|
||||
|
||||
isarray@0.0.1:
|
||||
version "0.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
|
||||
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
|
||||
|
||||
json-schema-traverse@^0.4.1:
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||
@@ -158,11 +95,6 @@ json5@^1.0.1:
|
||||
dependencies:
|
||||
minimist "^1.2.0"
|
||||
|
||||
just-extend@^4.0.2:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.1.0.tgz#7278a4027d889601640ee0ce0e5a00b992467da4"
|
||||
integrity sha512-ApcjaOdVTJ7y4r08xI5wIqpvwS48Q0PBG4DJROcEkH1f8MdAiNFyFxz3xoL0LWAVwjrwPYZdVHHxhRHcx/uGLA==
|
||||
|
||||
loader-utils@^1.0.0:
|
||||
version "1.2.3"
|
||||
resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7"
|
||||
@@ -172,39 +104,16 @@ loader-utils@^1.0.0:
|
||||
emojis-list "^2.0.0"
|
||||
json5 "^1.0.1"
|
||||
|
||||
lodash.get@^4.4.2:
|
||||
version "4.4.2"
|
||||
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||
|
||||
minimist@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
|
||||
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
|
||||
|
||||
nise@^4.0.1:
|
||||
version "4.0.4"
|
||||
resolved "https://registry.yarnpkg.com/nise/-/nise-4.0.4.tgz#d73dea3e5731e6561992b8f570be9e363c4512dd"
|
||||
integrity sha512-bTTRUNlemx6deJa+ZyoCUTRvH3liK5+N6VQZ4NIw90AgDXY6iPnsqplNFf6STcj+ePk0H/xqxnP75Lr0J0Fq3A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.0"
|
||||
"@sinonjs/fake-timers" "^6.0.0"
|
||||
"@sinonjs/text-encoding" "^0.7.1"
|
||||
just-extend "^4.0.2"
|
||||
path-to-regexp "^1.7.0"
|
||||
|
||||
node-ensure@^0.0.0:
|
||||
version "0.0.0"
|
||||
resolved "https://registry.yarnpkg.com/node-ensure/-/node-ensure-0.0.0.tgz#ecae764150de99861ec5c810fd5d096b183932a7"
|
||||
integrity sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=
|
||||
|
||||
path-to-regexp@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a"
|
||||
integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==
|
||||
dependencies:
|
||||
isarray "0.0.1"
|
||||
|
||||
pdfjs-dist@^2.2.228:
|
||||
version "2.2.228"
|
||||
resolved "https://registry.yarnpkg.com/pdfjs-dist/-/pdfjs-dist-2.2.228.tgz#777b068a0a16c96418433303807c183058b47aaa"
|
||||
@@ -231,36 +140,11 @@ schema-utils@^0.4.0:
|
||||
ajv "^6.1.0"
|
||||
ajv-keywords "^3.1.0"
|
||||
|
||||
sinon@^9.0.1:
|
||||
version "9.0.2"
|
||||
resolved "https://registry.yarnpkg.com/sinon/-/sinon-9.0.2.tgz#b9017e24633f4b1c98dfb6e784a5f0509f5fd85d"
|
||||
integrity sha512-0uF8Q/QHkizNUmbK3LRFqx5cpTttEVXudywY9Uwzy8bTfZUhljZ7ARzSxnRHWYWtVTeh4Cw+tTb3iU21FQVO9A==
|
||||
dependencies:
|
||||
"@sinonjs/commons" "^1.7.2"
|
||||
"@sinonjs/fake-timers" "^6.0.1"
|
||||
"@sinonjs/formatio" "^5.0.1"
|
||||
"@sinonjs/samsam" "^5.0.3"
|
||||
diff "^4.0.2"
|
||||
nise "^4.0.1"
|
||||
supports-color "^7.1.0"
|
||||
|
||||
streamsearch@0.1.2:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a"
|
||||
integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=
|
||||
|
||||
supports-color@^7.1.0:
|
||||
version "7.1.0"
|
||||
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
|
||||
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
|
||||
dependencies:
|
||||
has-flag "^4.0.0"
|
||||
|
||||
type-detect@4.0.8, type-detect@^4.0.8:
|
||||
version "4.0.8"
|
||||
resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
|
||||
integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
|
||||
|
||||
uri-js@^4.2.2:
|
||||
version "4.2.2"
|
||||
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
|
||||
|
||||
Reference in New Issue
Block a user