Description
Some handy Template Haskell splices for including the current git hash and branch in the code of your project. Useful for including in panic messages, --version
output, or diagnostic info for more informative bug reports.
There are two main interfaces:
1. Development.GitRev
This module provides untyped splices e.g.
-- Definition in Development.GitRev
gitHash :: ExpQ
{-# LANGUAGE TemplateHaskell #-}
import Development.GitRev qualified as GR
-- Returns a hash like "e67e943dd03744d3f93c21f84e127744e6a04543" or
-- "UNKNOWN", if something goes wrong.
myHash :: String
myHash = $(GR.gitHash)
2. Development.GitRev.Typed
This module -- on the other hand -- provides typed splices e.g.
-- Definition in Development.GitRev.Typed
gitHash :: Code Q String
{-# LANGUAGE TemplateHaskell #-}
import Development.GitRev.Typed qualified as GRT
-- Returns a hash like "e67e943dd03744d3f93c21f84e127744e6a04543" or
-- "UNKNOWN", if something goes wrong.
myHash :: String
myHash = $$(GRT.gitHash)
We also provide combinators for defining custom behavior. For instance, we can instead define a variant that fails at compile-time instead of returning the string UNKNOWN
.
-- gitHashQ :: Q (Either GitError String)
-- projectError :: Q (Either GitError String) -> Q String
-- qToCode :: Q a -> Code Q a
myHashOrDie :: String
myHashOrDie = $$(GRT.qToCode $ GRT.projectError GRT.gitHashQ)
Out-of-tree builds
Furthermore, we have workarounds for "out-of-tree" builds:
myHashEnv :: Code Q String
myHashEnv = toCode gitHash
where
toCode :: Q (Either (Exceptions GitOrLookupEnvError) String) -> Code Q String
toCode = GRT.qToCode . GRT.projectError
gitHash :: Q (Either (Exceptions GitOrLookupEnvError) String)
gitHash =
-- Tries, in order:
--
-- 1. Retrieving the git hash, as normal.
-- 2. Looking up environment variable EXAMPLE_HASH, returning the
-- value if it exists.
-- 3. Running the git action under the directory pointed to by the
-- environment variable EXAMPLE_HOME, if it exists.
GRT.firstSuccessQ
(GRT.embedGitError GRT.gitHashQ)
[ GRT.embedLookupEnvError $ GRT.envValQ "EXAMPLE_HASH",
GRT.runGitInEnvDirQ "EXAMPLE_HOME" GRT.gitHashQ
]
For example, myHashEnv
will work for cabal install
if we include the
environment variable:
$ export EXAMPLE_HOME=$(pwd); cabal install example
This function will also work with nix flakes:
# flake.nix
let
compiler = pkgs.haskell.packages."ghc9101";
in
{
# Using nixpkgs haskell infra i.e. developPackage.
packages.default = compiler.developPackage {
name = "example";
root = ./.;
returnShellEnv = false;
modifier =
drv:
let
drv' = pkgs.haskell.lib.addBuildTools drv [
compiler.cabal-install
compiler.ghc
pkgs.git
pkgs.zlib
];
in
drv'.overrideAttrs (oldAttrs: {
EXAMPLE_HASH = "${self.rev or self.dirtyRev}";
});
};
};
See example
in the flake.nix
for a full nix example, and Development.GitRev.Typed
for full documentation.
Addendum
Most of the complication is due to the various places the current git hash might be stored:
- Detached HEAD: the hash is in
.git/HEAD
- On a branch or tag: the hash is in a file pointed to by
.git/HEAD
in a location like .git/refs/heads
- On a branch or tag but in a repository with packed refs: the hash
is in
.git/packed-refs
- In any of the above situations, if the current repo is checked out
as a submodule, follow the reference to its
.git
directory first
These files are added as dependencies to modules that use GitRev
, and so the module should be rebuilt automatically whenever these files change.
If you run into further scenarios that cause problems, let me know!