1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00
joplin/Tools/git-changelog.js

251 lines
6.6 KiB
JavaScript

"use strict"
// Supported commit formats:
// (Desktop|Mobile|Android|iOS[CLI): (New|Improved|Fixed): Some message..... (#ISSUE)
// Requires git2json: https://github.com/tarmstrong/git2json
require('app-module-path').addPath(__dirname + '/../ReactNativeClient');
const rootDir = __dirname + '/..';
const { basename, dirname, filename, fileExtension } = require(rootDir + '/ReactNativeClient/lib/path-utils.js');
const fs = require('fs-extra');
const { execCommand } = require('./tool-utils.js');
const { sprintf } = require('sprintf-js');
async function gitTags() {
const r = await execCommand('git tag --format="%(objectname) %(refname:short)" --sort=-creatordate');
const output = [];
const lines = r.split('\n');
for (const line of lines) {
const s = line.split(' ');
if (s.length !== 2) throw new Error('Unexpected log line format: ' + line);
output.push({
hash: s[0].trim(),
name: s[1].trim(),
});
}
return output;
}
async function gitLog() {
await execCommand('git2json > gitlog.json');
const output = await fs.readJson(rootDir + '/gitlog.json');
if (!output || !output.length) throw new Error('Could not read git log or could not generate gitlog.json');
await fs.remove('gitlog.json');
return output;
}
async function gitLogSinceTag(logs, tagHash) {
const output = [];
let found = false;
for (const log of logs) {
if (log.commit === tagHash) {
found = true;
break;
}
output.push(log);
}
if (!found) throw new Error('Could not find tag hash: ' + tagHash);
return output;
}
function platformFromTag(tagName) {
if (tagName.indexOf('v') === 0) return 'desktop';
if (tagName.indexOf('android') >= 0) return 'android';
if (tagName.indexOf('ios') >= 0) return 'ios';
if (tagName.indexOf('clipper') === 0) return 'clipper';
if (tagName.indexOf('cli') === 0) return 'cli';
throw new Error('Could not determine platform from tag: ' + tagName);
}
function filterLogs(logs, platform) {
const output = [];
const revertedLogs = [];
for (const log of logs) {
// Save to an array any commit that has been reverted, and exclude
// these from the final output array.
const revertMatches = log.message.split('\n')[0].trim().match(/^Revert "(.*?)"$/);
if (revertMatches && revertMatches.length >= 2) {
revertedLogs.push(revertMatches[1]);
continue;
}
if (revertedLogs.indexOf(log.message) >= 0) continue;
let prefix = log.message.trim().toLowerCase().split(':');
if (prefix.length <= 1) continue;
prefix = prefix[0].split(',').map(s => s.trim());
let addIt = false;
if (prefix.indexOf('all') >= 0 && platform !== 'clipper') addIt = true;
if ((platform === 'android' || platform === 'ios') && prefix.indexOf('mobile') >= 0) addIt = true;
if (platform === 'android' && prefix.indexOf('android') >= 0) addIt = true;
if (platform === 'ios' && prefix.indexOf('ios') >= 0) addIt = true;
if (platform === 'desktop' && prefix.indexOf('desktop') >= 0) addIt = true;
if (platform === 'cli' && prefix.indexOf('cli') >= 0) addIt = true;
if (platform === 'clipper' && prefix.indexOf('clipper') >= 0) addIt = true;
if (addIt) output.push(log);
}
return output;
}
function formatCommitMessage(msg) {
let output = '';
const splitted = msg.split(':');
const isPlatformPrefix = prefix => {
prefix = prefix.split(',').map(p => p.trim().toLowerCase());
for (const p of prefix) {
if (['android', 'mobile', 'ios', 'desktop', 'cli', 'clipper', 'all'].indexOf(p) >= 0) return true;
}
return false;
}
if (splitted.length) {
const platform = splitted[0].trim().toLowerCase();
if (isPlatformPrefix(platform)) {
splitted.splice(0, 1);
}
output = splitted.join(':');
}
output = output.split('\n')[0].trim();
const detectType = msg => {
msg = msg.trim().toLowerCase();
if (msg.indexOf('fix') === 0) return 'fixed';
if (msg.indexOf('add') === 0) return 'new';
if (msg.indexOf('change') === 0) return 'improved';
if (msg.indexOf('update') === 0) return 'improved';
if (msg.indexOf('improve') === 0) return 'improved';
return 'improved';
}
const parseCommitMessage = (msg) => {
const parts = msg.split(':');
const defaultType = 'improved';
if (parts.length === 1) {
return {
type: detectType(msg),
message: msg.trim(),
};
}
const t = parts[0].trim().toLowerCase();
parts.splice(0, 1);
const message = parts.join(':').trim();
let type = null;
if (t.indexOf('fix') === 0) type = 'fixed';
if (t.indexOf('new') === 0) type = 'new';
if (t.indexOf('improved') === 0) type = 'improved';
if (!type) type = detectType(message);
let issueNumber = output.match(/#(\d+)/);
issueNumber = issueNumber && issueNumber.length >= 2 ? issueNumber[1] : null;
return {
type: type,
message: message,
issueNumber: issueNumber,
};
}
const commitMessage = parseCommitMessage(output);
output = capitalizeFirstLetter(commitMessage.type) + ': ' + capitalizeFirstLetter(commitMessage.message);
if (commitMessage.issueNumber) {
const formattedIssueNum = '(#' + commitMessage.issueNumber + ')'
if (output.indexOf(formattedIssueNum) < 0) output += ' ' + formattedIssueNum;
}
return output;
}
function createChangeLog(logs) {
const output = [];
for (const log of logs) {
output.push(formatCommitMessage(log.message));
}
return output;
}
function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1);
}
async function main() {
const argv = require('yargs').argv;
if (!argv._.length) throw new Error('Tag name must be specified');
const sinceTagName = argv._[0];
const platform = platformFromTag(sinceTagName);
const logs = await gitLog();
const tags = await gitTags();
let sinceTagHash = null;
for (const tag of tags) {
if (tag.name === sinceTagName) {
sinceTagHash = tag.hash;
break;
}
}
if (!sinceTagHash) throw new Error('Could not find tag: ' + sinceTagName);
const logsSinceTags = await gitLogSinceTag(logs, sinceTagHash);
const filteredLogs = filterLogs(logsSinceTags, platform);
let changelog = createChangeLog(filteredLogs);
const changelogFixes = [];
const changelogImproves = [];
const changelogNews = [];
for (const l of changelog) {
if (l.indexOf('Fix') === 0) {
changelogFixes.push(l);
} else if (l.indexOf('Improve') === 0) {
changelogImproves.push(l);
} else if (l.indexOf('New') === 0) {
changelogNews.push(l);
} else {
throw new Error('Invalid changelog line: ' + l);
}
}
changelog = [].concat(changelogNews).concat(changelogImproves).concat(changelogFixes);
const changelogString = changelog.map(l => '- ' + l);
console.info(changelogString.join('\n'));
}
main().catch((error) => {
console.error('Fatal error');
console.error(error);
process.exit(1);
});