Resurrecting Gitrev

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):

  1. GRT.embedGitError GRT.gitHashQ: This is the normal gitHash logic, which will work for ordinary cabal build, but will fail for nix and cabal install.

  2. GRT.embedEnvError $ GRT.envValQ "EXAMPLE_HASH": This will look up the environment variable EXAMPLE_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}";
    });
    
  3. GRT.runGitInEnvDirQ "EXAMPLE_HOME" GRT.gitHashQ: This will look up the environment variable EXAMPLE_HOME, and, if it exists, run the ordinary gitHashQ 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.

  1. The new interface uses typed TH, and the typed error is returned, so users can choose how to respond:

  2. “Out-of-tree” workarounds:

  3. Locale bug possibly fixed:

  4. Outstanding git commands

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!

13 Likes