Hello!
gitrev is a package for embedding git info in haskell programs via TH e.g.
import Development.GitRev (gitHash)
myVersion :: String
myVersion = "Version 1.0 (" ++ $(gitHash) ++ ")"
I used to be a regular user, but I mostly stopped because gitrev
does not handle “out-of-tree” builds, which afflicts both nix
and cabal install
(stack install
, interestingly, seems okay).
Recently, I decided to try my hand at implementing workrounds for this situation on my fork gitrev-typed. To get right down to it, here is an example:
Example
import Development.GitRev.Typed qualified as GRT
myHashEnv :: Code Q String
myHashEnv = toCode gitHash
where
toCode :: Q (Either (Exceptions GitRevError) String) -> Code Q String
toCode = GRT.qToCode . GRT.projectError
gitHash :: Q (Either (Exceptions GitRevError) String)
gitHash =
GRT.firstSuccessQ
[ GRT.embedGitError GRT.gitHashQ,
GRT.embedEnvError $ GRT.envValQ "EXAMPLE_HASH",
GRT.runGitInEnvDirQ "EXAMPLE_HOME" GRT.gitHashQ
]
Explanation
This tries (in order, stopping after the first success):
-
GRT.embedGitError GRT.gitHashQ
: This is the normalgitHash
logic, which will work for ordinarycabal build
, but will fail fornix
andcabal install
. -
GRT.embedEnvError $ GRT.envValQ "EXAMPLE_HASH"
: This will look up the environment variableEXAMPLE_HASH
, and, if it exists, return its value. This can work with nix if you inject the hash yourself e.g.drv.overrideAttrs (oldAttrs: { EXAMPLE_HASH = "${self.rev or self.dirtyRev}"; });
-
GRT.runGitInEnvDirQ "EXAMPLE_HOME" GRT.gitHashQ
: This will look up the environment variableEXAMPLE_HOME
, and, if it exists, run the ordinarygitHashQ
in the directory pointed to by the env var. For example:$ export EXAMPLE_HOME=$(pwd); cabal install my-exe
GRT.projectError
will cause a compilation failure if all methods fail. The previous behavior – which would return the string UNKNOWN
– is still available via GRT.projectStringUnknown
. Futhermore, because this is all typed in Q
, you can define whatever behavior you want.
Finally, GRT.qToCode
lifts the result to Code
, where it can be used with typed TH i.e. $$myHashEnv
.
Other features
In addition to the new Development.GitRev.Typed
(and corresponding Development.GitRev.Typed.OsString
) interfaces, a number of the open issues/PRs from the original repo are potentially resolved e.g.
-
The new interface uses typed TH, and the typed error is returned, so users can choose how to respond:
- Move to typed quotes by bgamari · Pull Request #9 · acfoltzer/gitrev · GitHub (typed TH)
- Using Maybe instead of "UNKNOWN"? · Issue #10 · acfoltzer/gitrev · GitHub (typed errors e.g. Maybe)
-
“Out-of-tree” workarounds:
-
Locale bug possibly fixed:
-
Outstanding git commands
- Add gitHashShort to allow obtain shorter (yet still unique) hashes by BartAdv · Pull Request #16 · acfoltzer/gitrev · GitHub (git short hash)
- Add gitDiff by tomjaguarpaw · Pull Request #20 · acfoltzer/gitrev · GitHub (git diff)
- Expose git tree too by jessekempf · Pull Request #22 · acfoltzer/gitrev · GitHub (git tree)
I also chose to expose
runGit :: [String] -> IndexUsed -> Q (Either GitError String)
so users can define their own git actions, without having to open a PR for every possibility.
The full API is available on the package candidate gitrev-typed.
Why not update gitrev?
The original repo has been archived, with an explicit message from the author noting its unmaintained status. I reached out about potentially taking over the gitrev
namespace on hackage, but the author preferred to keep it as-is, citing concerns for backwards compatibility and supply-chain attacks. Fair enough.
Even if the author consented, I now think the API (and some internal details) have diverged enough to warrant a new package.
Why not githash?
To be honest, when I set out to do this I completely forgot about the other fork, githash. Having revisited it now, I think forking off gitrev
was the right call.
githash
’s strategy seems to be collecting everything someone might want up front, then returning whatever subset the user asks for. gitrev
, on the other hand, only takes what is directly requested via the passed arguments.
I prefer gitrev
’s flexibility here, especially given that it allows users to easily specify custom commands. I am not very familiar with githash
’s internals, however, so please do let me know if I am missing something.
Next steps
This seems to work for my use-cases, though I am eager to hear what the community thinks. Is this useful? Does the API/documentation seem reasonable? Other thoughts/suggestions?
Assuming others find this useful, I will publish the candidate after some time.
Thanks!