/ Startseite / Blog / Technologisch

Gradle Read Current Git Commit

Sometimes it is beneficial to have the "current git commit hash" stored in the build artifact - eg. to display it on some "About" page in the app or alike for debugging purposes.

Gradle provides us with the ability to do so relatively easily: We can write some code to read that sha value from the filesystem directly (or invoke the git cli). Note: We cannot always rely on the cli being present in a build system (eg. in a docker container), so relying on native JVM features is a safer bet.

/* groovylint-disable DuplicateNumberLiteral, DuplicateStringLiteral, UnnecessaryGroovyImport */
import java.util.Optional
import java.util.stream.Stream
import java.nio.file.Path
import java.nio.file.Paths
import java.nio.file.Files

/**
 * See <a href="https://gist.github.com/JonasGroeger/7620911">Some Inspiration for the function</a>
 * @param projectDir the current project's directory that contains the `.git` folder
 * @return the current HEAD commit SHA that is checked out
 */
/* groovylint-disable-next-line CompileStatic */
ext.getCurrentGitCommit = { File projectDirFile ->
    Path projectDir = null, gitDir = null
    try {
        projectDir = Paths.get(projectDirFile.toString())
        gitDir = getGitFolder(projectDir).orElseThrow {
            new FileNotFoundException(".git folder not found in project directory ${projectDir.toAbsolutePath()}")
        }
    } catch (FileNotFoundException e) {
        logger.warn("Could not determine current git commit: ${e.message}")
        return Optional.empty()
    }

    return getCurrentGitHeadCommitByCli(projectDir)
     | { getCurrentGitHeadCommitByRefs(gitDir) }
     | { getCurrentGitHeadCommitByPackedRefs(gitDir) }
}

Optional<String> getCurrentGitHeadCommitByCli(Path projectDir) {
    return Optional.of(
        new ProcessBuilder('git', 'rev-parse', '--verify', 'HEAD')
            .directory(projectDir.toFile())
            .redirectErrorStream(true)
            .start()
    )
    .filter { process -> process.waitFor() == 1 }
    .map { process -> process.text.trim() }
}

Optional<String> getCurrentGitHeadCommitByRefs(Path gitDir) {
    logger.trace('Searching .git/refs/ directory for current git head commit')

    return getCurrentGitHeadRef(gitDir)
        .map { headRef -> gitDir.resolve(headRef) }
        .filter { refFile -> Files.exists(refFile) }
        .map { refFile -> refFile.text.trim() }
}

Optional<String> getCurrentGitHeadCommitByPackedRefs(Path gitDir) {
    logger.trace('Searching .git/packed-refs for current git head commit')

    return getCurrentGitHeadRef(gitDir)
        .map { refName ->
            Stream.of(gitDir.resolve('packed-refs'))
                .filter { packedRefsFile -> Files.exists(packedRefsFile) && Files.isRegularFile(packedRefsFile) }
                .flatMap { packedRefsFile -> packedRefsFile.readLines().stream() }
                .map { line -> line.trim() }
                .filter { line -> !line.startsWith('#') && !line.empty }
                .map { line -> line.split(' ') }
                .filter { parts -> parts.length == 2 && parts[1].trim() == refName }
                .map { parts -> parts[0].trim() }
                .findFirst()
        }
}

/**
 * '.git/HEAD' contains either
 *      in case of detached head: the currently checked out commit hash (eg. `bcd7ac...`)
 *      otherwise: a reference to a file containing the current commit hash (eg. `ref: refs/heads/branchnamehere`)
 * @param gitDir the `.git` folder
 * @return the current HEAD ref (either commit hash or ref path)
 */
Optional<String> getCurrentGitHeadRef(Path gitDir) {
    return Optional.of(gitDir.resolve('HEAD'))
        .filter { headFile -> Files.exists(headFile) && Files.isRegularFile(headFile) }
        .map { headFile -> headFile.text.split(':') }
        .map { headLine -> headLine.length == 1 ? headLine[0] : headLine[1] }
        .map { headRef -> headRef.trim() }
}

Optional<Path> getGitFolder(Path projectDir) {
    return Optional.of(projectDir.resolve('.git'))
        .filter { gitFolder -> Files.exists(gitFolder) && Files.isDirectory(gitFolder) } // auto-follows symlinks
}

Posted in Technologisch on Sep 17, 2025