module Git ( Branch(..) , checkout , deleteBranch , fetch , fullBranchName , isCommonBranch , isRemoteBranch , listBranches , rebaseInteractive , toBranches ) where import Data.Char ( isSpace ) import Data.List import System.Exit import System.Process data Branch = BranchLocal String | BranchCurrent String | BranchRemote String String deriving Eq instance (Show Branch) where show (BranchLocal n ) = n show (BranchCurrent n ) = n <> "*" show (BranchRemote o n) = o <> "/" <> n fetch :: IO String fetch = readGit ["fetch", "--all", "--prune"] listBranches :: IO [Branch] listBranches = toBranches <$> readGit [ "branch" , "--list" , "--all" , "--sort=-committerdate" , "--no-column" , "--no-color" ] toBranches :: String -> [Branch] toBranches input = toBranch <$> filter validBranch (lines input) where validBranch b = not $ isHead b || isDetachedHead b || isNoBranch b toBranch :: String -> Branch toBranch line = mkBranch $ words $ dropWhile isSpace line where mkBranch ("*" : name : _) = BranchCurrent name mkBranch (name : _) = case stripPrefix "remotes/" name of Just rest -> parseRemoteBranch rest Nothing -> BranchLocal name mkBranch [] = error "empty branch name" parseRemoteBranch str = BranchRemote remote name where (remote, _ : name) = span ('/' /=) str checkout :: Branch -> IO ExitCode checkout branch = spawnGit ["checkout", branchName branch] rebaseInteractive :: Branch -> IO ExitCode rebaseInteractive branch = do putStrLn $ "Rebase onto " <> fullBranchName branch spawnGit ["rebase", "--interactive", "--autostash", fullBranchName branch] deleteBranch :: Branch -> IO ExitCode deleteBranch (BranchCurrent _ ) = error "Cannot delete current branch" deleteBranch (BranchLocal n ) = spawnGit ["branch", "-D", n] deleteBranch (BranchRemote o n) = spawnGit ["push", o, "--delete", n] spawnGit :: [String] -> IO ExitCode spawnGit args = waitForProcess =<< spawnProcess "git" args readGit :: [String] -> IO String readGit args = readProcess "git" args [] isCommonBranch :: Branch -> Bool isCommonBranch b = branchName b `elem` [ "master" , "main" , "dev" , "devel" , "develop" , "development" , "staging" , "trunk" ] isRemoteBranch :: Branch -> Bool isRemoteBranch (BranchRemote _ _) = True isRemoteBranch _ = False --- Helper branchName :: Branch -> String branchName (BranchCurrent n ) = n branchName (BranchLocal n ) = n branchName (BranchRemote _ n) = n fullBranchName :: Branch -> String fullBranchName (BranchCurrent n ) = n fullBranchName (BranchLocal n ) = n fullBranchName (BranchRemote r n) = r <> "/" <> n isHead :: String -> Bool isHead = isInfixOf "HEAD" isDetachedHead :: String -> Bool isDetachedHead = isInfixOf "HEAD detached" -- While rebasing git will show "no branch" -- e.g. "* (no branch, rebasing branch-name)" isNoBranch :: String -> Bool isNoBranch = isInfixOf "(no branch,"