From 63e034fd01d916c5eb597273f5c47edaacc25ea7 Mon Sep 17 00:00:00 2001 From: James Brechtel Date: Wed, 13 May 2026 22:10:30 -0400 Subject: [PATCH] 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 - 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 --- src/Converge.hs | 24 +++++++++++++++++++++++- test/GitCommitSpec.hs | 27 ++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/src/Converge.hs b/src/Converge.hs index b3de44f..4cc9e54 100644 --- a/src/Converge.hs +++ b/src/Converge.hs @@ -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 diff --git a/test/GitCommitSpec.hs b/test/GitCommitSpec.hs index a1f1584..1c64646 100644 --- a/test/GitCommitSpec.hs +++ b/test/GitCommitSpec.hs @@ -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`)