Created
December 26, 2014 04:38
-
-
Save ELLIOTTCABLE/b86f54a5d11d65f378a1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env sh | |
':' //; exec "$(command -v nodejs || command -v node)" "$0" "$@" | |
~function(){ | |
// This script searches recent git commits' messages for [git labels][], and prints a list of labels | |
// to standard output, ordered by number of occurrences. | |
// | |
// Author: twitter.com/@ELLIOTTCABLE | |
// | |
// Usage: | |
// $ node <this script>.js [decorate] [age cuttoff] [max commits] [separator char] | |
// # for example, | |
// $ node prepare-commit-msg-suggest-gitlabels.js 6.months | |
// $ node prepare-commit-msg-suggest-gitlabels.js 2.years 10000 | |
// | |
// Parameters: (note, defaults for these are set in the corresponding shell-script to this file!) | |
// | |
// 1. decorate: whether or not to include the actual count for each tag in the output | |
// 2. age cutoff: the age of the oldest commit that will be examined (as taken by `git log`) | |
// 3. max commits: the maximum number of commits to examine | |
// 4. separator char: a character that will never appear in the commit-messages being examined | |
// (defaults to U+001E ‘Record Separator’) | |
// | |
// [git labels]: <https://github.com/ELLIOTTCABLE/.gitlabels> | |
// "gitlabels, a system for ‘tagging’ git commits" | |
// == Modules == | |
var child_process = require('child_process') | |
// == Command-line parameters == | |
, decorate = !process.argv[2] || !(process.argv[2][0] === 'n' || process.argv[2][0] === 'f') | |
, max_commit_age = process.argv[3] || '1 year' | |
, max_commits = parseInt(process.argv[4], 10) || 1000 | |
, separator = process.argv[5] && process.argv[4].length > 0 ? | |
process.argv[4] : "\x1E" | |
var d = require('domain').create() | |
d.on('error', function(err){ | |
if (err.code === 'EPIPE') return process.exit() }) | |
d.add(process.stdin) | |
d.add(process.stdout) | |
d.enter() | |
// First, I generate a log of recent commit messages (delimited by `separator` codepoints). | |
var format_code_body = '%B' | |
, format_code_separator = '%x'+separator.charCodeAt(0).toString(16) | |
var git_log = 'git --no-pager log' | |
git_log += " --all" | |
git_log += " --max-count='"+max_commits+"'" | |
git_log += " --since='"+max_commit_age+"'" | |
git_log += " --no-decorate" | |
git_log += " --no-notes" | |
git_log += ' --format="' + 'format:'+format_code_body+format_code_separator + '"' | |
child_process.exec(git_log, function(err, log){ var labels, extract_labels, occurrences = new Object | |
if (err) throw err | |
// This will iterate record-wise over each commit-message selected above, extracting gitlabels | |
// (both prefix-style `(foo bar)`, and slashtag-style `... hello there! /foo bar`) from each | |
// message. Duplicates within each message will only be counted once, although there will be | |
// multiple occurrences of each label across *all* of them. | |
extract_labels = function(labels, message){ var these_labels = new Array | |
if (message.length === 0) return labels | |
// This is fragile, and will fail when there are slashes *inside* the slashtag, somehow (a | |
// key-value pair whose value includes a slash, perhaps?). Then again, maybe I should just | |
// explicitly disallow that. | |
var bits = message.trim().split('/') | |
, first = bits[0] | |
, last = bits[bits.length - 1] | |
// This, too, is fragile; this will break on double-quoted label data containing closing- | |
// parens. | |
if (first && first[0] == '(') { var | |
label_string = first.split(')')[0] | |
label_string = label_string.substr(1) | |
// ... remove label ‘parameter’ values | |
label_string = label_string.replace(/:("[^"]+"|[^"\s]+)/gi, '') | |
these_labels = these_labels.concat(label_string.split(' ')) | |
} | |
//if (bits.length > 1 && /^\s/.test(last)) { | |
// // NYI | |
//} | |
// ... remove duplicate labels | |
these_labels = these_labels.filter(function(e, idx, labels){ return labels.indexOf(e) === idx }) | |
return labels.concat(these_labels) | |
} | |
labels = log.split(separator).reduce(extract_labels, new Array) | |
labels.forEach(function(label){ | |
occurrences[label] = occurrences[label] + 1 || 1 }) | |
labels = labels.sort(function(a,b){ return occurrences[b] - occurrences[a] }) | |
labels = labels.filter(function(e, idx, labels){ return labels.indexOf(e) === idx }) | |
labels.forEach(function(label){ | |
if (decorate) | |
console.log('('+label+'): ' + occurrences[label]) | |
else | |
console.log(label) }) | |
process.exit() | |
}) }() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment