gitrev-typed
Safe HaskellNone
LanguageHaskell2010

Development.GitRev.Typed

Description

Typed version of Development.GitRev.

Since: 0.1

Synopsis

Basic functions

These functions are simple, merely a typed version of Development.GitRev's API.

NOTE: These functions do not error if git fails to run, opting instead to return some default value (e.g. string UNKNOWN, boolean False).

gitBranch :: Code Q String Source #

Return the branch (or tag) name of the current git commit, or UNKNOWN if not in a git repository. For detached heads, this will just be HEAD.

Examples

Expand
λ. $$(gitBranch)
"main"
>>> $$(gitBranch)
...

Since: 0.1

gitCommitCount :: Code Q String Source #

Return the number of commits in the current head.

Examples

Expand
λ. $$(gitCommitCount)
"47"
>>> $$(gitCommitCount)
...

Since: 0.1

gitCommitDate :: Code Q String Source #

Return the commit date of the current head.

Examples

Expand
λ. $$(gitCommitDate)
"Mon Apr 14 22:14:44 2025 +1200"
>>> $$(gitCommitDate)
...

Since: 0.1

gitDescribe :: Code Q String Source #

Return the long git description for the current git commit, or UNKNOWN if not in a git repository.

Examples

Expand
λ. $$(gitDescribe)
"e67e943"
>>> $$(gitDescribe)
...

Since: 0.1

gitDiff :: Code Q String Source #

Return the diff of the working copy with HEAD.

Examples

Expand
λ. $$(gitDiff)
"Mon Apr 14 22:14:44 2025 +1200"
>>> $$(gitDiff)
...

gitDirty :: Code Q Bool Source #

Return True if there are non-committed files present in the repository.

Examples

Expand
λ. $$(gitDirty)
False
>>> $$(gitDirty)
...

Since: 0.1

gitDirtyTracked :: Code Q Bool Source #

Return True if there are non-commited changes to tracked files present in the repository.

Examples

Expand
λ. $$(gitDirtyTracked)
False
>>> $$(gitDirtyTracked)
...

Since: 0.1

gitHash :: Code Q String Source #

Return the hash of the current git commit, or UNKNOWN if not in a git repository.

Examples

Expand
λ. $$(gitHash)
"e67e943dd03744d3f93c21f84e127744e6a04543"
>>> $$(gitHash)
...

Since: 0.1

gitShortHash :: Code Q String Source #

Return the short hash of the current git commit, or UNKNOWN if not in a git repository.

Examples

Expand
λ. $$(gitShortHash)
"e67e943"
>>> $$(gitShortHash)
...

Since: 0.1

gitTree :: Code Q String Source #

Return the hash of the current tree.

Examples

Expand
λ. $$(gitTree)
"Mon Apr 14 22:14:44 2025 +1200"
>>> $$(gitTree)
...

Custom behavior

These functions allow defining custom behavior. For instance, using the primitive getHashQ and combinator projectError, we can define a variant of gitHash that instead fails to compile if there are any problems with git:

>>> :{
  let gitHashOrDie :: Code Q String
      gitHashOrDie = qToCode $ projectError gitHashQ
:}

We can also define a function that falls back to an environment variable, in case the git command fails. firstSuccessQ takes the first action that returns Right.

>>> :{
  let gitHashEnv :: String -> Code Q (Either (Exceptions GitOrLookupEnvError) String)
      gitHashEnv var =
        qToCode $
          firstSuccessQ
            (embedGitError gitHashQ)
            [embedLookupEnvError $ envValQ var]
:}

Naturally, these can be combined:

>>> :{
  let gitHashEnvOrDie :: String -> Code Q String
      gitHashEnvOrDie var =
        qToCode
          . projectError
          $ firstSuccessQ
            (embedGitError gitHashQ)
            [embedLookupEnvError $ envValQ var]
:}

"Out-of-tree" builds

An example where custom definitions are useful is "out-of-tree" builds, where the build takes place outside of the normal git tree.

