Skip to content

Instantly share code, notes, and snippets.

@ELLIOTTCABLE
Created December 26, 2014 04:38
Show Gist options
  • Save ELLIOTTCABLE/b86f54a5d11d65f378a1 to your computer and use it in GitHub Desktop.
Save ELLIOTTCABLE/b86f54a5d11d65f378a1 to your computer and use it in GitHub Desktop.
#!/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