• 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].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