93

I'd like to get the id/hash of the most recent commit on the current branch in NodeJS.

In NodeJS, I'd like to get the most recent id/hash, with respect to git and commits thereof.

Noah
  • 4,601
  • 9
  • 39
  • 52
  • You already seem to have found the correct node module. Afaik there were example to do exactly, what you want. – Sirko Dec 29 '15 at 20:20
  • @Sirko Would appreciate a link. I searched the docs this morning but didn't find it. – Noah Dec 29 '15 at 20:21
  • You can do this with [node-git](https://github.com/christkv/node-git/) lib. To get the id, take a look [here](https://github.com/christkv/node-git/blob/master/test/test_commit_stats.js), this is an example of how you can get commit stats and hash commit. – danilodeveloper Dec 29 '15 at 20:26
  • To get you started: https://github.com/nodegit/nodegit/blob/master/examples/walk-history.js also see http://www.nodegit.org/api/reference/#list and http://www.nodegit.org/api/repository/#getBranchCommit – Sirko Dec 29 '15 at 20:29

7 Answers7

175

Short solution, no external module needed (synchronous alternative to Edin's answer):

revision = require('child_process')
  .execSync('git rev-parse HEAD')
  .toString().trim()

and if you want to manually specify the root directory of the git project, use the second argument of execSync to pass the cwd option, like execSync('git rev-parse HEAD', {cwd: __dirname})

Antoine
  • 5,504
  • 5
  • 33
  • 54
94

Solution #1 (git required, with callback):

require('child_process').exec('git rev-parse HEAD', function(err, stdout) {
    console.log('Last commit hash on this branch is:', stdout);
});

Optionally, you can use execSync() to avoid the callback.

Solution #2 (no git required):

  • get contents of the file .git/HEAD
  • if the git repo is in the detached head state, the content will be the hash
  • if the git repo is on some branch, the content will be something like: "refs: refs/heads/current-branch-name"
  • get contents of .git/refs/heads/current-branch-name
  • handle all possible errors in this process
  • to get the latest hash from the master branch directly, you can get the contents of the file: .git/refs/heads/master

This can be coded with something like:

const rev = fs.readFileSync('.git/HEAD').toString().trim();
if (rev.indexOf(':') === -1) {
    return rev;
} else {
    return fs.readFileSync('.git/' + rev.substring(5)).toString().trim();
}
GTX
  • 727
  • 2
  • 13
  • 30
edin-m
  • 3,021
  • 3
  • 17
  • 27
  • Choosing this one because I like that you don't have to specify a `path_to_repo` like in Paulpro's answer. – Noah Dec 30 '15 at 14:58
  • While this works, @Paulpro's answer is much more portable (doesn't rely on git being installed), faster (child_process.exec takes a while to spawn), and stays "in Node" (as the OP wanted). – Cameron Tacklind Oct 06 '18 at 23:42
  • 1
    @CameronTacklind not really, it relies on compiling bindings to libgit2, which requires more software installed than only having git itself. I've included both w/ & w/o git solutions since this is the accepted answer. – edin-m Oct 07 '18 at 17:15
  • 1
    @edin-m Which part "not really"? I believe everything I said was correct. Sure, some people will find installing NodeGit more of a hassle, but this is rather minor. Relying on `git` being installed is potentially less portable but admittedly, in practice, `git` is rather ubiquitous. An alternative way to stay "in node" is as @hakatashi pointed out (which you've made available here). Anything that spawns/execs another binary does not stay "in node" imho. – Cameron Tacklind Oct 08 '18 at 00:41
  • 1
    Given that we're looking for a git commit, which is data that only makes sense inside a git-managed directory, with a hidden `.git` dir created by git itself (which you don't get if you just downloaded a release from github, gitlab, or any other git hosting service), the idea that git wouldn't be available is... baffling, at best? Without git installed, and managing the dir, there _are_ no git commits to look at. – Mike 'Pomax' Kamermans Dec 24 '21 at 22:09
  • _the idea that git wouldn't be available is... baffling, at best_ - The NodeJS Docker image doesn't have git installed. Copying in the `.git` folder and parsing in a multi-stage build is just as reasonable of a solution to installing git + node bindings for it – OneCricketeer Jul 12 '23 at 21:43
32

Using nodegit, with path_to_repo defined as a string containing the path to the repo you want to get the commit sha for. If you want to use the directory your process is running from, then replace path_to_repo with process.cwd():

var Git = require( 'nodegit' );

Git.Repository.open( path_to_repo ).then( function( repository ) {
  return repository.getHeadCommit( );
} ).then( function ( commit ) {
  return commit.sha();
} ).then( function ( hash ) {
  // use `hash` here
} );
Paul
  • 139,544
  • 27
  • 275
  • 264
  • 1
    just FIY on my machine, this is roughly 3.2 times faster than the more accepted answer from @antoine129. execSync takes about 6.4 ms while nodegit only takes about 1.8ms – Capaj Nov 14 '16 at 01:34
  • `nodegit` will be in most of the cases the best option – necrifede May 27 '18 at 09:41
  • 1
    This should be the accepted answer. It is the only answer that stays "in Node" and is robust to changing branches. – Cameron Tacklind Oct 06 '18 at 23:39
  • 7
    Does how long it take really matter when it's in the sub-10-millisecond range? Especially if you execute this at the start of your main and store it for use later (instead of executing this every time you need it). It also seems kindof overkill (if you use it just for this) to add in another dependency which pulls in up to [*477* more dependencies](https://github.com/nodegit/nodegit/network/dependencies#package-lock.json). But I guess an empty Next.js project uses about the same amount... – Cole Tobin May 12 '20 at 21:21
10

I was inspired by edin-m's "Solution #2 (no git required)", but I didn't like the substring(5) part which felt like a dangerous assumption. I feel my RegEx is much more tolerant to the variations allowed in git's loose requirements for that file.

The following demo shows that it works for both a checked out branch and a "detached HEAD".

$ cd /tmp

$ git init githash
Initialized empty Git repository in /private/tmp/githash/.git/

$ cd githash

$ cat > githash.js <<'EOF'
const fs = require('fs');

const git_hash = () => {
    const rev = fs.readFileSync('.git/HEAD').toString().trim().split(/.*[: ]/).slice(-1)[0];
    if (rev.indexOf('/') === -1) {
        return rev;
    } else {
        return fs.readFileSync('.git/' + rev).toString().trim();
    }

}

console.log(git_hash());

EOF

$ git add githash.js

$ git commit -m 'https://stackoverflow.com/a/56975550/117471'
[master (root-commit) 164b559] https://stackoverflow.com/a/56975550/117471
 1 file changed, 14 insertions(+)
 create mode 100644 githash.js

$ node githash.js
164b559e3b93eb4c42ff21b1e9cd9774d031bb38

$ cat .git/HEAD
ref: refs/heads/master

$ git checkout 164b559e3b93eb4c42ff21b1e9cd9774d031bb38
Note: checking out '164b559e3b93eb4c42ff21b1e9cd9774d031bb38'.

You are in 'detached HEAD' state.

$ cat .git/HEAD
164b559e3b93eb4c42ff21b1e9cd9774d031bb38

$ node githash.js
164b559e3b93eb4c42ff21b1e9cd9774d031bb38
Bruno Bronosky
  • 66,273
  • 12
  • 162
  • 149
6

Here's a version I worked up that uses fs.promises and async/await.

import {default as fsWithCallbacks} from 'fs';
const fs = fsWithCallbacks.promises;

const getGitId = async () => {
  const gitId = await fs.readFile('.git/HEAD', 'utf8');
  if (gitId.indexOf(':') === -1) {
    return gitId;
  }
  const refPath = '.git/' + gitId.substring(5).trim();
  return await fs.readFile(refPath, 'utf8');
};

const gitId = await getGitId();
erg
  • 111
  • 1
  • 5
  • 1
    This is very clean, does not depend on a git binary being available on the system, and has no assumptions about the default branch name. Nice! – Moritz Friedrich Nov 28 '22 at 08:19
5

If you are always on specific branch, you can read .git/refs/heads/<branch_name> to easily get commit hash.

const fs = require('fs');
const util = require('util');

util.promisify(fs.readFile)('.git/refs/heads/master').then((hash) => {
    console.log(hash.toString().trim());
});
hakatashi
  • 9,006
  • 2
  • 22
  • 22
1

You can also use git-fs (it's name on npm is git-fs, on Github it's node-git.)

Git('path/to/repo')
Git.getHead((err, sha) => {
    console.log('The hash is: ' + sha)
})

The same module can read directories and files from the repo.

Noah
  • 4,601
  • 9
  • 39
  • 52