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 -