These builds present a problem, as we normally rely on building in the project directory where the .git directory is easy to locate. For example, while gitHash will work for 'cabal build', it will not work for 'nix build' or 'cabal install'. Fortunately, there are workarounds, both relying on passing the right data via environment variables.

  1. Passing the git directory.

    For situations where we can pass the current directory during installation e.g.

    $ export EXAMPLE_HOME=$(pwd); cabal install example

    We can define

    >>> :{
      let gitHashSrcDir :: Code Q String
          gitHashSrcDir =
            qToCode
            . projectStringUnknown
            $ firstSuccessQ
              -- 1. We first try normal gitHashQ.
              (embedGitError gitHashQ)
              -- 2. If that fails, we try again in the directory pointed
              --    to by "EXAMPLE_HOME".
              [runGitInEnvDirQ "EXAMPLE_HOME" gitHashQ]
    :}
    

    If the initial call to gitHashQ fails, then we will try again, running the command from the directory pointed to by EXAMPLE_HOME. firstSuccessQ ensures we do not run the second action unless the first fails.

  2. Passing the value itself.

    This approach can work well with nix, as nix flakes provides a variety of revisions via its self interface. For example:

      # Injecting the git hash via EXAMPLE_HASH where drv is the normal
      # derivation.
      drv.overrideAttrs (oldAttrs: {
        # Also: self.shortRev, self.dirtyShortRev
        EXAMPLE_HASH = "${self.rev or self.dirtyRev}";
      });
    

    Then we can define

    >>> :{
      let gitHashVal :: Code Q String
          gitHashVal =
            qToCode
            . projectStringUnknown
            $ firstSuccessQ
              -- 1. We first try normal gitHashQ.
              (embedGitError gitHashQ)
              -- 2. If that fails, get the value directly from
              --    "EXAMPLE_HASH".
              [embedLookupEnvError $ envValQ "EXAMPLE_HASH"]
    :}
    

    Once again, if the first attempt fails, we will run the second action, looking for the value of EXAMPLE_HASH.

Finally, we can compose these together to make a function that works for all three cases:

>>> :{
  let gitHashValSrc :: Code Q String
      gitHashValSrc =
        qToCode
          . projectStringUnknown
          $ firstSuccessQ
            (embedGitError gitHashQ)
            [ embedLookupEnvError $ envValQ "EXAMPLE_HASH",
              runGitInEnvDirQ "EXAMPLE_HOME" gitHashQ
            ]
:}

A final note on laziness: As alluded to above, Q's default semigroup instance is not lazy enough:

>>> :{
  $$( qToCode $
      (runIO (putStrLn "in q1") $> (Right "q1") :: Q (Either () String))
        <> (runIO (putStrLn "in q2") $> Left ())
    )
:}
in q1
in q2
Right "q1"

For this reason, we introduce the QFirst newtype:

>>> :{
  $$( qToCode $ unQFirst $
      (mkQFirst $ runIO (putStrLn "in q1") $> (Right "q1") :: QFirst () String)
        <> (mkQFirst $ runIO (putStrLn "in q2") $> Left ())
    )
:}
in q1
Right "q1"

The convenience function

  firstSuccessQ :: Q (Either e a) -> [Q (Either e a)] -> Q (Either e a)

utilizes QFirst for sequencing a series of Q actions, stopping after the first success.

Git Primitives

gitBranchQ :: Q (Either GitError String) Source #

Returns the current git branch.

Examples

Expand
>>> $$(qToCode gitBranchQ)
Right ...

Since: 0.1

gitCommitCountQ :: Q (Either GitError String) Source #

Returns the git commit count.

Examples

Expand
>>> $$(qToCode gitCommitCountQ)
Right ...

Since: 0.1

gitCommitDateQ :: Q (Either GitError String) Source #

Returns the latest git commit date.

Examples

Expand
>>> $$(qToCode gitCommitDateQ)
Right ...

Since: 0.1

gitDescribeQ :: Q (Either GitError String) Source #

Returns the git description.

Examples

Expand
>>> $$(qToCode gitDescribeQ)
Right ...

Since: 0.1

gitDiffQ :: Q (Either GitError String) Source #

Return the diff of the working copy with HEAD.

Examples

Expand
>>> $$(qToCode gitDiffQ)
Right ...

Since: 0.1

gitDirtyQ :: Q (Either GitError Bool) Source #

Returns the git dirty status.

Examples

Expand
>>> $$(qToCode gitDirtyQ)
Right ...

Since: 0.1

gitDirtyTrackedQ :: Q (Either GitError Bool) Source #

Returns the git dirty status, ignoring untracked files.

Examples

Expand
>>> $$(qToCode gitDirtyTrackedQ)
Right ...

Since: 0.1

gitHashQ :: Q (Either GitError String) Source #

Returns the latest git hash.

Examples

Expand
>>> $$(qToCode gitHashQ)
Right ...

Since: 0.1

gitShortHashQ :: Q (Either GitError String) Source #

Returns the latest git short hash.

Examples

Expand
>>> $$(qToCode gitShortHashQ)
Right ...

Since: 0.1

gitTreeQ :: Q (Either GitError String) Source #

Returns the hash of the current tree.

Examples

Expand
>>> $$(qToCode gitTreeQ)
Right ...

Since: 0.1

Environment lookup

envValQ Source #

Arguments

:: String

The environment variable k.

-> Q (Either LookupEnvError String)

