diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,12 @@
 
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
+## 0.3.1.2 - 2022-04-08
+
+### Fixed
+
+ - `getFlakeFromGit` now handles branch names correctly
+
 ## 0.3.1.1 - 2022-03-21
 
 ### Fixed
diff --git a/hercules-ci-cnix-expr.cabal b/hercules-ci-cnix-expr.cabal
--- a/hercules-ci-cnix-expr.cabal
+++ b/hercules-ci-cnix-expr.cabal
@@ -1,7 +1,7 @@
 cabal-version: 2.4
 
 name:           hercules-ci-cnix-expr
-version:        0.3.1.1
+version:        0.3.1.2
 synopsis:       Bindings for the Nix evaluator
 category:       Nix, CI, Testing, DevOps
 homepage:       https://docs.hercules-ci.com
@@ -137,13 +137,16 @@
     , base
     , bytestring
     , containers
+    , filepath
     , hercules-ci-cnix-expr
     , hercules-ci-cnix-store
     , hspec
+    , process
     , protolude
     , QuickCheck
     , scientific
     , temporary
+    , text
     , unordered-containers
     , vector
   build-tool-depends:
diff --git a/src/Hercules/CNix/Expr.hs b/src/Hercules/CNix/Expr.hs
--- a/src/Hercules/CNix/Expr.hs
+++ b/src/Hercules/CNix/Expr.hs
@@ -488,9 +488,36 @@
   }|]
 
 getFlakeFromGit :: Ptr EvalState -> Text -> Text -> Text -> IO RawValue
-getFlakeFromGit evalState url ref rev = do
-  -- TODO: use a URL library, test
-  getFlakeFromFlakeRef evalState (encodeUtf8 $ "git+" <> url <> "?ref=" <> ref <> "&rev=" <> rev)
+getFlakeFromGit evalState url ref rev =
+  let
+    urlb = encodeUtf8 url
+    refb = encodeUtf8 ref
+    revb = encodeUtf8 rev
+  in [C.throwBlock| Value *{
+    EvalState &evalState = *$(EvalState *evalState);
+    Value *r = new (NoGC) Value();
+    std::string url($bs-ptr:urlb, $bs-len:urlb);
+    std::string ref($bs-ptr:refb, $bs-len:refb);
+    std::string rev($bs-ptr:revb, $bs-len:revb);
+
+    fetchers::Attrs attrs;
+    attrs.emplace("type", "git");
+    attrs.emplace("url", url);
+    attrs.emplace("ref", ref);
+    attrs.emplace("rev", rev);
+
+    auto flakeRef = nix::FlakeRef::fromAttrs(attrs);
+    nix::flake::callFlake(evalState,
+      nix::flake::lockFlake(evalState, flakeRef,
+        nix::flake::LockFlags {
+          .updateLockFile = false,
+          .useRegistries = false,
+          .allowMutable = false,
+        }),
+      *r);
+    return r;
+  }|]
+    >>= mkRawValue
 
 getFlakeFromArchiveUrl :: Ptr EvalState -> Text -> IO RawValue
 getFlakeFromArchiveUrl evalState url = do
diff --git a/test/Hercules/CNix/ExprSpec.hs b/test/Hercules/CNix/ExprSpec.hs
--- a/test/Hercules/CNix/ExprSpec.hs
+++ b/test/Hercules/CNix/ExprSpec.hs
@@ -5,18 +5,23 @@
 import qualified Data.Aeson as A
 import qualified Data.ByteString as BS
 import qualified Data.ByteString.Lazy as BL
+import Data.List ((\\))
 import qualified Data.Map as M
+import qualified Data.Text as T
+import qualified Data.Text.IO as T
 import Hercules.CNix.Expr
 import qualified Hercules.CNix.Expr.Typed as Typed
 import Protolude hiding (evalState)
 import qualified SingleState
+import System.FilePath ((</>))
+import System.IO.Temp (withSystemTempDirectory)
+import System.Process (waitForProcess)
+import qualified System.Process
 import Test.Hspec
 
 setup :: (Ptr EvalState -> IO a) -> IO a
 setup f = f SingleState.evalState
 
