2019-07-30 09:35:42 +02:00
'use strict' ;
2019-04-22 20:02:45 +02:00
2019-04-28 16:09:07 +02:00
// Supported commit formats:
// (Desktop|Mobile|Android|iOS[CLI): (New|Improved|Fixed): Some message..... (#ISSUE)
2019-04-22 20:02:45 +02:00
require ( 'app-module-path' ) . addPath ( _ _dirname + '/../ReactNativeClient' ) ;
const { execCommand } = require ( './tool-utils.js' ) ;
2019-05-06 23:18:17 +02:00
async function gitLog ( sinceTag ) {
let lines = await execCommand ( 'git log --pretty=format:"%H:%s" ' + sinceTag + '..HEAD' ) ;
lines = lines . split ( '\n' ) ;
const output = [ ] ;
for ( const line of lines ) {
const splitted = line . split ( ':' ) ;
const commit = splitted [ 0 ] ;
2019-07-30 09:35:42 +02:00
const message = line . substr ( commit . length + 1 ) . trim ( ) ;
2019-05-06 23:18:17 +02:00
output . push ( {
commit : commit ,
message : message ,
} ) ;
}
2019-04-22 20:02:45 +02:00
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 = [ ] ;
2019-04-29 08:42:40 +02:00
const revertedLogs = [ ] ;
2019-04-22 20:02:45 +02:00
for ( const log of logs ) {
2019-04-29 08:42:40 +02:00
// 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 ;
2019-05-03 01:19:42 +02:00
let prefix = log . message . trim ( ) . toLowerCase ( ) . split ( ':' ) ;
if ( prefix . length <= 1 ) continue ;
prefix = prefix [ 0 ] . split ( ',' ) . map ( s => s . trim ( ) ) ;
2019-04-22 20:02:45 +02:00
let addIt = false ;
2019-05-03 01:19:42 +02:00
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 ;
2019-04-22 20:02:45 +02:00
if ( addIt ) output . push ( log ) ;
}
return output ;
}
function formatCommitMessage ( msg ) {
let output = '' ;
2019-07-30 09:35:42 +02:00
2019-04-22 20:02:45 +02:00
const splitted = msg . split ( ':' ) ;
2019-05-03 17:02:32 +02:00
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 ;
2019-07-30 09:35:42 +02:00
} ;
2019-05-03 17:02:32 +02:00
2019-04-22 20:02:45 +02:00
if ( splitted . length ) {
const platform = splitted [ 0 ] . trim ( ) . toLowerCase ( ) ;
2019-05-03 17:02:32 +02:00
if ( isPlatformPrefix ( platform ) ) {
2019-04-22 20:02:45 +02:00
splitted . splice ( 0 , 1 ) ;
}
output = splitted . join ( ':' ) ;
}
output = output . split ( '\n' ) [ 0 ] . trim ( ) ;
2019-04-28 16:09:07 +02:00
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' ;
2019-07-30 09:35:42 +02:00
} ;
2019-04-28 16:09:07 +02:00
const parseCommitMessage = ( msg ) => {
const parts = msg . split ( ':' ) ;
if ( parts . length === 1 ) {
return {
type : detectType ( msg ) ,
message : msg . trim ( ) ,
} ;
}
2019-05-06 23:18:17 +02:00
let t = parts [ 0 ] . trim ( ) . toLowerCase ( ) ;
2019-04-28 16:09:07 +02:00
parts . splice ( 0 , 1 ) ;
2019-05-06 23:18:17 +02:00
let message = parts . join ( ':' ) . trim ( ) ;
2019-04-28 16:09:07 +02:00
let type = null ;
2019-05-06 23:18:17 +02:00
// eg. "All: Resolves #712: New: Support for note history (#1415)"
2019-07-30 09:35:42 +02:00
// "Resolves" doesn't tell us if it's new or improved so check the
2019-05-06 23:18:17 +02:00
// third token (which in this case is "new").
if ( t . indexOf ( 'resolves' ) === 0 && [ 'new' , 'improved' , 'fixed' ] . indexOf ( parts [ 0 ] . trim ( ) . toLowerCase ( ) ) >= 0 ) {
t = parts [ 0 ] . trim ( ) . toLowerCase ( ) ;
parts . splice ( 0 , 1 ) ;
message = parts . join ( ':' ) . trim ( ) ;
}
2019-04-28 16:09:07 +02:00
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 ) ;
2019-07-30 09:35:42 +02:00
2019-04-28 16:09:07 +02:00
let issueNumber = output . match ( /#(\d+)/ ) ;
issueNumber = issueNumber && issueNumber . length >= 2 ? issueNumber [ 1 ] : null ;
return {
type : type ,
message : message ,
issueNumber : issueNumber ,
} ;
2019-07-30 09:35:42 +02:00
} ;
2019-04-28 16:09:07 +02:00
const commitMessage = parseCommitMessage ( output ) ;
output = capitalizeFirstLetter ( commitMessage . type ) + ': ' + capitalizeFirstLetter ( commitMessage . message ) ;
if ( commitMessage . issueNumber ) {
2019-07-30 09:35:42 +02:00
const formattedIssueNum = '(#' + commitMessage . issueNumber + ')' ;
2019-04-28 16:09:07 +02:00
if ( output . indexOf ( formattedIssueNum ) < 0 ) output += ' ' + formattedIssueNum ;
}
2019-04-22 20:02:45 +02:00
return output ;
}
function createChangeLog ( logs ) {
const output = [ ] ;
for ( const log of logs ) {
output . push ( formatCommitMessage ( log . message ) ) ;
}
return output ;
}
function capitalizeFirstLetter ( string ) {
2019-07-30 09:35:42 +02:00
return string . charAt ( 0 ) . toUpperCase ( ) + string . slice ( 1 ) ;
2019-04-22 20:02:45 +02:00
}
2019-09-08 18:54:41 +02:00
function decreaseTagVersion ( tag ) {
const s = tag . split ( '.' ) ;
let num = Number ( s . pop ( ) ) ;
num -- ;
if ( num < 0 ) throw new Error ( 'Cannot decrease tag version: ' + tag ) ;
s . push ( '' + num ) ;
return s . join ( '.' ) ;
}
// This function finds the first relevant tag starting from the given tag.
// The first "relevant tag" is the one that exists, and from which there are changes.
async function findFirstRelevantTag ( baseTag ) {
let tag = decreaseTagVersion ( baseTag ) ;
while ( true ) {
try {
const logs = await gitLog ( tag ) ;
if ( logs . length ) return tag ;
} catch ( error ) {
if ( error . message . indexOf ( 'unknown revision' ) >= 0 ) {
// We skip the error - it means this particular tag has never been created
} else {
throw error ;
}
}
tag = decreaseTagVersion ( tag ) ;
}
}
2019-04-22 20:02:45 +02:00
async function main ( ) {
const argv = require ( 'yargs' ) . argv ;
2019-09-08 18:54:41 +02:00
if ( ! argv . _ . length ) throw new Error ( 'Tag name must be specified. Provide the tag of the new version and git-changelog will walk backward to find the changes to the previous relevant tag.' ) ;
const fromTagName = argv . _ [ 0 ] ;
const platform = platformFromTag ( fromTagName ) ;
const toTagName = await findFirstRelevantTag ( fromTagName ) ;
2019-04-22 20:02:45 +02:00
2019-09-08 18:54:41 +02:00
const logsSinceTags = await gitLog ( toTagName ) ;
2019-04-22 20:02:45 +02:00
const filteredLogs = filterLogs ( logsSinceTags , platform ) ;
2019-04-28 16:09:07 +02:00
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 ) ;
}
}
2019-04-22 20:02:45 +02:00
2019-04-28 16:09:07 +02:00
changelog = [ ] . concat ( changelogNews ) . concat ( changelogImproves ) . concat ( changelogFixes ) ;
2019-04-22 20:02:45 +02:00
const changelogString = changelog . map ( l => '- ' + l ) ;
console . info ( changelogString . join ( '\n' ) ) ;
}
main ( ) . catch ( ( error ) => {
console . error ( 'Fatal error' ) ;
console . error ( error ) ;
process . exit ( 1 ) ;
} ) ;