The result v or an error.

Performs an environment variable lookup in Q.

Examples

Expand
>>> setEnv "SOME_VAR" "val"
>>> $$(qToCode $ envValQ "SOME_VAR")
Right "val"

Since: 0.1

runGitInEnvDirQ Source #

Arguments

:: String

Environment variable pointing to a directory path, in which we run the git process.

-> Q (Either GitError a)

Git process to run.

-> Q (Either GitOrLookupEnvError a)

The result.

runGitInEnvDirQ var q runs q in the directory given by the environment variable.

Examples

Expand
>>> setEnv "SOME_DIR" "./"
>>> $$(qToCode $ runGitInEnvDirQ "SOME_DIR" gitHashQ)
Right ...

Since: 0.1

Q to Code

qToCode :: Lift a => Q a -> Code Q a Source #

Lifts a Q computation to Code, for usage with typed TH.

Since: 0.1

Q Combinators

First success

newtype QFirst e a Source #

Wrapper for Q over Either with a lazier Semigroup. With this, we can run:

  MkQFirst q1 <> MkQFirst q2

This will only execute q2 if q1 returns Left, unlike Q's normal Semigroup instance.

QFirst also collects all errors in Exceptions.

Since: 0.1

Constructors

MkQFirst 

Fields

Instances

Instances details
Bifunctor QFirst Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Methods

bimap :: (a -> b) -> (c -> d) -> QFirst a c -> QFirst b d #

first :: (a -> b) -> QFirst a c -> QFirst b c #

second :: (b -> c) -> QFirst a b -> QFirst a c #

Functor (QFirst e) Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Methods

fmap :: (a -> b) -> QFirst e a -> QFirst e b #

(<$) :: a -> QFirst e b -> QFirst e a #

Semigroup (QFirst e a) Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Methods

(<>) :: QFirst e a -> QFirst e a -> QFirst e a #

sconcat :: NonEmpty (QFirst e a) -> QFirst e a #

stimes :: Integral b => b -> QFirst e a -> QFirst e a #

mkQFirst :: Q (Either e a) -> QFirst e a Source #

Since: 0.1

firstSuccessQ :: Q (Either e a) -> [Q (Either e a)] -> Q (Either (Exceptions e) a) Source #

firstSuccessQ q qs takes the first qi in q : qs that returns Right, without executing any qj for j > i. If there are no Right's, returns the final result.

Examples

Expand
>>> :{
   $$( qToCode $
         firstSuccessQ
           (pure (Left GitNotFound))
           [ gitHashQ,
             error "oh no"
           ]
     )
:}
Right ...

Since: 0.1

newtype Exceptions e Source #

Collects multiple exceptions.

Since: 0.1

Constructors

MkExceptions (NonEmpty e) 

Instances

Instances details
Applicative Exceptions Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Methods

pure :: a -> Exceptions a #

(<*>) :: Exceptions (a -> b) -> Exceptions a -> Exceptions b #

liftA2 :: (a -> b -> c) -> Exceptions a -> Exceptions b -> Exceptions c #

(*>) :: Exceptions a -> Exceptions b -> Exceptions b #

(<*) :: Exceptions a -> Exceptions b -> Exceptions a #

Functor Exceptions Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Methods

fmap :: (a -> b) -> Exceptions a -> Exceptions b #

(<$) :: a -> Exceptions b -> Exceptions a #

Monad Exceptions Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Methods

(>>=) :: Exceptions a -> (a -> Exceptions b) -> Exceptions b #

(>>) :: Exceptions a -> Exceptions b -> Exceptions b #

return :: a -> Exceptions a #

Lift e => Lift (Exceptions e :: Type) Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Methods

lift :: Quote m => Exceptions e -> m Exp #

liftTyped :: forall (m :: Type -> Type). Quote m => Exceptions e -> Code m (Exceptions e) #

Semigroup (Exceptions e) Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Exception e => Exception (Exceptions e) Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Show e => Show (Exceptions e) Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Eq e => Eq (Exceptions e) Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils

Methods

(==) :: Exceptions e -> Exceptions e -> Bool #

(/=) :: Exceptions e -> Exceptions e -> Bool #

mkExceptions :: e -> Exceptions e Source #

Since: 0.1

Eliminating Either

projectStringUnknown :: Functor f => f (Either e String) -> f String Source #

Projects Left to the string UNKNOWN.

Examples

Expand
>>> :{
  let gitHashUnknownQ :: Q String
      gitHashUnknownQ = projectStringUnknown gitHashQ
  -- inling gitHashUnknownQ here due to stage restriction
  in $$(qToCode $ projectStringUnknown gitHashQ)
:}
...
>>> $$(qToCode $ projectStringUnknown (pure $ Left ()))
"UNKNOWN"