--- withTempStore \store -> withEvalState store f
-
 checkWithNix :: Ptr EvalState -> ByteString -> RawValue -> IO ()
 checkWithNix evalState s a = do
   f <- valueFromExpressionString evalState s "/home/someuser/src/dummy-project"
@@ -120,3 +125,33 @@
         Right r -> pure (r :: A.Value)
       a <- toRawValue evalState jsonValue
       a & checkWithNix evalState ("a: a == builtins.fromJSON ''" <> jsonExample <> "''")
+  describe "getFlakeFromGit" do
+    it "works" $ setup \evalState -> do
+      withSystemTempDirectory "cnix-expr-test" \dir -> do
+        let run exe args = do
+              -- putErrText $ "Running " <> show exe <> " " <> show args
+              r <- System.Process.withCreateProcess ((System.Process.proc exe args) {System.Process.cwd = Just dir}) \_ _ _ -> waitForProcess
+              case r of
+                ExitFailure n -> panic ("command " <> show exe <> " " <> show args <> " failed with exit code " <> show n)
+                ExitSuccess -> pass
+            readProc exe args = do
+              -- putErrText $ "Reading " <> show exe <> " " <> show args
+              (t, r) <- System.Process.withCreateProcess ((System.Process.proc exe args) {System.Process.cwd = Just dir, System.Process.std_out = System.Process.CreatePipe}) \_ (Just stdo) _ p -> do
+                t <- T.hGetContents stdo
+                (t,) <$> waitForProcess p
+              case r of
+                ExitFailure n -> panic ("command " <> show exe <> " " <> show args <> " failed with exit code " <> show n)
+                ExitSuccess -> pass
+              pure t
+
+        run "git" ["-c", "init.defaultBranch=main", "init"]
+        writeFile (dir </> "flake.nix") "{ outputs = { ... }: { hello = ''world''; }; }"
+        run "git" ["add", "flake.nix"]
+        run "git" ["-c", "user.name=Tester", "-c", "user.email=test@example.com", "commit", "-m", "go go go", "flake.nix"]
+        -- man git check-ref-format
+        let ref = (['!' .. 'A'] ++ ['Z' .. 'a'] ++ "Ü" ++ ['z' .. '}']) \\ "\\~^?*[:"
+        run "git" ["branch", ref]
+        rev <- T.strip . toS <$> readProc "git" ["show", "--format=format:%H", "--quiet"]
+        r <- getFlakeFromGit evalState ("file://" <> toS dir) (toS ref) rev
+        r & checkWithNix evalState "r: r.hello == ''world''"
+        r & checkWithNix evalState ("r: r.sourceInfo.rev == ''" <> encodeUtf8 rev <> "''")
diff --git a/test/TestMain.hs b/test/TestMain.hs
--- a/test/TestMain.hs
+++ b/test/TestMain.hs
@@ -4,11 +4,14 @@
 import Protolude
 import SingleState
 import qualified Spec
+import System.Environment (getEnvironment, setEnv)
+import System.FilePath ((</>))
+import System.IO.Temp (withSystemTempDirectory)
 import System.Mem (performMajorGC)
 import Test.Hspec.Runner
 
 main :: IO ()
-main = do
+main = withTempHome do
   init
   withGlobalState $ do
     -- for_ [(1 :: Int)..1000] \_ -> do
@@ -22,3 +25,18 @@
       defaultConfig
         { configColorMode = ColorAlways
         }
+
+-- Perfect for any housing crisis
+withTempHome :: IO () -> IO ()
+withTempHome io =
+  withSystemTempDirectory "test-home" \home -> do
+    -- HOME may not affect Nix getHome(), as its static variable may already be bound
+    setEnv "HOME" home
+
+    setEnv "XDG_CACHE_HOME" (home </> ".cache")
+    setEnv "XDG_CONFIG_HOME" (home </> ".config")
+    setEnv "XDG_DATA_HOME" (home </> ".local" </> "share")
+
+    getEnvironment >>= traverse_ \(k, v) ->
+      putStrLn (k <> " = " <> v)
+    io
