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 normalgitHashlogic, which will work for ordinarycabal build, but will fail fornixandcabal 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 ordinarygitHashQin 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!