Last active
December 15, 2023 15:17
-
-
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)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 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