packages feed

fbrnch-1.8.2: src/Branches.hs

{-# LANGUAGE CPP #-}

module Branches (
  fedoraBranches,
  fedoraBranchesNoRawhide,
  isFedoraBranch,
  isEPELBranch,
  localBranches,
  listBranches,
  listAllBranches,
  listRemoteBranches,
  listRemoteAllBranches,
  mockRoot,
  Branch(..),
  showBranch,
  AnyBranch(..),
  anyBranch,
  isRelBranch,
  onlyRelBranch,
  BranchOpts(..),
  listOfBranches,
  listOfAnyBranches,
  gitCurrentBranch,
  gitCurrentBranch',
  checkOnBranch,
  systemBranch,
  getReleaseBranch,
  getReleaseBranchWarn,
  branchVersion,
  anyBranchToRelease,
  getRequestedBranches,
  BranchesReq(..),
  gitLines,
  renameMasterToRawhide
) where

import Data.Time.Calendar (diffDays)
import Data.Time.Clock (getCurrentTime, utctDay)
import Data.Either (partitionEithers)
import Distribution.Fedora.Branch (Branch(..), branchRelease,
                                   eitherBranch, getActiveBranched,
                                   getActiveBranches, getLatestFedoraBranch,
                                   readActiveBranch, eitherActiveBranch,
                                   readBranch, showBranch)
import Distribution.Fedora.Release (Release(releaseEOL))
import SimpleCmd.Git
import SimplePrompt (promptEnter, promptInitial)
import qualified System.Info (arch)

import Common
import Common.System
import Pagure

data AnyBranch = RelBranch Branch | OtherBranch String
  deriving Eq

anyBranch :: String -> AnyBranch
anyBranch = either OtherBranch RelBranch . eitherBranch

-- allRelBranches :: [AnyBranch] -> Bool
-- allRelBranches = all isRelBranch

isRelBranch :: AnyBranch -> Bool
isRelBranch (RelBranch _) = True
isRelBranch _ = False

instance Show AnyBranch where
  show (RelBranch br) = showBranch br
  show (OtherBranch obr) = obr

-- FIXME replace with next fedora-releases
readActiveBranches :: [Branch] -> [String] -> [Branch]
readActiveBranches active =
  -- newest branch first
  reverseSort . mapMaybe (readActiveBranch active)

readAllBranches :: [String] -> [Branch]
readAllBranches = reverseSort . mapMaybe readBranch

fedoraBranches :: IO [String] -> IO [Branch]
fedoraBranches mthd = do
  active <- getActiveBranches
  readActiveBranches active <$> mthd

fedoraBranchesNoRawhide :: IO [String] -> IO [Branch]
fedoraBranchesNoRawhide mthd = do
  active <- getActiveBranched
  readActiveBranches active <$> mthd

listBranches :: Bool -- ^ local
             -> IO [Branch]
listBranches = fedoraBranches . localBranches

listAllBranches :: Bool -- ^ local
                -> IO [Branch]
listAllBranches local =
  readAllBranches <$> localBranches local

isFedoraBranch :: Branch -> Bool
isFedoraBranch (Fedora _) = True
isFedoraBranch Rawhide = True
isFedoraBranch _ = False

isEPELBranch :: Branch -> Bool
isEPELBranch (EPELNext _) = True
isEPELBranch (EPEL _) = True
isEPELBranch (EPELMinor _ _) = True
isEPELBranch _ = False

localBranches :: Bool -> IO [String]
localBranches local =
  if local
  then do
    locals <- gitLines "branch" ["--list", "--format=%(refname:lstrip=-1)"]
    return $ locals \\ ["HEAD", "master"]
  else do
    origins <-
      filter ("origin/" `isPrefixOf`) <$>
      gitLines "branch" ["--remote", "--list", "--format=%(refname:lstrip=-2)"]
    return $ map (removePrefix "origin/") origins \\ ["HEAD", "master"]

-- FIXME use Package?
pagurePkgBranches :: String -> IO [String]
pagurePkgBranches pkg = do
  let project = "rpms/" ++ pkg
  res <- pagureListGitBranches srcfpo project
  return $ either (error' . include project) id res
  where
    include p e = e ++ ":" +-+ p

listRemoteBranches :: String -> IO [Branch]
listRemoteBranches pkg =
  fedoraBranches (pagurePkgBranches pkg)

listRemoteAllBranches :: String -> IO [Branch]
listRemoteAllBranches pkg = do
  readAllBranches <$> pagurePkgBranches pkg

mockRoot :: Branch -> Maybe String -> String
mockRoot br march =
  let arch = maybe System.Info.arch mapArch march
  in
    case br of
      Rawhide -> "fedora-rawhide-" ++ arch
      Fedora n -> "fedora-" ++ show n ++ "-" ++ arch
      EPEL n -> "centos-stream+epel-" ++ show n ++ "-" ++ arch
      EPELNext n -> "centos-stream+epel-next-" ++ show n ++ "-" ++ arch
      EPELMinor n _ ->  "rhel+epel-" ++ show n ++ "-" ++ arch
  where
    mapArch :: String -> String
    mapArch "i686" = "i386"
    mapArch a = a

------

data BranchOpts = AllBranches | AllFedora | AllEPEL | ExcludeBranches [Branch]
  deriving Eq

onlyRelBranch :: AnyBranch -> Branch
onlyRelBranch (RelBranch br) = br
onlyRelBranch (OtherBranch br) = error' $ "Non-release branch not allowed:" +-+ br

systemBranch :: IO Branch
systemBranch = do
  mplatform <- listToMaybe <$> grep "PLATFORM_ID=" "/etc/os-release"
  case mplatform of
    Just p -> do
      -- PLATFORM_ID="platform:f42"
      let platform = takeWhileEnd (/= ':') $ dropSuffix "\"" p
      if platform == "eln"
        then return Rawhide
        else
        case readBranch platform of
          Just br -> do
            branched <- getLatestFedoraBranch
            return $
              if br > branched
              then Rawhide
              else br
          Nothing -> error' $ "unknown platform" +-+ platform
    Nothing -> do
      mcpe <- listToMaybe <$> grep "CPE_NAME=" "/etc/os-release"
      case mcpe of
        Just c -> do
          -- CPE_NAME="cpe:/o:redhat:enterprise_linux:9::baseos"
          -- CPE_NAME="cpe:/o:fedoraproject:fedora:43"
          let cpe = takeEnd 2 $ splitOn ":" $ dropPrefix "CPE_NAME=\"cpe:/o:" $ dropSuffix "\"" $ fst $ breakOn "::" c
          case cpe of
            [os,ver] ->
              if ver == "eln"
              then return Rawhide
              else
                case readBranch $ osPrefix os ++ ver of
                  Just br -> do
                    branched <- getLatestFedoraBranch
                    return $
                      if br > branched
                      then Rawhide
                      else br
                  Nothing -> error' "could not map CPE_NAME to branch"
            _ -> error' $ "could not parse:" +-+ c
        Nothing -> error' "CPE_NAME not found in /etc/os-release"
    where
      osPrefix "fedora" = "f"
      osPrefix "centos" = "epel"
      osPrefix "enterprise_linux" = "epel"
      osPrefix s = s

-- FIXME should be NonEmpty Branch
listOfBranches' :: Bool -> BranchesReq -> IO [Branch]
listOfBranches' distgit (BranchOpt AllBranches) =
  if distgit
  then fedoraBranches (localBranches False)
  else getActiveBranches
listOfBranches' distgit (BranchOpt AllFedora) =
  filter isFedoraBranch <$>
  if distgit
  then fedoraBranches (localBranches False)
  else getActiveBranches
listOfBranches' distgit (BranchOpt AllEPEL) =
  filter isEPELBranch <$>
  if distgit
  then fedoraBranches (localBranches False)
  else getActiveBranches
listOfBranches' distgit (BranchOpt (ExcludeBranches brs)) = do
  branches <- if distgit
              then fedoraBranches (localBranches False)
              else getActiveBranches
  return $ branches \\ brs
listOfBranches' distgit (Branches brs) =
  if null brs
  then
    pure <$> if distgit
             then getReleaseBranch
             else systemBranch
  else return brs

listOfBranches :: Bool -> Bool -> BranchesReq -> IO [Branch]
listOfBranches distgit active breq = do
  brs <- listOfBranches' distgit breq
  activeBrs <- getActiveBranches
  forM_ brs $ \ br ->
    if active
    then do
      unless (br `elem` activeBrs) $
        error' $ showBranch br +-+ "is not an active branch"
      rel <- branchRelease br
      let meol = releaseEOL rel
      whenJust meol $ \eol -> do
        let date = read eol
        today <- utctDay <$> getCurrentTime
        let diff = diffDays date today
        when (diff <= 7) $
          warning $ "EOL for" +-+ showBranch br +-+ "is" +-+ show diff +-+ "days away!"
    else
      case br of
        Fedora _ -> do
          let latest = maximum (delete Rawhide activeBrs)
          when (br > latest) $
            error' $ showBranch br +-+ "is newer than latest branch"
        -- FIXME also check for too new EPEL
        _ -> return ()
  return brs

listOfAnyBranches :: Bool -> Bool -> BranchesReq -> IO [AnyBranch]
listOfAnyBranches distgit active breq =
  if breq == Branches [] && distgit
  then pure <$> gitCurrentBranch
  else fmap RelBranch <$> listOfBranches distgit active breq

getReleaseBranch :: IO Branch
getReleaseBranch =
  gitCurrentBranch >>= anyBranchToRelease

getReleaseBranchWarn :: IO Branch
getReleaseBranchWarn =
  gitCurrentBranchWarn >>= anyBranchToRelease

gitCurrentBranch :: IO AnyBranch
gitCurrentBranch = do
  br <- gitCurrentBranch'
  if br == OtherBranch "HEAD"
    then do
    dir <- getDirectoryName
    promptEnter $ dir ++ ":" +-+ show br +-+ "is not a branch, please fix"
    gitCurrentBranch
    else return br

gitCurrentBranch' :: IO AnyBranch
gitCurrentBranch' = do
  anyBranch <$> git "rev-parse" ["--abbrev-ref", "HEAD"]

gitCurrentBranchWarn :: IO AnyBranch
gitCurrentBranchWarn = do
  br <- gitCurrentBranch
  if br == OtherBranch "master"
    then do
    dir <- getDirectoryName
    promptEnter $ dir +-+ show br +-+ "is not a valid branch: press Enter to rename to rawhide"
    renameMasterToRawhide
    gitCurrentBranchWarn
    else return br

checkOnBranch :: IO ()
checkOnBranch = void gitCurrentBranch

anyBranchToRelease :: AnyBranch -> IO Branch
anyBranchToRelease (RelBranch rbr) = return rbr
anyBranchToRelease (OtherBranch _) = systemBranch

-- move to fedora-dists
branchVersion :: Branch -> String
branchVersion Rawhide = "rawhide"
branchVersion (Fedora n) = show n
branchVersion (EPEL n) = show n
branchVersion (EPELNext n) = show n
branchVersion (EPELMinor n _) = show n

getRequestedBranches :: [String] -> BranchesReq -> IO [Branch]
getRequestedBranches existing breq = do
  activenew <- filter (\b -> showBranch b `notElem` existing) <$> getActiveBranched
  case breq of
    Branches brs ->
      if null brs
      then branchingPrompt activenew
      else
        case [b | b <- brs, b `elem` activenew] of
          [] -> error' "no active branch requested"
          bs -> return bs
    BranchOpt request -> do
      let requested = case request of
                        AllBranches -> activenew
                        AllFedora -> filter isFedoraBranch activenew
                        AllEPEL -> filter isEPELBranch activenew
                        ExcludeBranches xbrs -> activenew \\ xbrs
      confirmBranches activenew requested
  where
    branchingPrompt :: [Branch] -> IO [Branch]
    branchingPrompt active = do
      inp <- promptInitial "Enter required branches" $
             unwords $ map showBranch $ take 2 active
      let abrs = map anyBranch $ words inp
        in if all isRelBranch abrs
           then return $ map onlyRelBranch abrs
           else branchingPrompt active

    confirmBranches :: [Branch] -> [Branch] -> IO [Branch]
    confirmBranches activenew requested = do
      inp <- promptInitial "Confirm branches to request" $ unwords (map showBranch requested)
      let (errs,oks) = partitionEithers $
                       map (eitherActiveBranch activenew) $ words inp
      if null errs
        then return oks
        else do
        putStrLn $ "unknown branches:" +-+ unwords errs
        confirmBranches activenew requested

data BranchesReq =
  BranchOpt BranchOpts | Branches [Branch]
  deriving Eq

gitLines :: String -> [String] -> IO [String]
gitLines c args = lines <$> git c args

renameMasterToRawhide :: IO ()
renameMasterToRawhide = do
  locals <- gitLines "branch" ["--format=%(refname:short)"]
  -- FIXME dangling warning in current output:
    -- From ssh://pkgs.fedoraproject.org/rpms/hedgewars
    --  - [deleted]         (none)     -> origin/master
    --    (refs/remotes/origin/HEAD has become dangling)
    -- Branch 'rawhide' set up to track remote branch 'rawhide' from 'origin'.
  -- compare commands with github rename
  unless ("rawhide" `elem` locals) $ do
    git_ "fetch" ["--prune"]
    git_ "branch" ["--move", "master", "rawhide"]
    git_ "remote" ["set-head", "origin", "rawhide"]
    git_ "branch" ["--set-upstream-to", "origin/rawhide", "rawhide"]
    git_ "pull" []