git

How to retrieve a Git hash for my app version

Reading Time: 3 minutes

It’s popular to display your current git commit (or tag) as the version of your application. So then how does one go about getting that information? In this post, I’ll show you the various ways of getting that info.

Getting the Git Hash

git rev-parse HEAD

The above command will output the complete SHA-1 hash, which will look like the following:

4778d8eb93a5668fb37075261558505266872d99

If you prefer a shorter version you can use some built-in arguments such as –short

git rev-parse --short HEAD

Which will output the first 7 alpha-numeric characters

4778d8e

If you want custom length, you can add some scripting. If you OS supports the cut command you can do something like:

git rev-parse HEAD | cut -c 1-10

Which will cut by characters (-c) and return characters 1-10, basically it returns the first 10 characters.

4778d8eb93

Getting the Git tag

If you are actively tagging your releases, you may want to use the Git tag instead of or in addition to the Git hash.

Let’s assume you are tagging your git repo during CI/CD pipeline. For example, after a successful build & test, you may issue the following.

git -a v1.0.5 -m "Release version 1.0.5"
git push origin v1.0.5

# as an alternative / lazy way you can issue
# which will push any tag that hasn't been pushed to the remote
git push --tags

To view / get the most recent tag

git describe

One thing to note about git describe, if you’re on a different branch or commit point, then it may produce something like v1.0.05-4778d8e.

This is the nature of git describe, which does the following:

The command finds the most recent tag that is reachable from a commit. If the tag points to the commit, then only the tag is shown. Otherwise, it suffixes the tag name with the number of additional commits on top of the tagged object and the abbreviated object name of the most recent commit. The result is a “human-readable” object name which can also be used to identify the commit to other git commands.

Source: https://git-scm.com/docs/git-describe

Inject the Hash or Tag into your build pipeline

To add it to your build process, execute a script to grab the hash or tag, then replace the version information prior to building the code.

For example, you would write a simple python script to read the git tag, hash or both, then replace a version placeholder with the values.

For example: Assume you have an environment file or version file, etc. For simplicity, I’m going to call it version.txt. In that file you have some json or plain text that looks like this:

version: "{VERSION_INFO}"

Next you have some python scripts that look something like this:

#!/usr/bin/env python3
# requires Python 3.6.x or above
import os
import subprocess
import sys
import fileinput

# get the full git commit hash
def get_git_commit_hash(repoPath):

    if ( not os.path.exists(repoPath) ):
        print (f"[ERROR]: Failed to get commit hash.  Reason: Path Not Found {repoPath}")
        return None

    cmd = f"git -C {repoPath} rev-parse  HEAD "


    stream = os.popen(cmd)
    output = stream.read().strip()

    return output

def get_git_tag(repoPath):

    if ( not os.path.exists(repoPath) ):
        print (f"[ERROR]: Failed to get commit hash.  Reason: Path Not Found {repoPath}")
        return None

    cmd = f"git -C {repoPath} describe "


    stream = os.popen(cmd)
    output = stream.read().strip()

    return output

# truncate string
def truncate_string(string, length):

    if (string == None):
        return None

    if (len(string) > length):
        return string[0:length]
    else:
        return string

def replace_text_in_file(filePath, find, replace):

    if (not os.path.exists(filePath)):
        print (F"[ERROR]: File does not exist: {filePath}")
        return None

    # read in the file
    with open(filePath, 'r') as file :
        text = file.read()

    # find/replace
    text = text.replace(find, replace)

    # write the file out again
    with open(filePath, 'w') as file:
        file.write(text)

    # return all went well
    return 0



# execute if this file is called directly
if ( __name__ == "__main__" ) :
    
    if (sys.argv.__len__() > 2) :
        path = (sys.argv[1])
        file = (sys.argv[2])

        # call the function
        hash = get_git_commit_hash(path)
        tag = get_git_tag(path)
        print (f"Origina Hash: {hash}")
        print (f"Git Tag: {tag}")

        resizedHash = (truncate_string(hash, 10))

        print (f"Resized Hash: {resizedHash}")

        if (hash != None and tag != None) :
            replace_text_in_file(file, "{VERSION_INFO}", f"{tag}.{resizedHash}")


    else:
        print("Required Arguments: RepoPath, VersionFile")

Then in your Jekins pipeline, you set up an execution point with something like this:

python3 gitUtility.py 'PathToRepo' 'PathToRepo/src/version.json'

The code above executes the python code (listed above and saved to a file gitUtility.py) passing in two arguments. The first is the path the repo, the second is the path to the specific version file that you intend to overwrite.

I don’t intend to check the version file back in since I want to keep it templated with the {VERSION} placeholder. Besides the git tag and hash give me enough info, if I ever need to check out the code at a specific point.

Leave a Reply

Your email address will not be published. Required fields are marked *