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
}