Commit and push uncommitted changes on startup

- watchRepo: call syncRepo at startup to commit any pre-existing
  uncommitted changes before entering the file watcher
- Add gitPush function for git push <remote> <branch>
- Add push step to syncRepo after successful pull (with failure
  notification via notifyUser)
- Export syncRepo and gitPush for testing
- Test: startup sync commits and pushes uncommitted changes
This commit is contained in:
2026-05-13 22:10:30 -04:00
parent eb847c03f6
commit 63e034fd01
2 changed files with 49 additions and 2 deletions
+23 -1
View File
@@ -14,6 +14,7 @@ module Converge (
-- * Running
runConverge,
syncRepo,
-- * Logger
Logger (..),
@@ -22,6 +23,7 @@ module Converge (
-- * Git operations
gitCommitAll,
gitPull,
gitPush,
runGitIn,
hasConflicts,
isInMiddleOfOperation,
@@ -190,6 +192,8 @@ watchRepo opts logger repo = do
-- Drain any events that arrived during the debounce window
void $ tryTakeMVar pending
syncRepo opts logger repo
-- Sync any uncommitted changes that exist on startup
syncRepo opts logger repo
bracket
(startManagerConf defaultConfig)
stopManager
@@ -293,7 +297,18 @@ syncRepo opts logger repo = do
notifyUser opts
"Converge: Merge Conflict"
("A merge conflict occurred in " <> rcPath repo <> ". Manual resolution required.")
else logRepo logger repo "sync complete, no conflicts"
else do
logRepo logger repo "sync complete, no conflicts"
-- Push local commits to remote
(pushCode, pushOut, pushErr) <- gitPush repo
case pushCode of
ExitSuccess ->
logRepo logger repo "push succeeded"
_ -> do
logRepo logger repo ("push FAILED (exit " <> T.pack (show pushCode) <> "): " <> T.strip (pushOut <> pushErr))
notifyUser opts
"Converge: Push Failed"
("Push failed in " <> rcPath repo <> " (exit " <> show pushCode <> ")")
----------------------------------------------------------------------
-- Git operations
@@ -315,6 +330,13 @@ gitPull repo =
(rcPath repo)
["pull", "--rebase", T.unpack (rcRemote repo), T.unpack (rcBranch repo)]
-- | Push to the configured remote and branch.
gitPush :: RepoConfig -> IO (ExitCode, Text, Text)
gitPush repo =
runGitIn
(rcPath repo)
["push", T.unpack (rcRemote repo), T.unpack (rcBranch repo)]
-- | Check if the repository currently has merge conflicts.
hasConflicts :: FilePath -> IO Bool
hasConflicts repoPath = do
+26 -1
View File
@@ -1,6 +1,6 @@
module GitCommitSpec (spec) where
import Converge (gitCommitAll)
import Converge (Options (..), RepoConfig (..), defaultOptions, defaultRepoConfig, gitCommitAll, syncRepo)
import qualified Data.Text as T
import System.Exit (ExitCode (..))
import System.FilePath ((</>))
@@ -55,3 +55,28 @@ spec = do
code2 `shouldSatisfy` (/= ExitSuccess)
countAfter2 <- commitCount repo
countAfter2 `shouldBe` countBefore2
describe "startup sync" $ do
it "commits and pushes uncommitted changes on startup" $ do
logger <- testLogger
withTempGitRepoWithRemote $ \repo -> do
-- Leave a file uncommitted (simulating state before converge starts)
writeFile (repo </> "uncommitted.txt") "pending change"
countBefore <- commitCount repo
countBefore `shouldBe` 1
let cfg = defaultRepoConfig {rcPath = repo}
let opts = defaultOptions
{ optRepos = [cfg]
, optNotifyCmd = "true" -- avoid firing notify-send
}
syncRepo opts logger cfg
countAfter <- commitCount repo
countAfter `shouldBe` 2
-- Verify the commit was pushed to remote
(_, remoteLog, _) <- runGitIn repo ["log", "origin/main", "--oneline", "--name-only"]
remoteLog `shouldSatisfy` ("uncommitted.txt" `T.isInfixOf`)