From c94e2230bd1cd807eb279202f9d6e40cc3c54b36 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 02:59:16 -0500 Subject: feat: implement t-rWa5yilwM.1 --- Omni/Agent/Git.hs | 139 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 Omni/Agent/Git.hs (limited to 'Omni/Agent/Git.hs') diff --git a/Omni/Agent/Git.hs b/Omni/Agent/Git.hs new file mode 100644 index 0000000..cbb63bd --- /dev/null +++ b/Omni/Agent/Git.hs @@ -0,0 +1,139 @@ +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE NoImplicitPrelude #-} + +-- | Git operations for the agent. +-- +-- : out omni-agent-git +-- : dep temporary +module Omni.Agent.Git + ( checkout, + main, + test, + ) +where + +import Alpha +import qualified Data.Text as Text +import qualified Omni.Log as Log +import Omni.Test ((@=?)) +import qualified Omni.Test as Test +import qualified System.Directory as Directory +import qualified System.Exit as Exit +import qualified System.IO.Temp as Temp +import qualified System.Process as Process + +main :: IO () +main = Test.run test + +test :: Test.Tree +test = + Test.group + "Omni.Agent.Git" + [ Test.unit "checkout works" <| do + Temp.withSystemTempDirectory "omni-agent-git-test" <| \tmpDir -> do + let repo = tmpDir <> "/repo" + Directory.createDirectory repo + -- init repo + git repo ["init"] + git repo ["branch", "-m", "master"] + git repo ["config", "user.email", "you@example.com"] + git repo ["config", "user.name", "Your Name"] + + -- commit A + writeFile (repo <> "/a.txt") "A" + git repo ["add", "a.txt"] + git repo ["commit", "-m", "A"] + shaA <- getSha repo "HEAD" + + -- create branch dev + git repo ["checkout", "-b", "dev"] + + -- commit B + writeFile (repo <> "/b.txt") "B" + git repo ["add", "b.txt"] + git repo ["commit", "-m", "B"] + shaB <- getSha repo "HEAD" + + -- switch back to master + git repo ["checkout", "master"] + + -- Test 1: checkout dev + checkout repo "dev" + current <- getSha repo "HEAD" + shaB @=? current + + -- Test 2: checkout master + checkout repo "master" + current' <- getSha repo "HEAD" + shaA @=? current' + + -- Test 3: dirty state + writeFile (repo <> "/a.txt") "DIRTY" + checkout repo "dev" + current'' <- getSha repo "HEAD" + shaB @=? current'' + -- Verify dirty file is gone/overwritten (b.txt should exist, a.txt should be A from master? No, a.txt is in A and B) + -- Wait, in dev, a.txt is "A". + content <- readFile (repo <> "/a.txt") + "A" @=? content + + -- Test 4: untracked file + writeFile (repo <> "/untracked.txt") "DELETE ME" + checkout repo "master" + exists <- Directory.doesFileExist (repo <> "/untracked.txt") + False @=? exists + ] + +getSha :: FilePath -> String -> IO String +getSha dir ref = do + let cmd = (Process.proc "git" ["rev-parse", ref]) {Process.cwd = Just dir} + (code, out, _) <- Process.readCreateProcessWithExitCode cmd "" + case code of + Exit.ExitSuccess -> pure <| strip out + _ -> panic "getSha failed" + +-- | Checkout a specific ref (SHA, branch, tag) in the given repository path. +-- This function ensures the repository is in the correct state by: +-- 1. Fetching all updates +-- 2. Checking out the ref (forcing overwrites of local changes) +-- 3. Resetting hard to the ref (to ensure clean state) +-- 4. Cleaning untracked files +-- 5. Updating submodules +checkout :: FilePath -> Text -> IO () +checkout repoPath ref = do + let r = Text.unpack ref + + Log.info ["git", "checkout", ref, "in", Text.pack repoPath] + + -- Fetch all refs to ensure we have the target + git repoPath ["fetch", "--all", "--tags"] + + -- Checkout the ref, discarding local changes + git repoPath ["checkout", "--force", r] + + -- Reset hard to ensure we are exactly at the target state + git repoPath ["reset", "--hard", r] + + -- Remove untracked files and directories + git repoPath ["clean", "-fdx"] + + -- Update submodules + git repoPath ["submodule", "update", "--init", "--recursive"] + + Log.good ["git", "checkout", "complete"] + Log.br + +-- | Run a git command in the given directory. +git :: FilePath -> [String] -> IO () +git dir args = do + let cmd = (Process.proc "git" args) {Process.cwd = Just dir} + (exitCode, out, err) <- Process.readCreateProcessWithExitCode cmd "" + case exitCode of + Exit.ExitSuccess -> pure () + Exit.ExitFailure code -> do + Log.fail ["git command failed", Text.pack (show args), "code: " <> show code] + Log.info [Text.pack out] + Log.info [Text.pack err] + Log.br + panic <| "git command failed: git " <> show args -- cgit v1.2.3 From 04c9043b89be694d8e74cf9e11b8648488416aee Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Fri, 21 Nov 2025 04:25:10 -0500 Subject: fix: lint errors in Omni/Agent/Git.hs and Log.hs Amp-Thread-ID: https://ampcode.com/threads/T-7109f8d0-feb4-4a24-bc4b-37743227e2cb Co-authored-by: Amp --- Omni/Agent/Git.hs | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) (limited to 'Omni/Agent/Git.hs') diff --git a/Omni/Agent/Git.hs b/Omni/Agent/Git.hs index cbb63bd..a7afb20 100644 --- a/Omni/Agent/Git.hs +++ b/Omni/Agent/Git.hs @@ -1,4 +1,3 @@ -{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE NoImplicitPrelude #-} @@ -39,7 +38,7 @@ test = git repo ["branch", "-m", "master"] git repo ["config", "user.email", "you@example.com"] git repo ["config", "user.name", "Your Name"] - + -- commit A writeFile (repo <> "/a.txt") "A" git repo ["add", "a.txt"] @@ -48,26 +47,26 @@ test = -- create branch dev git repo ["checkout", "-b", "dev"] - + -- commit B writeFile (repo <> "/b.txt") "B" git repo ["add", "b.txt"] git repo ["commit", "-m", "B"] shaB <- getSha repo "HEAD" - + -- switch back to master git repo ["checkout", "master"] - + -- Test 1: checkout dev checkout repo "dev" current <- getSha repo "HEAD" shaB @=? current - + -- Test 2: checkout master checkout repo "master" current' <- getSha repo "HEAD" shaA @=? current' - + -- Test 3: dirty state writeFile (repo <> "/a.txt") "DIRTY" checkout repo "dev" @@ -77,7 +76,7 @@ test = -- Wait, in dev, a.txt is "A". content <- readFile (repo <> "/a.txt") "A" @=? content - + -- Test 4: untracked file writeFile (repo <> "/untracked.txt") "DELETE ME" checkout repo "master" @@ -103,9 +102,9 @@ getSha dir ref = do checkout :: FilePath -> Text -> IO () checkout repoPath ref = do let r = Text.unpack ref - + Log.info ["git", "checkout", ref, "in", Text.pack repoPath] - + -- Fetch all refs to ensure we have the target git repoPath ["fetch", "--all", "--tags"] @@ -120,7 +119,7 @@ checkout repoPath ref = do -- Update submodules git repoPath ["submodule", "update", "--init", "--recursive"] - + Log.good ["git", "checkout", "complete"] Log.br -- cgit v1.2.3