Skip to content

Instantly share code, notes, and snippets.

@davidmurdoch
Last active December 15, 2023 15:17
Show Gist options
  • Save davidmurdoch/c8620564c24dbcbe647e00e6eecfafdf to your computer and use it in GitHub Desktop.
Save davidmurdoch/c8620564c24dbcbe647e00e6eecfafdf to your computer and use it in GitHub Desktop.
A Node.js function to get the latest commit's authorship timestamp in UTC (millisecond precision)
/**
* Retrieves the datetime of the last commit in UTC for the current Git branch.
*
* The author timestamp is used for its consistency across different
* repositories and its inclusion in the Git commit hash calculation. This makes
* it a stable choice for reproducible builds.
*
* @returns Millisecond precision timestamp in UTC of the last commit on the
* current branch. If the branch is detached or has no commits, it will throw an
* error.
*/
export function getLastCommitDatetimeUtc(gitDir = join(__dirname, "..", ".git")): number {
// Note: this function is syncronous because it needs to be used in a syncronous
// context
const { unzipSync } = require('node:zlib');
const { readFileSync } = require('node:fs');
const headPath = join(gitDir, 'HEAD');
// read .git/HEAD to get the current branch/commit
const ref = readFileSync(headPath, 'utf-8').trim();
// determine if we're in a detached HEAD state or on a branch
const oid = ref.startsWith('ref: ')
? readFileSync(join(gitDir, ref.slice(5)), 'utf-8').trim() // pointer to a branch, find the commit hash
: ref; // detached HEAD state, so use the commit hash directly
// read the commit object from the file system
const commitPath = join(gitDir, 'objects', oid.slice(0, 2), oid.slice(2));
const rawCommit = readFileSync(commitPath);
// it's compressed with zlib DEFLATE, so we need to decompress it
const decompressed = unzipSync(rawCommit);
// the commit object is a text file with a header and a body, we just want the
// body, which is after the first null byte
const firstNull = decompressed.indexOf(0);
const commitBuffer = decompressed.subarray(firstNull + 1);
const commitText = new TextDecoder().decode(commitBuffer);
// git commits are strictly formatted, so we can use a regex to extract the
// authorship time fields
const [, timestamp, timezoneOffset] = commitText.match(
/^author .* <.*> (.*) (.*)$/mu
)!;
// convert git timestamp from seconds to milliseconds
const msSinceLocalEpoch = (parseInt(timestamp, 10) * 1000);
const msTimezoneOffset = (parseInt(timezoneOffset, 10) * 60000);
return msSinceLocalEpoch - msTimezoneOffset;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment