• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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