From 837a9f770802468e78940be8f087b053d9adf006 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Sat, 19 Nov 2016 10:14:35 -0500 Subject: [PATCH 1/5] Enables gitlogg to be distributed via npm `npm install -g gitlogg` will put it on your path Script is now agnostic to the pwd in which it's invoked `.js` is pre-transpiled so babel isn't required at runtime - set environment variable GITLOGG_DEV to enable runtime transpilation and avoid the build step Instead of a hardcoded `yourpath` variable, each positional argument to `gitlogg` is a repository path. - You can easily `gitlogg ./myrepos/*/` to process all repositories in a directory `.js` reads and writes from file descriptors 3 and 4; these are managed by bash script --- .gitignore | 6 +- .npmignore | 8 ++ package.json | 18 +++-- scripts/colors.sh | 2 - scripts/gitlogg-generate-log.sh | 105 ++++++++------------------ scripts/gitlogg-parse-json.js | 20 +++-- scripts/gitlogg.sh | 12 ++- scripts/output-intermediate-gitlog.sh | 13 ++-- 8 files changed, 79 insertions(+), 105 deletions(-) create mode 100644 .npmignore mode change 100755 => 100644 scripts/colors.sh mode change 100755 => 100644 scripts/gitlogg-generate-log.sh diff --git a/.gitignore b/.gitignore index 877c75e..89bc60e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,6 @@ npm-debug.log node_modules/ assets/ -_repos/ -_output/ -_tmp/ +gitlogg.json +*.compiled.js +*.compiled.js.map diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..862ce51 --- /dev/null +++ b/.npmignore @@ -0,0 +1,8 @@ +*.DS_Store +npm-debug.log + +node_modules/ +assets/ +gitlogg.json +.editorconfig +.babelrc \ No newline at end of file diff --git a/package.json b/package.json index 39f8093..96d69b0 100644 --- a/package.json +++ b/package.json @@ -22,13 +22,21 @@ }, "copyright": "Copyright (c) Wallace Sidhrée - All rights reserved.", "license": "MIT", - "devDependencies": { - "babel-preset-es2015": "^6.9.0", - "chalk": "^1.1.3" + "bin": { + "gitlogg": "scripts/gitlogg.sh" }, "scripts": { - "setup": "npm install babel-cli -g && npm install && mkdir -p _output && mkdir -p _repos && mkdir -p _tmp", - "gitlogg": "./scripts/gitlogg.sh" + "build": "babel --source-maps --out-file scripts/gitlogg-parse-json.compiled.js scripts/gitlogg-parse-json.js", + "test": "GITLOGG_DEV=1 ./gitlogg/gitlogg.sh ./", + "prepublish": "npm run build" + }, + "dependencies": { + "chalk": "^1.1.3" + }, + "devDependencies": { + "babel-cli": "^6.18.0", + "babel-preset-es2015": "^6.9.0", + "source-map-support": "^0.4.6" }, "engines": { "node": ">=4.3.0" diff --git a/scripts/colors.sh b/scripts/colors.sh old mode 100755 new mode 100644 index cf1a6ba..3b219a5 --- a/scripts/colors.sh +++ b/scripts/colors.sh @@ -1,5 +1,3 @@ -#!/usr/bin/bash - # Extracted command line colors for nicer output # Text Reset diff --git a/scripts/gitlogg-generate-log.sh b/scripts/gitlogg-generate-log.sh old mode 100755 new mode 100644 index 5ae192f..7ef1d52 --- a/scripts/gitlogg-generate-log.sh +++ b/scripts/gitlogg-generate-log.sh @@ -1,40 +1,32 @@ -#!/bin/bash - -my_dir="$(dirname "$0")" -cd $my_dir - -source "colors.sh" - -cd .. - -# define the absolute path to the directory that contains all your repositories. -yourpath='./_repos/' - -# define temporary 'git log' output file that will be parsed to 'json' -tempOutputFile='_tmp/gitlogg.tmp' - -# ensure file exists -mkdir -p ${tempOutputFile%%.*} -touch $tempOutputFile - -# name and path to this very script, for output message purposes -thisFile='./scripts/gitlogg-generate-log.sh' +source "$__dirname/colors.sh" + +runJs() { + local script="$1" + shift + if [[ "$GITLOGG_DEV" = "" ]]; then + # Run precompiled .compiled.js + node "${script%.js}.compiled.js" "$@" + else + # Development: transpile .js at runtime + babel "$script" | node "$@" + fi +} -workerFile='./scripts/output-intermediate-gitlog.sh' +workerFile="$__dirname/output-intermediate-gitlog.sh" # define path to 'json' parser -jsonParser='./scripts/gitlogg-parse-json.js' - +jsonParser="$__dirname/gitlogg-parse-json.js" # Display system usage and exit usage() { + local exitCode="$1" cat <<\USAGE_EOF -usage: gitlogg-generate-log.sh [options] +usage: gitlogg [options] [repository ...] The following options are available -n X: Run X instances of gitlogg concurrently USAGE_EOF - exit 2 + exit $exitCode } test "$1" = "--help" && usage @@ -46,57 +38,26 @@ test $NUM_THREADS -lt 1 && NUM_THREADS=1 test "$1" = "-n" && NUM_THREADS=$2 echo -e "${Blu}Info: Calculating in $NUM_THREADS thread(s)${RCol}" -# ensure there's always a '/' at the end of the 'yourpath' variable, since its value can be changed by user. -case "$yourpath" in - */) - yourpathSanitized="${yourpath}" # no changes if there's already a slash at the end - syntax sugar - ;; - *) - yourpathSanitized="${yourpath}/" # add a slash at the end if there isn't already one - ;; -esac - -# 'thepath' sets the path to each repository under 'yourpath' (the trailing asterix [*/] represents all the repository folders). -thepath="${yourpathSanitized}*/" +# Each positional argument is a path to a git repository +repositories=("$@") - -# function to trim whitespace -trim() { - local var="$*" - var="${var#'${var%%[![:space:]]*}'}" # remove leading whitespace characters - var="${var%'${var##*[![:space:]]}'}" # remove trailing whitespace characters - echo -n "$var" -} - -# number of directories (repos) under 'thepath' -DIRCOUNT="$(find $thepath -maxdepth 0 -type d | wc -l)" - -# trim whitespace from DIRCOUNT -DIRNR="$(trim $DIRCOUNT)" +# number of directories (repos) +DIRCOUNT="${#repositories[@]}" # determine if we're dealing with a singular repo or multiple -if [ "${DIRNR}" -gt "1" ]; then - reporef="all ${Red}${DIRNR}${Yel} repositories" -elif [ "${DIRNR}" -eq "1" ]; then - reporef="the one repository" +if [ "${DIRCOUNT}" -gt "1" ]; then + reporef="${Red}${DIRCOUNT}${Yel} repositories" +elif [ "${DIRCOUNT}" -eq "1" ]; then + reporef="${Red}${DIRCOUNT}${Yel} repository" +elif [ "${DIRCOUNT}" -eq "0" ]; then + echo -e "${Whi}[ERROR 003]: ${Yel}No repositories specified${RCol}" 1>&2 + usage 1 1>&2 fi # start counting seconds elapsed SECONDS=0 -# if the path exists and is not empty -if [ -d "${yourpathSanitized}" ] && [ "$(ls $yourpathSanitized)" ]; then - echo -e "${Yel}Generating ${Pur}git log ${Yel}for ${reporef} located at ${Red}'${thepath}'${Yel}. ${Blu}This might take a while!${RCol}" - dirs=$(ls -d $thepath) - echo $dirs | xargs -n 1 -P $NUM_THREADS $workerFile > ${tempOutputFile} - echo -e "${Gre}The file ${Blu}${tempOutputFile} ${Gre}generated in${RCol}: ${SECONDS}s" && - babel "${jsonParser}" | node # only parse JSON if we have a source to parse it from -# if the path exists but is empty -elif [ -d "${yourpathSanitized}" ] && [ ! "$(ls $yourpathSanitized)" ]; then - echo -e "${Whi}[ERROR 002]: ${Yel}The path to the local repositories ${Red}'${yourpath}'${Yel}, which is set on the file ${Blu}'${thisFile}' ${UYel}exists, but is empty!${RCol}" - echo -e "${Yel}Please move the repos to ${Red}'${yourpath}'${Yel} or update the variable ${Pur}'yourpath'${Yel} to reflect the absolute path to the directory where the repos are located.${RCol}" -# if the path does not exists -elif [ ! -d "${yourpathSanitized}" ]; then - echo -e "${Whi}[ERROR 001]: ${Yel}The path to the local repositories ${Red}'${yourpath}'${Yel}, which is set on the file ${Blu}'${thisFile}' ${UYel}does not exist!${RCol}" - echo -e "${Yel}Please create ${Red}'${yourpath}'${Yel} and move the repos under it, or update the variable ${Pur}'yourpath'${Yel} to reflect the absolute path to the directory where the repos are located.${RCol}" -fi +echo -e "${Yel}Generating ${Pur}git log ${Yel}for ${reporef} located at ${Red}'${thepath}'${Yel}. ${Blu}This might take a while!${RCol}" +echo "${repositories[@]}" \ +| xargs -n 1 -P $NUM_THREADS "$workerFile" \ +| runJs "${jsonParser}" 3<&0 4>"gitlogg.json" # only parse JSON if we have a source to parse it from \ No newline at end of file diff --git a/scripts/gitlogg-parse-json.js b/scripts/gitlogg-parse-json.js index 9e8b9fa..9b845ea 100644 --- a/scripts/gitlogg-parse-json.js +++ b/scripts/gitlogg-parse-json.js @@ -1,8 +1,13 @@ var fs = require('fs'), path = require('path'), chalk = require('chalk'), - output_file_temp = '_tmp/gitlogg.tmp', - output_file = '_output/gitlogg.json'; + // Read and write from file descriptors. Bash script will hook these up. + input_file = 3, + output_file = 4; + +try { + require('source-map-support').install(); +} catch(e) {} console.log(chalk.yellow('Generating JSON output...')); @@ -17,7 +22,7 @@ var changes = function(data, index) { console.time(chalk.green('JSON output generated in')); -var output = fs.readFileSync(output_file_temp, 'utf8') +var output = fs.readFileSync(input_file, 'utf8') .trim() .split('\n') .map(line => line.split('\\t')) @@ -137,11 +142,4 @@ console.timeEnd(chalk.green('JSON output generated in')); console.log(chalk.yellow('Writing output to file...')); -// console.log('output', JSON.stringify(output, null, 2)) // comment this out to see the output on the terminal as well - -fs.writeFile(output_file, JSON.stringify(output, null, 2), function(err) { - if(err) { - return console.log(chalk.red(err, 'Something went wrong, the file could not be written / saved')); - } - console.log(chalk.green('The file ' + chalk.blue(output_file) + ' was saved! ')); -}); +fs.writeFileSync(output_file, JSON.stringify(output, null, 2)); diff --git a/scripts/gitlogg.sh b/scripts/gitlogg.sh index ad26c59..df3fec0 100755 --- a/scripts/gitlogg.sh +++ b/scripts/gitlogg.sh @@ -1,6 +1,10 @@ -#!/bin/bash +#!/usr/bin/env bash -my_dir="$(dirname "$0")" -cd $my_dir +# Magical incantation mimics node's __dirname +__dirname="$(cd "$( + dirname "$( + node -e "var out=process.argv[1];try {out=require('path').resolve(out, '..', require('fs').readlinkSync(out))} catch(e) {} finally {console.log(out)}" "${BASH_SOURCE[0]}" + )" +)" && pwd)" -bash gitlogg-generate-log.sh $@ +source "$__dirname/gitlogg-generate-log.sh" diff --git a/scripts/output-intermediate-gitlog.sh b/scripts/output-intermediate-gitlog.sh index f6532f6..2f54128 100755 --- a/scripts/output-intermediate-gitlog.sh +++ b/scripts/output-intermediate-gitlog.sh @@ -1,18 +1,15 @@ -#!/bin/bash +#!/usr/bin/env bash # Returns an intermediate representation of git log with the given repository to stdout -my_dir="$(dirname "$0")" -cd $my_dir +__dirname="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -source "colors.sh" - -cd .. +source "$__dirname/colors.sh" test "$1" || exit 1 -dir=$1 +dir="$1" -cd $dir && +cd "$dir" && echo -e "${Whi}Outputting ${Pur}${PWD##*/}${RCol}" >&2 && git log --all --no-merges --shortstat --reverse --pretty=format:'commits\trepository\t'"${PWD##*/}"'\tcommit_hash\t%H\tcommit_hash_abbreviated\t%h\ttree_hash\t%T\ttree_hash_abbreviated\t%t\tparent_hashes\t%P\tparent_hashes_abbreviated\t%p\tauthor_name\t%an\tauthor_name_mailmap\t%aN\tauthor_email\t%ae\tauthor_email_mailmap\t%aE\tauthor_date\t%ad\tauthor_date_RFC2822\t%aD\tauthor_date_relative\t%ar\tauthor_date_unix_timestamp\t%at\tauthor_date_iso_8601\t%ai\tauthor_date_iso_8601_strict\t%aI\tcommitter_name\t%cn\tcommitter_name_mailmap\t%cN\tcommitter_email\t%ce\tcommitter_email_mailmap\t%cE\tcommitter_date\t%cd\tcommitter_date_RFC2822\t%cD\tcommitter_date_relative\t%cr\tcommitter_date_unix_timestamp\t%ct\tcommitter_date_iso_8601\t%ci\tcommitter_date_iso_8601_strict\t%cI\tref_names\t%d\tref_names_no_wrapping\t%D\tencoding\t%e\tsubject\t%s\tsubject_sanitized\t%f\tcommit_notes\t%N\tstats\t' | sed '/^[ \t]*$/d' | # remove all newlines/line-breaks, including those with empty spaces From 52fec10ff515ee929024d866ae48afa510de155a Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Thu, 1 Dec 2016 02:09:12 -0500 Subject: [PATCH 2/5] Adds .editorconfig so my editor does 2-space indents --- .editorconfig | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .editorconfig diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..481327c --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +root = true +[*] +indent_style = space +indent_size = 2 \ No newline at end of file From 77527497bfe2b338738f6b6d73dbcd4ae48b18a8 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Wed, 30 Nov 2016 23:54:47 -0500 Subject: [PATCH 3/5] Adds script to parse and generate metadata for all possible git log fields --- package.json | 3 +- scripts/git-log-fields.generate.js | 156 ++++++++++++++++++++ scripts/git-log-fields.js | 226 +++++++++++++++++++++++++++++ 3 files changed, 384 insertions(+), 1 deletion(-) create mode 100644 scripts/git-log-fields.generate.js create mode 100644 scripts/git-log-fields.js diff --git a/package.json b/package.json index 96d69b0..d49cdac 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "prepublish": "npm run build" }, "dependencies": { - "chalk": "^1.1.3" + "chalk": "^1.1.3", + "lodash": "^4.17.2" }, "devDependencies": { "babel-cli": "^6.18.0", diff --git a/scripts/git-log-fields.generate.js b/scripts/git-log-fields.generate.js new file mode 100644 index 0000000..c561b27 --- /dev/null +++ b/scripts/git-log-fields.generate.js @@ -0,0 +1,156 @@ +// Code generator: generates JS list of all git log fields + +const {groupBy, each} = require('lodash'); +const {inspect} = require('util'); +const fs = require('fs'); +const path = require('path'); + +// Copy-pasted from https://git-scm.com/docs/pretty-formats, with all formatting-related codes removed. +const gitLogFieldDocs = ` +'%H': commit hash + +'%h': abbreviated commit hash + +'%T': tree hash + +'%t': abbreviated tree hash + +'%P': parent hashes + +'%p': abbreviated parent hashes + +'%an': author name + +'%aN': author name (respecting .mailmap, see git-shortlog[1] or git-blame[1]) + +'%ae': author email + +'%aE': author email (respecting .mailmap, see git-shortlog[1] or git-blame[1]) + +'%ad': author date (format respects --date= option) + +'%aD': author date, RFC2822 style + +'%ar': author date, relative + +'%at': author date, UNIX timestamp + +'%ai': author date, ISO 8601-like format + +'%aI': author date, strict ISO 8601 format + +'%cn': committer name + +'%cN': committer name (respecting .mailmap, see git-shortlog[1] or git-blame[1]) + +'%ce': committer email + +'%cE': committer email (respecting .mailmap, see git-shortlog[1] or git-blame[1]) + +'%cd': committer date (format respects --date= option) + +'%cD': committer date, RFC2822 style + +'%cr': committer date, relative + +'%ct': committer date, UNIX timestamp + +'%ci': committer date, ISO 8601-like format + +'%cI': committer date, strict ISO 8601 format + +'%d': ref names, like the --decorate option of git-log[1] + +'%D': ref names without the " (", ")" wrapping. + +'%e': encoding + +'%s': subject + +'%f': sanitized subject line, suitable for a filename + +'%b': body + +'%B': raw body (unwrapped subject and body) + +'%N': commit notes + +'%GG': raw verification message from GPG for a signed commit + +'%G?': show "G" for a good (valid) signature, "B" for a bad signature, "U" for a good signature with unknown validity, "X" for a good signature that has expired, "Y" for a good signature made by an expired key, "R" for a good signature made by a revoked key, "E" if the signature cannot be checked (e.g. missing key) and "N" for no signature + +'%GS': show the name of the signer for a signed commit + +'%GK': show the key used to sign a signed commit + +'%gD': reflog selector, e.g., refs/stash@{1} or refs/stash@{2 minutes ago}; the format follows the rules described for the -g option. The portion before the @ is the refname as given on the command line (so git log -g refs/heads/master would yield refs/heads/master@{0}). + +'%gd': shortened reflog selector; same as %gD, but the refname portion is shortened for human readability (so refs/heads/master becomes just master). + +'%gn': reflog identity name + +'%gN': reflog identity name (respecting .mailmap, see git-shortlog[1] or git-blame[1]) + +'%ge': reflog identity email + +'%gE': reflog identity email (respecting .mailmap, see git-shortlog[1] or git-blame[1]) + +'%gs': reflog subject + +// '%Cred': switch color to red +// +// '%Cgreen': switch color to green +// +// '%Cblue': switch color to blue +// +// '%Creset': reset color +// +// '%C(…​)': color specification, as described under Values in the "CONFIGURATION FILE" section of git-config[1]; adding auto, at the beginning will emit color only when colors are enabled for log output (by color.diff, color.ui, or --color, and respecting the auto settings of the former if we are going to a terminal). auto alone (i.e. %C(auto)) will turn on auto coloring on the next placeholders until the color is switched again. +// +// '%m': left (<), right (>) or boundary (-) mark +// +// '%n': newline +// +// '%%': a raw '%' +// +// '%x00': print a byte from a hex code +// +// '%w([[,[,]]])': switch line wrapping, like the -w option of git-shortlog[1]. +// +// '%<([,trunc|ltrunc|mtrunc])': make the next placeholder take at least N columns, padding spaces on the right if necessary. Optionally truncate at the beginning (ltrunc), the middle (mtrunc) or the end (trunc) if the output is longer than N columns. Note that truncating only works correctly with N >= 2. +// +// '%<|()': make the next placeholder take at least until Nth columns, padding spaces on the right if necessary +// +// '%>()', '%>|()': similar to '%<()', '%<|()' respectively, but padding spaces on the left +// +// '%>>()', '%>>|()': similar to '%>()', '%>|()' respectively, except that if the next placeholder takes more spaces than given and there are spaces on its left, use those spaces +// +// '%><()', '%><|()': similar to '% <()', '%<|()' respectively, but padding both sides (i.e. the text is centered) +`; + +const fields = gitLogFieldDocs.split(/\r\n|\n/).filter(line => ( + line && line.indexOf('//') != 0 +)).map(line => { + const [, code, fullDescription, name, description] = line.match(/'(.*?)': ((.*?)((?:,|;| \(|$).*))/); + return { + code, + name, + fullDescription, + description, + identifier: name.replace(/ /g, '_') + }; +}); + +// For fields that end up with identical identifiers, give them longer, unique identifiers +each(groupBy(fields, 'identifier'), group => { + if(group.length < 2) return; + group.forEach(field => { + field.identifier = field.fullDescription.replace(/[,;]? /g, '_'); + }); +}); + +const output = ` +exports.fields = ${ inspect(fields) }; +`; + +fs.writeFileSync(path.join(__dirname, 'git-log-fields.js'), output); \ No newline at end of file diff --git a/scripts/git-log-fields.js b/scripts/git-log-fields.js new file mode 100644 index 0000000..077afe5 --- /dev/null +++ b/scripts/git-log-fields.js @@ -0,0 +1,226 @@ + +exports.fields = [ { code: '%H', + name: 'commit hash', + fullDescription: 'commit hash', + description: '', + identifier: 'commit_hash' }, + { code: '%h', + name: 'abbreviated commit hash', + fullDescription: 'abbreviated commit hash', + description: '', + identifier: 'abbreviated_commit_hash' }, + { code: '%T', + name: 'tree hash', + fullDescription: 'tree hash', + description: '', + identifier: 'tree_hash' }, + { code: '%t', + name: 'abbreviated tree hash', + fullDescription: 'abbreviated tree hash', + description: '', + identifier: 'abbreviated_tree_hash' }, + { code: '%P', + name: 'parent hashes', + fullDescription: 'parent hashes', + description: '', + identifier: 'parent_hashes' }, + { code: '%p', + name: 'abbreviated parent hashes', + fullDescription: 'abbreviated parent hashes', + description: '', + identifier: 'abbreviated_parent_hashes' }, + { code: '%an', + name: 'author name', + fullDescription: 'author name', + description: '', + identifier: 'author_name' }, + { code: '%aN', + name: 'author name', + fullDescription: 'author name (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + identifier: 'author_name_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + { code: '%ae', + name: 'author email', + fullDescription: 'author email', + description: '', + identifier: 'author_email' }, + { code: '%aE', + name: 'author email', + fullDescription: 'author email (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + identifier: 'author_email_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + { code: '%ad', + name: 'author date', + fullDescription: 'author date (format respects --date= option)', + description: ' (format respects --date= option)', + identifier: 'author_date_(format_respects_--date=_option)' }, + { code: '%aD', + name: 'author date', + fullDescription: 'author date, RFC2822 style', + description: ', RFC2822 style', + identifier: 'author_date_RFC2822_style' }, + { code: '%ar', + name: 'author date', + fullDescription: 'author date, relative', + description: ', relative', + identifier: 'author_date_relative' }, + { code: '%at', + name: 'author date', + fullDescription: 'author date, UNIX timestamp', + description: ', UNIX timestamp', + identifier: 'author_date_UNIX_timestamp' }, + { code: '%ai', + name: 'author date', + fullDescription: 'author date, ISO 8601-like format', + description: ', ISO 8601-like format', + identifier: 'author_date_ISO_8601-like_format' }, + { code: '%aI', + name: 'author date', + fullDescription: 'author date, strict ISO 8601 format', + description: ', strict ISO 8601 format', + identifier: 'author_date_strict_ISO_8601_format' }, + { code: '%cn', + name: 'committer name', + fullDescription: 'committer name', + description: '', + identifier: 'committer_name' }, + { code: '%cN', + name: 'committer name', + fullDescription: 'committer name (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + identifier: 'committer_name_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + { code: '%ce', + name: 'committer email', + fullDescription: 'committer email', + description: '', + identifier: 'committer_email' }, + { code: '%cE', + name: 'committer email', + fullDescription: 'committer email (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + identifier: 'committer_email_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + { code: '%cd', + name: 'committer date', + fullDescription: 'committer date (format respects --date= option)', + description: ' (format respects --date= option)', + identifier: 'committer_date_(format_respects_--date=_option)' }, + { code: '%cD', + name: 'committer date', + fullDescription: 'committer date, RFC2822 style', + description: ', RFC2822 style', + identifier: 'committer_date_RFC2822_style' }, + { code: '%cr', + name: 'committer date', + fullDescription: 'committer date, relative', + description: ', relative', + identifier: 'committer_date_relative' }, + { code: '%ct', + name: 'committer date', + fullDescription: 'committer date, UNIX timestamp', + description: ', UNIX timestamp', + identifier: 'committer_date_UNIX_timestamp' }, + { code: '%ci', + name: 'committer date', + fullDescription: 'committer date, ISO 8601-like format', + description: ', ISO 8601-like format', + identifier: 'committer_date_ISO_8601-like_format' }, + { code: '%cI', + name: 'committer date', + fullDescription: 'committer date, strict ISO 8601 format', + description: ', strict ISO 8601 format', + identifier: 'committer_date_strict_ISO_8601_format' }, + { code: '%d', + name: 'ref names', + fullDescription: 'ref names, like the --decorate option of git-log[1]', + description: ', like the --decorate option of git-log[1]', + identifier: 'ref_names' }, + { code: '%D', + name: 'ref names without the "', + fullDescription: 'ref names without the " (", ")" wrapping.', + description: ' (", ")" wrapping.', + identifier: 'ref_names_without_the_"' }, + { code: '%e', + name: 'encoding', + fullDescription: 'encoding', + description: '', + identifier: 'encoding' }, + { code: '%s', + name: 'subject', + fullDescription: 'subject', + description: '', + identifier: 'subject' }, + { code: '%f', + name: 'sanitized subject line', + fullDescription: 'sanitized subject line, suitable for a filename', + description: ', suitable for a filename', + identifier: 'sanitized_subject_line' }, + { code: '%b', + name: 'body', + fullDescription: 'body', + description: '', + identifier: 'body' }, + { code: '%B', + name: 'raw body', + fullDescription: 'raw body (unwrapped subject and body)', + description: ' (unwrapped subject and body)', + identifier: 'raw_body' }, + { code: '%N', + name: 'commit notes', + fullDescription: 'commit notes', + description: '', + identifier: 'commit_notes' }, + { code: '%GG', + name: 'raw verification message from GPG for a signed commit', + fullDescription: 'raw verification message from GPG for a signed commit', + description: '', + identifier: 'raw_verification_message_from_GPG_for_a_signed_commit' }, + { code: '%G?', + name: 'show "G" for a good', + fullDescription: 'show "G" for a good (valid) signature, "B" for a bad signature, "U" for a good signature with unknown validity, "X" for a good signature that has expired, "Y" for a good signature made by an expired key, "R" for a good signature made by a revoked key, "E" if the signature cannot be checked (e.g. missing key) and "N" for no signature', + description: ' (valid) signature, "B" for a bad signature, "U" for a good signature with unknown validity, "X" for a good signature that has expired, "Y" for a good signature made by an expired key, "R" for a good signature made by a revoked key, "E" if the signature cannot be checked (e.g. missing key) and "N" for no signature', + identifier: 'show_"G"_for_a_good' }, + { code: '%GS', + name: 'show the name of the signer for a signed commit', + fullDescription: 'show the name of the signer for a signed commit', + description: '', + identifier: 'show_the_name_of_the_signer_for_a_signed_commit' }, + { code: '%GK', + name: 'show the key used to sign a signed commit', + fullDescription: 'show the key used to sign a signed commit', + description: '', + identifier: 'show_the_key_used_to_sign_a_signed_commit' }, + { code: '%gD', + name: 'reflog selector', + fullDescription: 'reflog selector, e.g., refs/stash@{1} or refs/stash@{2 minutes ago}; the format follows the rules described for the -g option. The portion before the @ is the refname as given on the command line (so git log -g refs/heads/master would yield refs/heads/master@{0}).', + description: ', e.g., refs/stash@{1} or refs/stash@{2 minutes ago}; the format follows the rules described for the -g option. The portion before the @ is the refname as given on the command line (so git log -g refs/heads/master would yield refs/heads/master@{0}).', + identifier: 'reflog_selector' }, + { code: '%gd', + name: 'shortened reflog selector', + fullDescription: 'shortened reflog selector; same as %gD, but the refname portion is shortened for human readability (so refs/heads/master becomes just master).', + description: '; same as %gD, but the refname portion is shortened for human readability (so refs/heads/master becomes just master).', + identifier: 'shortened_reflog_selector' }, + { code: '%gn', + name: 'reflog identity name', + fullDescription: 'reflog identity name', + description: '', + identifier: 'reflog_identity_name' }, + { code: '%gN', + name: 'reflog identity name', + fullDescription: 'reflog identity name (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + identifier: 'reflog_identity_name_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + { code: '%ge', + name: 'reflog identity email', + fullDescription: 'reflog identity email', + description: '', + identifier: 'reflog_identity_email' }, + { code: '%gE', + name: 'reflog identity email', + fullDescription: 'reflog identity email (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', + identifier: 'reflog_identity_email_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + { code: '%gs', + name: 'reflog subject', + fullDescription: 'reflog subject', + description: '', + identifier: 'reflog_subject' } ]; From a310d5b2599df5450aed8059d8cb085049326239 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Thu, 1 Dec 2016 02:47:24 -0500 Subject: [PATCH 4/5] Simplifies & refactors the way `git log` fields are parsed `--pretty=tformat` string is generated automatically from array of `git log` fields. Each field is separated by a null character, which as far as I know can't appear naturally in any of those fields. Babel now transpiles from .es to .js, which is easier than transpiling to .compiled.js now that there are multiple .es scripts --- .babelrc | 3 +- .gitignore | 4 +- package.json | 5 +- .../{git-log-fields.js => git-log-fields.es} | 129 ++++++++-------- ...generate.js => git-log-fields.generate.es} | 2 +- scripts/gitlogg-generate-log.sh | 6 +- scripts/gitlogg-parse-json.es | 64 ++++++++ scripts/gitlogg-parse-json.js | 145 ------------------ scripts/output-intermediate-gitlog.es | 23 +++ scripts/output-intermediate-gitlog.sh | 28 ---- 10 files changed, 165 insertions(+), 244 deletions(-) rename scripts/{git-log-fields.js => git-log-fields.es} (70%) rename scripts/{git-log-fields.generate.js => git-log-fields.generate.es} (98%) create mode 100644 scripts/gitlogg-parse-json.es delete mode 100644 scripts/gitlogg-parse-json.js create mode 100755 scripts/output-intermediate-gitlog.es delete mode 100755 scripts/output-intermediate-gitlog.sh diff --git a/.babelrc b/.babelrc index e0c2a5a..c2efdec 100644 --- a/.babelrc +++ b/.babelrc @@ -1,3 +1,4 @@ { - presets: ['es2015'] + presets: ['es2015'], + only: 'scripts/**.es' } diff --git a/.gitignore b/.gitignore index 89bc60e..19c7031 100644 --- a/.gitignore +++ b/.gitignore @@ -4,5 +4,5 @@ npm-debug.log node_modules/ assets/ gitlogg.json -*.compiled.js -*.compiled.js.map +scripts/**/*.js +scripts/**/*.js.map diff --git a/package.json b/package.json index d49cdac..b2141bb 100644 --- a/package.json +++ b/package.json @@ -26,13 +26,14 @@ "gitlogg": "scripts/gitlogg.sh" }, "scripts": { - "build": "babel --source-maps --out-file scripts/gitlogg-parse-json.compiled.js scripts/gitlogg-parse-json.js", + "build": "babel --source-maps --out-dir scripts scripts", "test": "GITLOGG_DEV=1 ./gitlogg/gitlogg.sh ./", "prepublish": "npm run build" }, "dependencies": { "chalk": "^1.1.3", - "lodash": "^4.17.2" + "lodash": "^4.17.2", + "which": "^1.2.12" }, "devDependencies": { "babel-cli": "^6.18.0", diff --git a/scripts/git-log-fields.js b/scripts/git-log-fields.es similarity index 70% rename from scripts/git-log-fields.js rename to scripts/git-log-fields.es index 077afe5..a18b572 100644 --- a/scripts/git-log-fields.js +++ b/scripts/git-log-fields.es @@ -1,5 +1,6 @@ - -exports.fields = [ { code: '%H', + +const fields = exports.fields = [ + { code: '%H', name: 'commit hash', fullDescription: 'commit hash', description: '', @@ -38,7 +39,7 @@ exports.fields = [ { code: '%H', name: 'author name', fullDescription: 'author name (respecting .mailmap, see git-shortlog[1] or git-blame[1])', description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', - identifier: 'author_name_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + identifier: 'author_name_mailmap' }, { code: '%ae', name: 'author email', fullDescription: 'author email', @@ -48,37 +49,37 @@ exports.fields = [ { code: '%H', name: 'author email', fullDescription: 'author email (respecting .mailmap, see git-shortlog[1] or git-blame[1])', description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', - identifier: 'author_email_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, - { code: '%ad', - name: 'author date', - fullDescription: 'author date (format respects --date= option)', - description: ' (format respects --date= option)', - identifier: 'author_date_(format_respects_--date=_option)' }, - { code: '%aD', - name: 'author date', - fullDescription: 'author date, RFC2822 style', - description: ', RFC2822 style', - identifier: 'author_date_RFC2822_style' }, + identifier: 'author_email_mailmap' }, + // { code: '%ad', + // name: 'author date', + // fullDescription: 'author date (format respects --date= option)', + // description: ' (format respects --date= option)', + // identifier: 'author_date_(format_respects_--date=_option)' }, + // { code: '%aD', + // name: 'author date', + // fullDescription: 'author date, RFC2822 style', + // description: ', RFC2822 style', + // identifier: 'author_date_RFC2822_style' }, { code: '%ar', name: 'author date', fullDescription: 'author date, relative', description: ', relative', identifier: 'author_date_relative' }, - { code: '%at', - name: 'author date', - fullDescription: 'author date, UNIX timestamp', - description: ', UNIX timestamp', - identifier: 'author_date_UNIX_timestamp' }, - { code: '%ai', - name: 'author date', - fullDescription: 'author date, ISO 8601-like format', - description: ', ISO 8601-like format', - identifier: 'author_date_ISO_8601-like_format' }, + // { code: '%at', + // name: 'author date', + // fullDescription: 'author date, UNIX timestamp', + // description: ', UNIX timestamp', + // identifier: 'author_date_UNIX_timestamp' }, + // { code: '%ai', + // name: 'author date', + // fullDescription: 'author date, ISO 8601-like format', + // description: ', ISO 8601-like format', + // identifier: 'author_date_ISO_8601-like_format' }, { code: '%aI', name: 'author date', fullDescription: 'author date, strict ISO 8601 format', description: ', strict ISO 8601 format', - identifier: 'author_date_strict_ISO_8601_format' }, + identifier: 'author_date' }, { code: '%cn', name: 'committer name', fullDescription: 'committer name', @@ -88,7 +89,7 @@ exports.fields = [ { code: '%H', name: 'committer name', fullDescription: 'committer name (respecting .mailmap, see git-shortlog[1] or git-blame[1])', description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', - identifier: 'committer_name_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + identifier: 'committer_name_mailmap' }, { code: '%ce', name: 'committer email', fullDescription: 'committer email', @@ -98,47 +99,47 @@ exports.fields = [ { code: '%H', name: 'committer email', fullDescription: 'committer email (respecting .mailmap, see git-shortlog[1] or git-blame[1])', description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', - identifier: 'committer_email_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, - { code: '%cd', - name: 'committer date', - fullDescription: 'committer date (format respects --date= option)', - description: ' (format respects --date= option)', - identifier: 'committer_date_(format_respects_--date=_option)' }, - { code: '%cD', - name: 'committer date', - fullDescription: 'committer date, RFC2822 style', - description: ', RFC2822 style', - identifier: 'committer_date_RFC2822_style' }, + identifier: 'committer_email_mailmap' }, + // { code: '%cd', + // name: 'committer date', + // fullDescription: 'committer date (format respects --date= option)', + // description: ' (format respects --date= option)', + // identifier: 'committer_date_(format_respects_--date=_option)' }, + // { code: '%cD', + // name: 'committer date', + // fullDescription: 'committer date, RFC2822 style', + // description: ', RFC2822 style', + // identifier: 'committer_date_RFC2822_style' }, { code: '%cr', name: 'committer date', fullDescription: 'committer date, relative', description: ', relative', identifier: 'committer_date_relative' }, - { code: '%ct', - name: 'committer date', - fullDescription: 'committer date, UNIX timestamp', - description: ', UNIX timestamp', - identifier: 'committer_date_UNIX_timestamp' }, - { code: '%ci', - name: 'committer date', - fullDescription: 'committer date, ISO 8601-like format', - description: ', ISO 8601-like format', - identifier: 'committer_date_ISO_8601-like_format' }, + // { code: '%ct', + // name: 'committer date', + // fullDescription: 'committer date, UNIX timestamp', + // description: ', UNIX timestamp', + // identifier: 'committer_date_UNIX_timestamp' }, + // { code: '%ci', + // name: 'committer date', + // fullDescription: 'committer date, ISO 8601-like format', + // description: ', ISO 8601-like format', + // identifier: 'committer_date_ISO_8601-like_format' }, { code: '%cI', name: 'committer date', fullDescription: 'committer date, strict ISO 8601 format', description: ', strict ISO 8601 format', - identifier: 'committer_date_strict_ISO_8601_format' }, - { code: '%d', - name: 'ref names', - fullDescription: 'ref names, like the --decorate option of git-log[1]', - description: ', like the --decorate option of git-log[1]', - identifier: 'ref_names' }, + identifier: 'committer_date' }, + // { code: '%d', + // name: 'ref names', + // fullDescription: 'ref names, like the --decorate option of git-log[1]', + // description: ', like the --decorate option of git-log[1]', + // identifier: 'ref_names' }, { code: '%D', name: 'ref names without the "', fullDescription: 'ref names without the " (", ")" wrapping.', description: ' (", ")" wrapping.', - identifier: 'ref_names_without_the_"' }, + identifier: 'ref_names' }, { code: '%e', name: 'encoding', fullDescription: 'encoding', @@ -153,7 +154,7 @@ exports.fields = [ { code: '%H', name: 'sanitized subject line', fullDescription: 'sanitized subject line, suitable for a filename', description: ', suitable for a filename', - identifier: 'sanitized_subject_line' }, + identifier: 'sanitized_subject' }, { code: '%b', name: 'body', fullDescription: 'body', @@ -173,22 +174,22 @@ exports.fields = [ { code: '%H', name: 'raw verification message from GPG for a signed commit', fullDescription: 'raw verification message from GPG for a signed commit', description: '', - identifier: 'raw_verification_message_from_GPG_for_a_signed_commit' }, + identifier: 'raw_GPG_verification_message' }, { code: '%G?', name: 'show "G" for a good', fullDescription: 'show "G" for a good (valid) signature, "B" for a bad signature, "U" for a good signature with unknown validity, "X" for a good signature that has expired, "Y" for a good signature made by an expired key, "R" for a good signature made by a revoked key, "E" if the signature cannot be checked (e.g. missing key) and "N" for no signature', description: ' (valid) signature, "B" for a bad signature, "U" for a good signature with unknown validity, "X" for a good signature that has expired, "Y" for a good signature made by an expired key, "R" for a good signature made by a revoked key, "E" if the signature cannot be checked (e.g. missing key) and "N" for no signature', - identifier: 'show_"G"_for_a_good' }, + identifier: 'signature_validity' }, { code: '%GS', name: 'show the name of the signer for a signed commit', fullDescription: 'show the name of the signer for a signed commit', description: '', - identifier: 'show_the_name_of_the_signer_for_a_signed_commit' }, + identifier: 'signer_name' }, { code: '%GK', name: 'show the key used to sign a signed commit', fullDescription: 'show the key used to sign a signed commit', description: '', - identifier: 'show_the_key_used_to_sign_a_signed_commit' }, + identifier: 'key' }, { code: '%gD', name: 'reflog selector', fullDescription: 'reflog selector, e.g., refs/stash@{1} or refs/stash@{2 minutes ago}; the format follows the rules described for the -g option. The portion before the @ is the refname as given on the command line (so git log -g refs/heads/master would yield refs/heads/master@{0}).', @@ -208,7 +209,7 @@ exports.fields = [ { code: '%H', name: 'reflog identity name', fullDescription: 'reflog identity name (respecting .mailmap, see git-shortlog[1] or git-blame[1])', description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', - identifier: 'reflog_identity_name_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + identifier: 'reflog_identity_name_mailmap' }, { code: '%ge', name: 'reflog identity email', fullDescription: 'reflog identity email', @@ -218,9 +219,13 @@ exports.fields = [ { code: '%H', name: 'reflog identity email', fullDescription: 'reflog identity email (respecting .mailmap, see git-shortlog[1] or git-blame[1])', description: ' (respecting .mailmap, see git-shortlog[1] or git-blame[1])', - identifier: 'reflog_identity_email_(respecting_.mailmap_see_git-shortlog[1]_or_git-blame[1])' }, + identifier: 'reflog_identity_email_mailmap' }, { code: '%gs', name: 'reflog subject', fullDescription: 'reflog subject', description: '', - identifier: 'reflog_subject' } ]; + identifier: 'reflog_subject' } ]; + +const formatString = exports.formatString = fields.map(field => + `${ field.code }%x00` +).join(''); diff --git a/scripts/git-log-fields.generate.js b/scripts/git-log-fields.generate.es similarity index 98% rename from scripts/git-log-fields.generate.js rename to scripts/git-log-fields.generate.es index c561b27..4ddcc32 100644 --- a/scripts/git-log-fields.generate.js +++ b/scripts/git-log-fields.generate.es @@ -153,4 +153,4 @@ const output = ` exports.fields = ${ inspect(fields) }; `; -fs.writeFileSync(path.join(__dirname, 'git-log-fields.js'), output); \ No newline at end of file +fs.writeFileSync(path.join(__dirname, 'git-log-fields.es'), output); \ No newline at end of file diff --git a/scripts/gitlogg-generate-log.sh b/scripts/gitlogg-generate-log.sh index 7ef1d52..86ca965 100644 --- a/scripts/gitlogg-generate-log.sh +++ b/scripts/gitlogg-generate-log.sh @@ -5,17 +5,17 @@ runJs() { shift if [[ "$GITLOGG_DEV" = "" ]]; then # Run precompiled .compiled.js - node "${script%.js}.compiled.js" "$@" + node "${script%.es}.js" "$@" else # Development: transpile .js at runtime babel "$script" | node "$@" fi } -workerFile="$__dirname/output-intermediate-gitlog.sh" +workerFile="$__dirname/output-intermediate-gitlog.es" # define path to 'json' parser -jsonParser="$__dirname/gitlogg-parse-json.js" +jsonParser="$__dirname/gitlogg-parse-json.es" # Display system usage and exit usage() diff --git a/scripts/gitlogg-parse-json.es b/scripts/gitlogg-parse-json.es new file mode 100644 index 0000000..64d6b75 --- /dev/null +++ b/scripts/gitlogg-parse-json.es @@ -0,0 +1,64 @@ +var fs = require('fs'), + path = require('path'), + chalk = require('chalk'), + {chunk} = require('lodash'), + {fields: gitLogFields} = require('./git-log-fields'), + // Read and write from file descriptors. Bash script will hook these up. + input_file = 3, + output_file = 4; + +try { + require('source-map-support').install(); +} catch(e) {} + +console.log(chalk.yellow('Generating JSON output...')); + +var changes = function(data, index) { + var v = data.split(',')[index] || 0; + var w = 0; + if (v !== 0) { + var w = v.split(' ')[1]; // the number of changes is second on the array + } + return parseInt(w); +}; + +console.time(chalk.green('JSON output generated in')); + + +const fields = [ + {identifier: 'repository'}, + ...gitLogFields, + {identifier: 'shortstats'} +]; + +let input = fs.readFileSync(input_file, 'utf8').split('\0'); +input.shift(); +const totalFields = fields.length; +const inputCommits = chunk(input, totalFields); + +const output = {}; +inputCommits.forEach(item => { + // The last field for each commit includes the trailing newline output by `git log`; remove it + item[totalFields - 1] = item[totalFields - 1].slice(0, -1); + + const commit = {}; + fields.forEach((field, i) => { + commit[field.identifier] = item[i]; + }); + const stats = commit.shortstats; + commit.files_changed = changes(stats, 0); + const insertions = commit.insertions = changes(stats, 1); + const deletions = commit.deletions = changes(stats, 2); + commit.impact = insertions - deletions; + // TODO reimplement commit_nr + + const repository = commit.repository; + output[repository] = output[repository] || []; + output[repository].push(commit); + }); + +console.timeEnd(chalk.green('JSON output generated in')); + +console.log(chalk.yellow('Writing output to file...')); + +fs.writeFileSync(output_file, JSON.stringify(output, null, 2)); diff --git a/scripts/gitlogg-parse-json.js b/scripts/gitlogg-parse-json.js deleted file mode 100644 index 9b845ea..0000000 --- a/scripts/gitlogg-parse-json.js +++ /dev/null @@ -1,145 +0,0 @@ -var fs = require('fs'), - path = require('path'), - chalk = require('chalk'), - // Read and write from file descriptors. Bash script will hook these up. - input_file = 3, - output_file = 4; - -try { - require('source-map-support').install(); -} catch(e) {} - -console.log(chalk.yellow('Generating JSON output...')); - -var changes = function(data, index) { - var v = data.split(',')[index] || 0; - var w = 0; - if (v !== 0) { - var w = v.split(' ')[1]; // the number of changes is second on the array - } - return parseInt(w); -}; - -console.time(chalk.green('JSON output generated in')); - -var output = fs.readFileSync(input_file, 'utf8') - .trim() - .split('\n') - .map(line => line.split('\\t')) - .reduce((commits, item) => { - - // vars based on sequential values ( sanitise " to ' on fields that accept user input ) - var repository = item[3], // color-consolidator - commit_nr = parseInt(item[0], 10), // 3 - commit_hash = item[5], // 5109ad5a394a4873290ff7f7a38b7ca2e1b3b8e1 - commit_hash_abbreviated = item[7], // 5109ad5 - tree_hash = item[9], // a1606ea8d6e24e1c832b52cb9c04ae1df2242ed4 - tree_hash_abbreviated = item[11], // a1606ea - parent_hashes = item[13], // 7082fa621bf93503fe173d06ada3c6111054a62b - parent_hashes_abbreviated = item[15], // 7082fa6 - author_name = item[17].replace(/"/g, "'"), // Wallace Sidhrée - author_name_mailmap = item[19], // Wallace Sidhrée - author_email = item[21], // i@dreamyguy.com - author_email_mailmap = item[23], // i@dreamyguy.com - author_date = item[25], // Fri Jan 3 14:16:56 2014 +0100 - author_date_RFC2822 = item[27], // Fri, 3 Jan 2014 14:16:56 +0100 - author_date_relative = item[29], // 2 years, 5 months ago - author_date_unix_timestamp = item[31], // 1388755016 - author_date_iso_8601 = item[33], // 2014-01-03 14:16:56 +0100 - author_date_iso_8601_strict = item[35], // 2014-01-03T14:16:56+01:00 - committer_name = item[37].replace(/"/g, "'"), // Wallace Sidhrée - committer_name_mailmap = item[39], // Wallace Sidhrée - committer_email = item[41], // i@dreamyguy.com - committer_email_mailmap = item[43], // i@dreamyguy.com - committer_date = item[45], // Fri Jan 3 14:16:56 2014 +0100 - committer_date_RFC2822 = item[47], // Fri, 3 Jan 2014 14:16:56 +0100 - committer_date_relative = item[49], // 2 years, 5 months ago - committer_date_unix_timestamp = item[51], // 1388755016 - committer_date_iso_8601 = item[53], // 2014-01-03 14:16:56 +0100 - committer_date_iso_8601_strict = item[55], // 2014-01-03T14:16:56+01:00 - ref_names = item[57].replace(/"/g, "'"), // "" - ref_names_no_wrapping = item[59].replace(/"/g, "'"), // "" - encoding = item[61], // "" - subject = item[63].replace(/"/g, "'"), // Upgrade FontAwesome from 3.2.1 to 4.0.3" - subject_sanitized = item[65], // Upgrade-FontAwesome-from-3.2.1-to-4.0.3" - commit_notes = item[67].replace(/"/g, "'"), // "" - stats = item[69].slice(1); // ` 9 files changed, 507 insertions(+), 2102 deletions(-)` - // vars that require manipulation - var time_array = author_date.split(' '), // Fri Jan 3 14:16:56 2014 +0100 => [Fri, Jan, 3, 14:16:56, 2014, +0100] - time_array_clock = time_array[3].split(':'), // 14:16:56 => [14, 16, 56] - time_hour = parseInt(time_array_clock[0], 10), // [14, 16, 56] => 14 - time_minutes = parseInt(time_array_clock[1], 10), // [14, 16, 56] => 16 - time_seconds = parseInt(time_array_clock[2], 10), // [14, 16, 56] => 56 - time_gmt = time_array[5], // [Fri, Jan, 3, 14:16:56, 2014, +0100] => +0100 - date_array = author_date_iso_8601.split(' ')[0], // 2014-01-03 14:16:56 +0100 => 2014-01-03 - date_day_week = time_array[0], // [Fri, Jan, 3, 14:16:56, 2014, +0100] => Fri - date_iso_8601 = date_array, // 2014-01-03 - date_month_day = parseInt(date_array.split('-')[2], 10), // 2014-01-03 => [2014, 01, 03] => 03 - date_month_name = time_array[1], // [Fri, Jan, 3, 14:16:56, 2014, +0100] => Jan - date_month_number = parseInt(date_array.split('-')[1], 10), // 2014-01-03 => [2014, 01, 03] => 01 - date_year = time_array[4], // [Fri, Jan, 3, 14:16:56, 2014, +0100] => 2014 - files_changed = changes(stats, 0), // ` 9 files changed, 507 insertions(+), 2102 deletions(-)` => 9 - insertions = changes(stats, 1), // ` 9 files changed, 507 insertions(+), 2102 deletions(-)` => 507 - deletions = changes(stats, 2), // ` 9 files changed, 507 insertions(+), 2102 deletions(-)` => 2102 - impact = (insertions - deletions); // 507 - 2102 => -1595 - - commits[item[1]] = commits[item[1]] || []; - commits[item[1]].push({ - repository: repository, - commit_nr: commit_nr, - commit_hash: commit_hash, - // commit_hash_abbreviated: commit_hash_abbreviated, - // tree_hash: tree_hash, - // tree_hash_abbreviated: tree_hash_abbreviated, - // parent_hashes: parent_hashes, - // parent_hashes_abbreviated: parent_hashes_abbreviated, - author_name: author_name, - // author_name_mailmap: author_name_mailmap, - author_email: author_email, - // author_email_mailmap: author_email_mailmap, - author_date: author_date, - // author_date_RFC2822: author_date_RFC2822, - author_date_relative: author_date_relative, - author_date_unix_timestamp: author_date_unix_timestamp, - author_date_iso_8601: author_date_iso_8601, - // author_date_iso_8601_strict: author_date_iso_8601_strict, - // committer_name: committer_name, - // committer_name_mailmap: committer_name_mailmap, - // committer_email: committer_email, - // committer_email_mailmap: committer_email_mailmap, - // committer_date: committer_date, - // committer_date_RFC2822: committer_date_RFC2822, - // committer_date_relative: committer_date_relative, - // committer_date_unix_timestamp: committer_date_unix_timestamp, - // committer_date_iso_8601: committer_date_iso_8601, - // committer_date_iso_8601_strict: committer_date_iso_8601_strict, - // ref_names: ref_names, - // ref_names_no_wrapping: ref_names_no_wrapping, - // encoding: encoding, - subject: subject, - subject_sanitized: subject_sanitized, - // commit_notes: commit_notes, - stats: stats, - time_hour: time_hour, - time_minutes: time_minutes, - time_seconds: time_seconds, - time_gmt: time_gmt, - date_day_week: date_day_week, - date_month_day: date_month_day, - date_month_name: date_month_name, - date_month_number: date_month_number, - date_year: date_year, - date_iso_8601: date_iso_8601, - files_changed: files_changed, - insertions: insertions, - deletions: deletions, - impact: impact - }); - return commits; - }, {}); - -console.timeEnd(chalk.green('JSON output generated in')); - -console.log(chalk.yellow('Writing output to file...')); - -fs.writeFileSync(output_file, JSON.stringify(output, null, 2)); diff --git a/scripts/output-intermediate-gitlog.es b/scripts/output-intermediate-gitlog.es new file mode 100755 index 0000000..bb56eae --- /dev/null +++ b/scripts/output-intermediate-gitlog.es @@ -0,0 +1,23 @@ +#!/usr/bin/env node +const fs = require('fs'); +const path = require('path'); +const childProcess = require('child_process'); +const chalk = require('chalk'); +const which = require('which'); +const {formatString} = require('./git-log-fields'); + +// Returns an intermediate representation of git log with the given repository to stdout +const dir = path.resolve(process.argv[2]); +const dirName = path.basename(dir); + +fs.existsSync(dir) || process.exit(1); + +console.error('Outputting ' + chalk.magenta(dirName)); + +childProcess.spawnSync(which.sync('git'), [ + '--no-pager', 'log', '--all', '--no-merges', '--shortstat', '--reverse', + `--pretty=tformat:%x00${ dirName }%x00${ formatString }` +], { + cwd: dir, + stdio: 'inherit' +}); \ No newline at end of file diff --git a/scripts/output-intermediate-gitlog.sh b/scripts/output-intermediate-gitlog.sh deleted file mode 100755 index 2f54128..0000000 --- a/scripts/output-intermediate-gitlog.sh +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/env bash - -# Returns an intermediate representation of git log with the given repository to stdout - -__dirname="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -source "$__dirname/colors.sh" - -test "$1" || exit 1 -dir="$1" - -cd "$dir" && - echo -e "${Whi}Outputting ${Pur}${PWD##*/}${RCol}" >&2 && - git log --all --no-merges --shortstat --reverse --pretty=format:'commits\trepository\t'"${PWD##*/}"'\tcommit_hash\t%H\tcommit_hash_abbreviated\t%h\ttree_hash\t%T\ttree_hash_abbreviated\t%t\tparent_hashes\t%P\tparent_hashes_abbreviated\t%p\tauthor_name\t%an\tauthor_name_mailmap\t%aN\tauthor_email\t%ae\tauthor_email_mailmap\t%aE\tauthor_date\t%ad\tauthor_date_RFC2822\t%aD\tauthor_date_relative\t%ar\tauthor_date_unix_timestamp\t%at\tauthor_date_iso_8601\t%ai\tauthor_date_iso_8601_strict\t%aI\tcommitter_name\t%cn\tcommitter_name_mailmap\t%cN\tcommitter_email\t%ce\tcommitter_email_mailmap\t%cE\tcommitter_date\t%cd\tcommitter_date_RFC2822\t%cD\tcommitter_date_relative\t%cr\tcommitter_date_unix_timestamp\t%ct\tcommitter_date_iso_8601\t%ci\tcommitter_date_iso_8601_strict\t%cI\tref_names\t%d\tref_names_no_wrapping\t%D\tencoding\t%e\tsubject\t%s\tsubject_sanitized\t%f\tcommit_notes\t%N\tstats\t' | - sed '/^[ \t]*$/d' | # remove all newlines/line-breaks, including those with empty spaces - tr '\n' 'ò' | # convert newlines/line-breaks to a character, so we can manipulate it without much trouble - tr '\r' ' ' | # replace carriage returns with a space, so we avoid new lines popping from placeholders that allow user input - sed 's/tòcommits/tòòcommits/g' | # because some commits have no stats, we have to create an extra line-break to make `paste -d ' ' - -` consistent - tr 'ò' '\n' | # bring back all line-breaks - sed '{ - N - s/[)]\n\ncommits/)\ - commits/g - }' | # some rogue mystical line-breaks need to go down to their knees and beg for mercy, which they're not getting - paste -d ' ' - - | # collapse lines so that the `shortstat` is merged with the rest of the commit data, on a single line - awk '{print NR"\\t",$0}' | # print line number in front of each line, along with the `\t` delimiter - sed 's/\\t\ commits\\trepo/\\t\commits\\trepo/g' # get rid of the one space that shouldn't be there - From fd0584acfe2061c3269d2b786e77a2751e5368f7 Mon Sep 17 00:00:00 2001 From: Andrew Bradley Date: Mon, 5 Dec 2016 16:40:12 -0500 Subject: [PATCH 5/5] Fixes quotes in magic __dirname incantation --- scripts/gitlogg.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/gitlogg.sh b/scripts/gitlogg.sh index df3fec0..ba2f622 100755 --- a/scripts/gitlogg.sh +++ b/scripts/gitlogg.sh @@ -3,7 +3,7 @@ # Magical incantation mimics node's __dirname __dirname="$(cd "$( dirname "$( - node -e "var out=process.argv[1];try {out=require('path').resolve(out, '..', require('fs').readlinkSync(out))} catch(e) {} finally {console.log(out)}" "${BASH_SOURCE[0]}" + node -e 'var out=process.argv[1];try {out=require("path").resolve(out, "..", require("fs").readlinkSync(out))} catch(e) {} finally {console.log(out)}' "${BASH_SOURCE[0]}" )" )" && pwd)"