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].toLowerCase(), { 45 author: match[1], email: match[2] 46 }); 47 // <replaced@example.com> <original@example.com> 48 } else if (match = line.match(/^<([^>]+)>\s+(<[^>]+>)$/)) { 49 mailmap.set(match[2].toLowerCase(), { email: match[1] }); 50 // Replaced Name <replaced@example.com> <original@example.com> 51 } else if (match = line.match(/^([^<]+)\s+(<[^>]+>)\s+(<[^>]+>)$/)) { 52 mailmap.set(match[3].toLowerCase(), { 53 author: match[1], email: match[2] 54 }); 55 // Replaced Name <replaced@example.com> Original Name <original@example.com> 56 } else if (match = 57 line.match(/^([^<]+)\s+(<[^>]+>)\s+([^<]+)\s+(<[^>]+>)$/)) { 58 mailmap.set(match[3] + '\0' + match[4].toLowerCase(), { 59 author: match[1], email: match[2] 60 }); 61 } else { 62 console.warn('Unknown .mailmap format:', line); 63 } 64 } 65} 66 67const seen = new Set(); 68 69// Support regular git author metadata, as well as `Author:` and 70// `Co-authored-by:` in the message body. Both have been used in the past 71// to indicate multiple authors per commit, with the latter standardized 72// by GitHub now. 73const authorRe = 74 /(^Author:|^Co-authored-by:)\s+(?<author>[^<]+)\s+(?<email><[^>]+>)/i; 75rl.on('line', (line) => { 76 const match = line.match(authorRe); 77 if (!match) return; 78 79 let { author, email } = match.groups; 80 const emailLower = email.toLowerCase(); 81 82 const replacement = 83 mailmap.get(author + '\0' + emailLower) || mailmap.get(emailLower); 84 if (replacement) { 85 ({ author, email } = { author, email, ...replacement }); 86 } 87 88 if (seen.has(email) || 89 /@chromium\.org/.test(email) || 90 email === '<erik.corry@gmail.com>') { 91 return; 92 } 93 94 seen.add(email); 95 output.write(`${author} ${email}\n`); 96}); 97 98rl.on('close', () => { 99 output.end('\n# Generated by tools/update-authors.js\n'); 100}); 101