1#!/usr/bin/env node 2// Usage: tools/update-author.js [--dry] 3// Passing --dry will redirect output to stdout rather than write to 'AUTHORS'. 4'use strict'; 5const { spawn } = require('child_process'); 6const path = require('path'); 7const fs = require('fs'); 8const readline = require('readline'); 9 10class CaseIndifferentMap { 11 _map = new Map(); 12 13 get(key) { return this._map.get(key.toLowerCase()); } 14 set(key, value) { return this._map.set(key.toLowerCase(), value); } 15} 16 17const log = spawn( 18 'git', 19 // Inspect author name/email and body. 20 ['log', '--reverse', '--format=Author: %aN <%aE>\n%b'], { 21 stdio: ['inherit', 'pipe', 'inherit'] 22 }); 23const rl = readline.createInterface({ input: log.stdout }); 24 25let output; 26if (process.argv.includes('--dry')) 27 output = process.stdout; 28else 29 output = fs.createWriteStream('AUTHORS'); 30 31output.write('# Authors ordered by first contribution.\n\n'); 32 33const mailmap = new CaseIndifferentMap(); 34{ 35 const lines = fs.readFileSync(path.resolve(__dirname, '../', '.mailmap'), 36 { encoding: 'utf8' }).split('\n'); 37 for (let line of lines) { 38 line = line.trim(); 39 if (line.startsWith('#') || line === '') continue; 40 41 let match; 42 // Replaced Name <original@example.com> 43 if (match = line.match(/^([^<]+)\s+(<[^>]+>)$/)) { 44 mailmap.set(match[2], { author: match[1] }); 45 // <replaced@example.com> <original@example.com> 46 } else if (match = line.match(/^<([^>]+)>\s+(<[^>]+>)$/)) { 47 mailmap.set(match[2], { email: match[1] }); 48 // Replaced Name <replaced@example.com> <original@example.com> 49 } else if (match = line.match(/^([^<]+)\s+(<[^>]+>)\s+(<[^>]+>)$/)) { 50 mailmap.set(match[3], { 51 author: match[1], email: match[2] 52 }); 53 // Replaced Name <replaced@example.com> Original Name <original@example.com> 54 } else if (match = 55 line.match(/^([^<]+)\s+(<[^>]+>)\s+([^<]+)\s+(<[^>]+>)$/)) { 56 mailmap.set(match[3] + '\0' + match[4], { 57 author: match[1], email: match[2] 58 }); 59 } else { 60 console.warn('Unknown .mailmap format:', line); 61 } 62 } 63} 64 65const seen = new Set(); 66 67// Support regular git author metadata, as well as `Author:` and 68// `Co-authored-by:` in the message body. Both have been used in the past 69// to indicate multiple authors per commit, with the latter standardized 70// by GitHub now. 71const authorRe = 72 /(^Author:|^Co-authored-by:)\s+(?<author>[^<]+)\s+(?<email><[^>]+>)/i; 73rl.on('line', (line) => { 74 const match = line.match(authorRe); 75 if (!match) return; 76 77 let { author, email } = match.groups; 78 79 const replacement = mailmap.get(author + '\0' + email) || mailmap.get(email); 80 if (replacement) { 81 ({ author, email } = { author, email, ...replacement }); 82 } 83 84 if (seen.has(email) || 85 /@chromium\.org/.test(email) || 86 email === '<erik.corry@gmail.com>') { 87 return; 88 } 89 90 seen.add(email); 91 output.write(`${author} ${email}\n`); 92}); 93 94rl.on('close', () => { 95 output.end('\n# Generated by tools/update-authors.js\n'); 96}); 97