Skip to content

Instantly share code, notes, and snippets.

@varemenos
Last active February 11, 2025 12:00
Show Gist options
  • Save varemenos/e95c2e098e657c7688fd to your computer and use it in GitHub Desktop.
Save varemenos/e95c2e098e657c7688fd to your computer and use it in GitHub Desktop.
Git log in JSON format

Get Git log in JSON format

git log --pretty=format:'{%n  "commit": "%H",%n  "abbreviated_commit": "%h",%n  "tree": "%T",%n  "abbreviated_tree": "%t",%n  "parent": "%P",%n  "abbreviated_parent": "%p",%n  "refs": "%D",%n  "encoding": "%e",%n  "subject": "%s",%n  "sanitized_subject_line": "%f",%n  "body": "%b",%n  "commit_notes": "%N",%n  "verification_flag": "%G?",%n  "signer": "%GS",%n  "signer_key": "%GK",%n  "author": {%n    "name": "%aN",%n    "email": "%aE",%n    "date": "%aD"%n  },%n  "commiter": {%n    "name": "%cN",%n    "email": "%cE",%n    "date": "%cD"%n  }%n},'

The only information that aren't fetched are:

  • %B: raw body (unwrapped subject and body)
  • %GG: raw verification message from GPG for a signed commit

The format is applied to each line, so once you get all the lines, you need to remove the trailing , and wrap them around an Array.

git log pretty format source: http://git-scm.com/docs/pretty-formats

Here is an example in Javascript based on a package I'm working on for Atom:

var format = '{%n  "commit": "%H",%n  "abbreviated_commit": "%h",%n  "tree": "%T",%n  "abbreviated_tree": "%t",%n  "parent": "%P",%n  "abbreviated_parent": "%p",%n  "refs": "%D",%n  "encoding": "%e",%n  "subject": "%s",%n  "sanitized_subject_line": "%f",%n  "body": "%b",%n  "commit_notes": "%N",%n  "verification_flag": "%G?",%n  "signer": "%GS",%n  "signer_key": "%GK",%n  "author": {%n    "name": "%aN",%n    "email": "%aE",%n    "date": "%aD"%n  },%n  "commiter": {%n    "name": "%cN",%n    "email": "%cE",%n    "date": "%cD"%n  }%n},';

var commits = [];

new BufferedProcess({
    command: 'git',
    args: [
        'log',
        '--pretty=format:' + format
    ],
    stdout: function (chunk) { commits += chunk },
    exit: function (code) {
        if (code === 0) {
            var result = JSON.parse('[' + commits.slice(0, -1) + ']');

            console.log(result); // valid JSON array
        }
    }
});
@tugrulates
Copy link

tugrulates commented Feb 2, 2025

The commit hash itself cannot be found inside the commit (since it is calculated from the commit file). So potentially, it could be used as a delimiter between commit fields. There should not be a risk of collision, unless I am missing something.

In that case, something like git log --format="%H#%s%H%b%H" would be all textual and parsable.

Example implementation:

function parseLog(log: string) {
  const commits: { hash: string; summary: string; body: string }[] = [];

  while (log.length) {
    const delimiter = log.indexOf("#");
    if (delimiter === -1) throw new Error(`Cannot parse commit log`);
    const hash = log.slice(0, delimiter);
    const [summary, body, rest] = log.slice(hash.length + 1).split(hash, 7);
    if (summary === undefined || body === undefined) {
      throw new Error(`Cannot parse commit log`);
    }
    log = rest?.trimStart() ?? "";
    commits.push({ hash, summary, body });
  }

  return commits;
}

Ref names (branches, tags and alike) are not part of the commit, so their names could collide, but they are easier to delimiter for.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment