git.iko.soy/typeable/gitrev
on master help sign in
.travis.yml+85−2
since splitsnapshots
unifiedsplit
@@ -1,2 +1,85 @@
1+# This file has been generated -- see https://github.com/hvr/multi-ghc-travis
1−language: haskell
2+language: c
2−ghc: 7.8
3+sudo: false
4+
5+cache:
6+ directories:
7+ - $HOME/.cabsnap
8+ - $HOME/.cabal/packages
9+
10+before_cache:
11+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/build-reports.log
12+ - rm -fv $HOME/.cabal/packages/hackage.haskell.org/00-index.tar
13+
14+matrix:
15+ include:
16+ - env: CABALVER=1.16 GHCVER=7.6.3
17+ compiler: ": #GHC 7.6.3"
18+ addons: {apt: {packages: [cabal-install-1.16,ghc-7.6.3], sources: [hvr-ghc]}}
19+ - env: CABALVER=1.18 GHCVER=7.8.4
20+ compiler: ": #GHC 7.8.4"
21+ addons: {apt: {packages: [cabal-install-1.18,ghc-7.8.4], sources: [hvr-ghc]}}
22+ - env: CABALVER=1.22 GHCVER=7.10.3
23+ compiler: ": #GHC 7.10.3"
24+ addons: {apt: {packages: [cabal-install-1.22,ghc-7.10.3], sources: [hvr-ghc]}}
25+ - env: CABALVER=1.24 GHCVER=8.0.2
26+ compiler: ": #GHC 8.0.2"
27+ addons: {apt: {packages: [cabal-install-1.24,ghc-8.0.2], sources: [hvr-ghc]}}
28+
29+before_install:
30+ - unset CC
31+ - export PATH=/opt/ghc/$GHCVER/bin:/opt/cabal/$CABALVER/bin:$PATH
32+
33+install:
34+ - cabal --version
35+ - echo "$(ghc --version) [$(ghc --print-project-git-commit-id 2> /dev/null || echo '?')]"
36+ - if [ -f $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz ];
37+ then
38+ zcat $HOME/.cabal/packages/hackage.haskell.org/00-index.tar.gz >
39+ $HOME/.cabal/packages/hackage.haskell.org/00-index.tar;
40+ fi
41+ - travis_retry cabal update -v
42+ - sed -i 's/^jobs:/-- jobs:/' ${HOME}/.cabal/config
43+ - cabal install --only-dependencies --enable-tests --enable-benchmarks --dry -v > installplan.txt
44+ - sed -i -e '1,/^Resolving /d' installplan.txt; cat installplan.txt
45+
46+# check whether current requested install-plan matches cached package-db snapshot
47+ - if diff -u $HOME/.cabsnap/installplan.txt installplan.txt;
48+ then
49+ echo "cabal build-cache HIT";
50+ rm -rfv .ghc;
51+ cp -a $HOME/.cabsnap/ghc $HOME/.ghc;
52+ cp -a $HOME/.cabsnap/lib $HOME/.cabsnap/share $HOME/.cabsnap/bin $HOME/.cabal/;
53+ else
54+ echo "cabal build-cache MISS";
55+ rm -rf $HOME/.cabsnap;
56+ mkdir -p $HOME/.ghc $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin;
57+ cabal install --only-dependencies --enable-tests --enable-benchmarks;
58+ fi
59+
60+# snapshot package-db on cache miss
61+ - if [ ! -d $HOME/.cabsnap ];
62+ then
63+ echo "snapshotting package-db to build-cache";
64+ mkdir $HOME/.cabsnap;
65+ cp -a $HOME/.ghc $HOME/.cabsnap/ghc;
66+ cp -a $HOME/.cabal/lib $HOME/.cabal/share $HOME/.cabal/bin installplan.txt $HOME/.cabsnap/;
67+ fi
68+
69+# Here starts the actual work to be performed for the package under test;
70+# any command which exits with a non-zero exit code causes the build to fail.
71+script:
72+ - if [ -f configure.ac ]; then autoreconf -i; fi
73+ - cabal configure --enable-tests --enable-benchmarks -v2 # -v2 provides useful information for debugging
74+ - cabal build # this builds all libraries and executables (including tests/benchmarks)
75+ - cabal test
76+ - cabal check
77+ - cabal sdist # tests that a source-distribution can be generated
78+
79+# Check that the resulting source distribution can be built & installed.
80+# If there are no other `.tar.gz` files in `dist`, this can be even simpler:
81+# `cabal install --force-reinstalls dist/*-*.tar.gz`
82+ - SRC_TGZ=$(cabal info . | awk '{print $2;exit}').tar.gz &&
83+ (cd dist && cabal install --force-reinstalls "$SRC_TGZ")
84+
85+# EOF
Example.hs+6−1
@@ -1,11 +1,16 @@
1+{-# LANGUAGE NoImplicitPrelude #-}
1{-# LANGUAGE TemplateHaskell #-}
2{-# LANGUAGE TemplateHaskell #-}
2module Example where
3module Example where
4+import Prelude (String, concat, error, otherwise)
3import Development.GitRev
5import Development.GitRev
4
6
5panic :: String -> a
7panic :: String -> a
6panic msg = error panicMsg
8panic msg = error panicMsg
7 where panicMsg =
9 where panicMsg =
8− concat [ "[panic ", $(gitBranch), "@", $(gitHash), dirty, "] ", msg ]
10+ concat [ "[panic ", $(gitBranch), "@", $(gitHash)
11+ , " (", $(gitCommitDate), ")"
12+ , " (", $(gitCommitCount), " commits in HEAD)"
13+ , dirty, "] ", msg ]
9 dirty | $(gitDirty) = " (uncommitted files present)"
14 dirty | $(gitDirty) = " (uncommitted files present)"
10 | otherwise = ""
15 | otherwise = ""
11
16
README.md+11−5
@@ -1,3 +1,5 @@
1+[![Build Status](https://travis-ci.org/acfoltzer/gitrev.svg?branch=master)](https://travis-ci.org/acfoltzer/gitrev)
2+
1Some handy Template Haskell splices for including the current git hash
3Some handy Template Haskell splices for including the current git hash
2and branch in the code of your project. Useful for including in panic
4and branch in the code of your project. Useful for including in panic
3messages, `--version` output, or diagnostic info for more informative
5messages, `--version` output, or diagnostic info for more informative
@@ -9,9 +11,13 @@ places the current git hash might be stored:
91. Detached HEAD: the hash is in `.git/HEAD`
111. Detached HEAD: the hash is in `.git/HEAD`
102. On a branch or tag: the hash is in a file pointed to by `.git/HEAD`
122. On a branch or tag: the hash is in a file pointed to by `.git/HEAD`
11in a location like `.git/refs/heads`
13in a location like `.git/refs/heads`
12−3. On a branch or tag but in a repository with packed refs: the hash is
14+3. On a branch or tag but in a repository with packed refs: the hash
13−in `.git/packed-refs`
15+is in `.git/packed-refs`
16+4. In any of the above situations, if the current repo is checked out
17+as a submodule, follow the reference to its `.git` directory first
18+
19+These files are added as dependencies to modules that use `GitRev`, and
20+so the module should be rebuilt automatically whenever these files
21+change.
14
22
15−These situations all arise under normal development workflows, but
16−there might be further scenarios that cause problems. Let me know if
23+If you run into further scenarios that cause problems, let me know!
17−you run into them!
gitrev.cabal+6−2
@@ -1,5 +1,5 @@
1name: gitrev
1name: gitrev
2−version: 1.0.0
2+version: 1.3.1
3synopsis: Compile git revision info into Haskell projects
3synopsis: Compile git revision info into Haskell projects
4homepage: https://github.com/acfoltzer/gitrev
4homepage: https://github.com/acfoltzer/gitrev
5license: BSD3
5license: BSD3
@@ -9,17 +9,21 @@ maintainer: acfoltzer@galois.com
9category: Development
9category: Development
10build-type: Simple
10build-type: Simple
11cabal-version: >=1.10
11cabal-version: >=1.10
12+tested-with: GHC == 7.6.3, GHC == 7.8.4, GHC == 7.10.3, GHC == 8.0.2
13+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.
12
14
13source-repository head
15source-repository head
14 type: git
16 type: git
15 location: https://github.com/acfoltzer/gitrev.git
17 location: https://github.com/acfoltzer/gitrev.git
16
18
17library
19library
18− build-depends: base >= 4.7,
20+ build-depends: base >= 4.6 && < 5,
21+ base-compat >= 0.6.0,
19 directory,
22 directory,
20 filepath,
23 filepath,
21 template-haskell,
24 template-haskell,
22 process
25 process
23 hs-source-dirs: src
26 hs-source-dirs: src
27+ ghc-options: -Wall
24 default-language: Haskell2010
28 default-language: Haskell2010
25 exposed-modules: Development.GitRev
29 exposed-modules: Development.GitRev
src/Development/GitRev.hs+90−15
@@ -1,3 +1,6 @@
1+{-# LANGUAGE LambdaCase #-}
2+{-# LANGUAGE MultiWayIf #-}
3+
1-- |
4-- |
2-- Module : $Header$
5-- Module : $Header$
3-- Copyright : (c) 2015 Adam C. Foltzer
6-- Copyright : (c) 2015 Adam C. Foltzer
@@ -17,18 +20,28 @@
17-- > panic :: String -> a
20-- > panic :: String -> a
18-- > panic msg = error panicMsg
21-- > panic msg = error panicMsg
19-- > where panicMsg =
22-- > where panicMsg =
20−-- > concat [ "[panic ", $(gitBranch), "@", $(gitHash), dirty, "] ", msg ]
23+-- > concat [ "[panic ", $(gitBranch), "@", $(gitHash)
24+-- > , " (", $(gitCommitDate), ")"
25+-- > , " (", $(gitCommitCount), " commits in HEAD)"
26+-- > , dirty, "] ", msg ]
21-- > dirty | $(gitDirty) = " (uncommitted files present)"
27-- > dirty | $(gitDirty) = " (uncommitted files present)"
22-- > | otherwise = ""
28-- > | otherwise = ""
23-- >
29-- >
24-- > main = panic "oh no!"
30-- > main = panic "oh no!"
25--
31--
26-- > % cabal exec runhaskell Example.hs
32-- > % cabal exec runhaskell Example.hs
27−-- > Example.hs: [panic master@4a0a592c37ad908889bd2a7a411923a903ed05a3 (uncommitted files present)] oh no!
33+-- > Example.hs: [panic master@2ae047ba5e4a6f0f3e705a43615363ac006099c1 (Mon Jan 11 11:50:59 2016 -0800) (14 commits in HEAD) (uncommitted files present)] oh no!
28
34
29−module Development.GitRev (gitHash, gitBranch, gitDirty) where
35+module Development.GitRev
36+ ( gitBranch
37+ , gitCommitCount
38+ , gitCommitDate
39+ , gitDescribe
40+ , gitDirty
41+ , gitDirtyTracked
42+ , gitHash
43+ ) where
30
44
31−import Control.Applicative
32import Control.Exception
45import Control.Exception
33import Control.Monad
46import Control.Monad
34import Data.Maybe
47import Data.Maybe
@@ -39,18 +52,21 @@ import System.Exit
39import System.FilePath
52import System.FilePath
40import System.Process
53import System.Process
41
54
55+import Prelude ()
56+import Prelude.Compat
57+
42-- | Run git with the given arguments and no stdin, returning the
58-- | Run git with the given arguments and no stdin, returning the
43-- stdout output. If git isn't available or something goes wrong,
59-- stdout output. If git isn't available or something goes wrong,
44-- return the second argument.
60-- return the second argument.
45−runGit :: [String] -> String -> Q String
61+runGit :: [String] -> String -> IndexUsed -> Q String
46−runGit args def = do
62+runGit args def useIdx = do
47 let oops :: SomeException -> IO (ExitCode, String, String)
63 let oops :: SomeException -> IO (ExitCode, String, String)
48 oops _e = return (ExitFailure 1, def, "")
64 oops _e = return (ExitFailure 1, def, "")
49 gitFound <- runIO $ isJust <$> findExecutable "git"
65 gitFound <- runIO $ isJust <$> findExecutable "git"
50 if gitFound
66 if gitFound
51 then do
67 then do
52 -- a lot of bookkeeping to record the right dependencies
68 -- a lot of bookkeeping to record the right dependencies
53− pwd <- runIO getCurrentDirectory
69+ pwd <- runIO getDotGit
54 let hd = pwd </> ".git" </> "HEAD"
70 let hd = pwd </> ".git" </> "HEAD"
55 index = pwd </> ".git" </> "index"
71 index = pwd </> ".git" </> "index"
56 packedRefs = pwd </> ".git" </> "packed-refs"
72 packedRefs = pwd </> ".git" </> "packed-refs"
@@ -58,8 +74,7 @@ runGit args def = do
58 when hdExists $ do
74 when hdExists $ do
59 -- the HEAD file either contains the hash of a detached head
75 -- the HEAD file either contains the hash of a detached head
60 -- or a pointer to the file that contains the hash of the head
76 -- or a pointer to the file that contains the hash of the head
61− hdRef <- runIO $ readFile hd
77+ splitAt 5 `fmap` runIO (readFile hd) >>= \case
62− case splitAt 5 hdRef of
63 -- pointer to ref
78 -- pointer to ref
64 ("ref: ", relRef) -> do
79 ("ref: ", relRef) -> do
65 let ref = pwd </> ".git" </> relRef
80 let ref = pwd </> ".git" </> relRef
@@ -69,7 +84,7 @@ runGit args def = do
69 _hash -> addDependentFile hd
84 _hash -> addDependentFile hd
70 -- add the index if it exists to set the dirty flag
85 -- add the index if it exists to set the dirty flag
71 indexExists <- runIO $ doesFileExist index
86 indexExists <- runIO $ doesFileExist index
72− when indexExists $ addDependentFile index
87+ when (indexExists && useIdx == IdxUsed) $ addDependentFile index
73 -- if the refs have been packed, the info we're looking for
88 -- if the refs have been packed, the info we're looking for
74 -- might be in that file rather than the one-file-per-ref case
89 -- might be in that file rather than the one-file-per-ref case
75 -- handled above
90 -- handled above
@@ -82,24 +97,84 @@ runGit args def = do
82 ExitFailure _ -> return def
97 ExitFailure _ -> return def
83 else return def
98 else return def
84
99
100+-- | Determine where our @.git@ directory is, in case we're in a
101+-- submodule.
102+getDotGit :: IO FilePath
103+getDotGit = do
104+ pwd <- getGitRoot
105+ let dotGit = pwd </> ".git"
106+ oops = return dotGit -- it's gonna fail, that's fine
107+ isDir <- doesDirectoryExist dotGit
108+ isFile <- doesFileExist dotGit
109+ if | isDir -> return dotGit
110+ | not isFile -> oops
111+ | isFile ->
112+ splitAt 8 `fmap` readFile dotGit >>= \case
113+ ("gitdir: ", relDir) -> do
114+ isRelDir <- doesDirectoryExist relDir
115+ if isRelDir
116+ then return relDir
117+ else oops
118+ _ -> oops
119+
120+-- | Get the root directory of the Git repo.
121+getGitRoot :: IO FilePath
122+getGitRoot = do
123+ pwd <- getCurrentDirectory
124+ (code, out, _) <-
125+ readProcessWithExitCode "git" ["rev-parse", "--show-toplevel"] ""
126+ case code of
127+ ExitSuccess -> return $ takeWhile (/= '\n') out
128+ ExitFailure _ -> return pwd -- later steps will fail, that's fine
129+
130+-- | Type to flag if the git index is used or not in a call to runGit
131+data IndexUsed = IdxUsed -- ^ The git index is used
132+ | IdxNotUsed -- ^ The git index is /not/ used
133+ deriving (Eq)
134+
85-- | Return the hash of the current git commit, or @UNKNOWN@ if not in
135-- | Return the hash of the current git commit, or @UNKNOWN@ if not in
86-- a git repository
136-- a git repository
87gitHash :: ExpQ
137gitHash :: ExpQ
88gitHash =
138gitHash =
89− stringE =<< runGit ["rev-parse", "HEAD"] "UNKNOWN"
139+ stringE =<< runGit ["rev-parse", "HEAD"] "UNKNOWN" IdxNotUsed
90
140
91-- | Return the branch (or tag) name of the current git commit, or @UNKNOWN@
141-- | Return the branch (or tag) name of the current git commit, or @UNKNOWN@
92-- if not in a git repository. For detached heads, this will just be
142-- if not in a git repository. For detached heads, this will just be
93-- "HEAD"
143-- "HEAD"
94gitBranch :: ExpQ
144gitBranch :: ExpQ
95gitBranch =
145gitBranch =
96− stringE =<< runGit ["rev-parse", "--abbrev-ref", "HEAD"] "UNKNOWN"
146+ stringE =<< runGit ["rev-parse", "--abbrev-ref", "HEAD"] "UNKNOWN" IdxNotUsed
147+
148+-- | Return the long git description for the current git commit, or
149+-- @UNKNOWN@ if not in a git repository.
150+gitDescribe :: ExpQ
151+gitDescribe =
152+ stringE =<< runGit ["describe", "--long", "--always"] "UNKNOWN" IdxNotUsed
97
153
98-- | Return @True@ if there are non-committed files present in the
154-- | Return @True@ if there are non-committed files present in the
99-- repository
155-- repository
100gitDirty :: ExpQ
156gitDirty :: ExpQ
101gitDirty = do
157gitDirty = do
102− output <- runGit ["status", "--porcelain"] ""
158+ output <- runGit ["status", "--porcelain"] "" IdxUsed
159+ case output of
160+ "" -> conE falseName
161+ _ -> conE trueName
162+
163+-- | Return @True@ if there are non-commited changes to tracked files
164+-- present in the repository
165+gitDirtyTracked :: ExpQ
166+gitDirtyTracked = do
167+ output <- runGit ["status", "--porcelain","--untracked-files=no"] "" IdxUsed
103 case output of
168 case output of
104− "" -> conE $ mkName "Prelude.False"
169+ "" -> conE falseName
105− _ -> conE $ mkName "Prelude.True"
170+ _ -> conE trueName
171+
172+-- | Return the number of commits in the current head
173+gitCommitCount :: ExpQ
174+gitCommitCount =
175+ stringE =<< runGit ["rev-list", "HEAD", "--count"] "UNKNOWN" IdxNotUsed
176+
177+-- | Return the commit date of the current head
178+gitCommitDate :: ExpQ
179+gitCommitDate =
180+ stringE =<< runGit ["log", "HEAD", "-1", "--format=%cd"] "UNKNOWN" IdxNotUsed