Since: 0.1

projectString :: Functor f => String -> f (Either e String) -> f String Source #

Projects Left to the given string.

Examples

Expand
>>> :{
  let gitHashDefStringQ :: Q String
      gitHashDefStringQ = projectString "FAILURE" gitHashQ
  in $$(qToCode $ projectString "FAILURE" gitHashQ)
:}
...
>>> $$(qToCode $ projectString "FAILURE" (pure $ Left ()))
"FAILURE"

Since: 0.1

projectFalse :: Functor f => f (Either e Bool) -> f Bool Source #

Projects Left to False.

Examples

Expand
>>> :{
  let gitDirtyDefFalseQ :: Q Bool
      gitDirtyDefFalseQ = projectFalse gitDirtyQ
  in $$(qToCode $ projectFalse gitDirtyQ)
:}
...
>>> $$(qToCode $ projectFalse (pure $ Left ()))
False

Since: 0.1

projectError :: forall f e a. (Exception e, Functor f) => f (Either e a) -> f a Source #

Projects Left via error, rendering via displayException. Hence an error will cause a compilation failure.

Examples

Expand
>>> :{
  let gitHashOrDieQ :: Q String
      gitHashOrDieQ = projectError gitHashQ
  in $$(qToCode $ projectError gitHashQ)
:}
...

Since: 0.1

projectErrorMap :: Functor f => (e -> String) -> f (Either e a) -> f a Source #

Projects Left via error, rendering via the given function. Hence an error will cause a compilation failure.

Examples

Expand
>>> :{
  let gitHashOrDieQ :: Q String
      gitHashOrDieQ = (projectErrorMap show) gitHashQ
  in $$(qToCode $ (projectErrorMap show) gitHashQ)
:}
...

Since: 0.1

Errors

data GitError Source #

Errors that can be encountered with git.

Since: 0.1

Constructors

GitNotFound

Since: 0.1

GitRunError String

Since: 0.1

Instances

Instances details
Exception GitError Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils.Git

Show GitError Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils.Git

Eq GitError Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils.Git

Lift GitError Source #

Since: 0.1

Instance details

Defined in Development.GitRev.Utils.Git

Methods

lift :: Quote m => GitError -> m Exp #

liftTyped :: forall (m :: Type -> Type). Quote m => GitError -> Code m GitError #

newtype LookupEnvError Source #

Environment variable lookup failure. The value is the variable we attempted to look up.

Since: 0.1

Constructors

MkLookupEnvError String 

Utilities

embedGitError Source #

Arguments

:: forall f p a. (Bifunctor p, Functor f) 
=> f (p GitError a)

.

-> f (p GitOrLookupEnvError a) 

Embeds a GitError in the larger GitOrLookupEnvError.

Examples

Expand
>>> :{
  let q :: Q (Either GitError ())
      q = pure (Left GitNotFound)
  in runQ $ embedGitError q
:}
Left (GitOrLookupEnvGit GitNotFound)

Since: 0.1

embedLookupEnvError Source #

Arguments

:: forall f p a. (Bifunctor p, Functor f) 
=> f (p LookupEnvError a)

.

-> f (p GitOrLookupEnvError a) 

Embeds a LookupEnvError in the larger GitOrLookupEnvError.

Examples

Expand
>>> :{
  let q :: Q (Either LookupEnvError ())
      q = pure (Left $ MkLookupEnvError "VAR")
  in runQ $ embedLookupEnvError q
:}
Left (GitOrLookupEnvLookupEnv (MkLookupEnvError "VAR"))

Since: 0.1

joinLookupEnvGitErrors Source #

Arguments

:: (Bifunctor p, forall e. Monad (p e)) 
=> p LookupEnvError (p GitError a)

.

-> p GitOrLookupEnvError a 

Utility function for joining lookup and git errors.

Examples

Expand
>>> :{
  let e :: Either LookupEnvError (Either GitError ())
      e = Right (Left GitNotFound)
  in joinLookupEnvGitErrors e
:}
Left (GitOrLookupEnvGit GitNotFound)

Since: 0.1

joinGitLookupEnvErrors Source #

Arguments

:: (Bifunctor p, forall e. Monad (p e)) 
=> p GitError (p LookupEnvError a)

.

-> p GitOrLookupEnvError a 

Utility function for joining git and lookup errors.

Examples

Expand
>>> :{
  let e :: Either GitError (Either LookupEnvError ())
      e = Right (Left $ MkLookupEnvError "VAR")
  in joinGitLookupEnvErrors e
:}
Left (GitOrLookupEnvLookupEnv (MkLookupEnvError "VAR"))

Since: 0.1