diff --git a/tools/find-inactive-collaborators.js b/tools/find-inactive-collaborators.js new file mode 100755 index 00000000000000..9c1297df109ce8 --- /dev/null +++ b/tools/find-inactive-collaborators.js @@ -0,0 +1,76 @@ +#!/usr/bin/env node +'use strict'; + +// Identify inactive collaborators. "Inactive" is not quite right, as the things +// this checks for are not the entirety of collaborator activities. Still, it is +// a pretty good proxy. Feel free to suggest or implement further metrics. + +const SINCE = process.argv[2] || '6 months ago'; + +const childProcess = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +function runGitCommand(command) { + return childProcess.execSync(command, { + cwd: path.join(__dirname, '..'), + encoding: 'utf8', + stdio: ['inherit', 'pipe', 'pipe'] + }); +} + +// Retrieve all commit authors during the time period. +const authors = runGitCommand(`git shortlog -n -s --since="${SINCE}"`) + .split('\n') + .map((line) => line.trim().split('\t', 2)[1]) + .filter((val) => !!val); + +// Retrieve all commit landers during the time period. +const landers = runGitCommand(`git shortlog -n -s -c --since="${SINCE}"`) + .split('\n') + .map((line) => line.trim().split('\t', 2)[1]) + .filter((val) => !!val); + +// Retrieve all approving reviewers of landed commits during the time period. +const approvingReviewers = runGitCommand( + `git log --since="${SINCE}" | egrep "^ Reviewed-By: " | sort | uniq ` +) + .split('\n') + .filter((line) => !!line) + .map((line) => /^ Reviewed-By: ([^<]+)/.exec(line)[1].trim()); + +// Retrieve list of current collaborators from README.md. +const readmeText = fs.readFileSync( + path.resolve(__dirname, '..', 'README.md'), + 'utf8' +); +let processingCollaborators = false; +const collaborators = readmeText + .split('\n') + .filter((line) => { + const isCollaborator = processingCollaborators && line.length; + if (line === '### Collaborators') { + processingCollaborators = true; + } + if (line === '### Collaborator emeriti') { + processingCollaborators = false; + } + return line.startsWith('**') && isCollaborator; + }) + .map((line) => line.split('**')[1].trim()); + +console.log(`${authors.length.toLocaleString()} authors have made commits since ${SINCE}.`); +console.log(`${landers.length.toLocaleString()} landers have landed commits since ${SINCE}.`); +console.log(`${approvingReviewers.length.toLocaleString()} reviewers have approved landed commits since ${SINCE}.`); +console.log(`${collaborators.length.toLocaleString()} collaborators currently in the project.`); + +const inactive = collaborators.filter((collaborator) => + !authors.includes(collaborator) && + !landers.includes(collaborator) && + !approvingReviewers.includes(collaborator) +); + +if (inactive.length) { + console.log('\nInactive collaborators:\n'); + console.log(inactive.join('\n')); +}