pkgtreediff 0.4.1 → 0.5.0
raw patch · 6 files changed
+187/−227 lines, 6 filesdep +http-commondep +rpm-nvr
Dependencies added: http-common, rpm-nvr
Files
- CHANGELOG.md +7/−0
- README.md +49/−38
- TODO +3/−0
- pkgtreediff.cabal +21/−5
- src/Distribution/RPM/PackageTreeDiff.hs +41/−140
- src/Main.hs +66/−44
CHANGELOG.md view
@@ -1,5 +1,12 @@ # Changelog +## 0.5.0 (2021-11-03)+- use rpm-nvr library for NVRA type (API changes) and rpmVerCompare+ to order ver-rel's+- add --downgraded filter+- add --subdir+- remove --ignore-arch+ ## 0.4.1 (2021-04-19) - expose a library in Distribution.RPM.PackageTreeDiff (@juhp) - full correct rpm version comparison (ie handling ^ and ~) (@juhp)
README.md view
@@ -19,52 +19,62 @@ ### Containers -Compare the content of two rpm based containers (new added packages in fedora:34)+Compare the content of two rpm based containers versions,+filtering for new added packages: -```bash session-$ pkgtreediff --new "podman run --rm fedora:33 rpm -qa" "podman run --rm fedora:34 rpm -qa"-dejavu-sans-fonts.noarch 2.37-16.fc34-fonts-filesystem.noarch 2.0.5-5.fc34-langpacks-core-en_GB.noarch 3.0-12.fc34-langpacks-core-font-en.noarch 3.0-12.fc34-langpacks-en_GB.noarch 3.0-12.fc34+```shellsession+$ pkgtreediff --new "podman run --rm fedora:34 rpm -qa" "podman run --rm fedora:35 rpm -qa"+libfsverity-1.4-4.fc35.x86_64+mpdecimal-2.5.1-2.fc35.x86_64 ``` ### Package trees -Package source changes between Fedora 30 and 31 Server at GA (ignoring release bumps):+Package source changes between Fedora 33 and 34 Server at GA (ignoring release bumps): -```bash session-$ pkgtreediff --ignore-release https://dl.fedoraproject.org/pub/fedora/linux/releases/{32,33}/Server/source/tree/Packages/-ModemManager.src: 1.12.8-1.fc32 -> 1.14.2-1.fc33-NetworkManager.src: 1.22.10-1.fc32 -> 1.26.2-2.fc33-PackageKit.src: 1.1.13-2.fc32 -> 1.2.1-1.fc33-abrt.src: 2.14.0-2.fc32 -> 2.14.4-6.fc33-adwaita-icon-theme.src: 3.36.0-1.fc32 -> 3.38.0-1.fc33-alsa-lib.src: 1.2.2-2.fc32 -> 1.2.3.2-5.fc33-alsa-sof-firmware.src: 1.4.2-4.fc32 -> 1.5-2.fc33-anaconda.src: 32.24.7-1.fc32 -> 33.25.4-1.fc33+```shellsession+$ pkgtreediff --ignore-release https://dl.fedoraproject.org/pub/fedora/linux/releases/{33,34}/Server/source/tree/Packages/+ModemManager.src: 1.14.2-1.fc33 -> 1.14.10-2.fc34+NetworkManager.src: 1.26.2-2.fc33 -> 1.30.2-1.fc34+PackageKit.src: 1.2.1-1.fc33 -> 1.2.3-1.fc34+abrt.src: 2.14.4-6.fc33 -> 2.14.5-2.fc34 :-gobject-introspection.src: 1.64.1-1.fc32 -> 1.66.1-1.fc33-+ google-carlito-fonts.src 1.103-0.15.20130920.fc33-- google-crosextra-caladea-fonts.src 1.002-0.14.20130214.fc32-- google-crosextra-carlito-fonts.src 1.103-0.12.20130920.fc32-gpgme.src: 1.13.1-6.fc32 -> 1.14.0-2.fc33+bash.src: 5.0.17-2.fc33 -> 5.1.0-2.fc34+bash-completion.src: 2.8-9.fc33 -> 2.11-2.fc34+- bcache-tools-1.0.8-19.fc33.src+bcm283x-firmware.src: 20201008-2.63b1922.fc33 -> 20210407-1.8c7c524.fc34+bind.src: 9.11.23-1.fc33 -> 9.16.11-5.fc34+binutils.src: 2.35-10.fc33 -> 2.35.1-41.fc34 :+cockpit.src: 229-1.fc33 -> 241-1.fc34+colord.src: 1.4.4-5.fc33 -> 1.4.5-2.fc34++ compsize-1.5-1.fc34.src+conmon.src: 2.0.21-3.fc33 -> 2.0.27-1.fc34+container-selinux.src: 2.145.0-1.fc33 -> 2.158.0-1.gite78ac4f.fc34+containernetworking-plugins.src: 0.8.7-1.fc33 -> 0.9.1-4.fc34++ containers-common-1-13.fc34.src+- copy-jdk-configs-3.7-7.fc33.src+- createrepo_c-0.15.11-4.fc33.src++ criu-3.15-3.fc34.src+crun.src: 0.15-5.fc33 -> 0.18-5.fc34 :-xkeyboard-config.src: 2.29-1.fc32 -> 2.30-3.fc33-xxhash.src: 0.7.3-1.fc32 -> 0.8.0-1.fc33-yelp.src: 3.36.0-1.fc32 -> 3.38.1-1.fc33-yelp-xsl.src: 3.36.0-1.fc32 -> 3.38.1-1.fc33-- zram.src 0.4-1.fc32-zstd.src: 1.4.4-2.fc32 -> 1.4.5-5.fc33+:+vim.src: 8.2.1770-1.fc33 -> 8.2.2637-1.fc34++ vulkan-loader-1.2.162.0-2.fc34.src+wayland.src: 1.18.0-2.fc33 -> 1.19.0-1.fc34+webkit2gtk3.src: 2.30.1-1.fc33 -> 2.32.0-2.fc34+wget.src: 1.20.3-8.fc33 -> 1.21.1-2.fc34+:+zchunk.src: 1.1.5-3.fc33 -> 1.1.9-2.fc34+zstd.src: 1.4.5-5.fc33 -> 1.4.9-1.fc34 Summary-Updated: 340-Added: 27-Deleted: 32+Updated: 330+Downgraded: 0+Added: 63+Deleted: 98 Arch changed: 0-Total packages: 937 -> 932+Total packages: 932 -> 897 ``` ### Hosts@@ -97,9 +107,10 @@ ([more details](https://copr.fedorainfracloud.org/coprs/petersen/pkgtreediff/monitor/detailed)). ## RPM version ordering+pkgtreediff use the [rpm-nvr](https://hackage.haskell.org/package/rpm-nvr)+library implementation of the `rpmvercmp()` algorithm,+though it has not been verified to behave identically.+ RPM version ordering is somewhat involved <https://blog.jasonantman.com/2014/07/how-yum-and-rpm-compare-versions/>.-For example "1.2.1^rc1" < "1.2.1" (similarly for tilde).--pkgtreediff tries to implement the rpmvercmp() algorithm,-but it has not been verified to behave identically.+For example "1.2.1~rc1" < "1.2.1" (greater for ^).
TODO view
@@ -1,3 +1,5 @@+- keep subdir prefices+ - compare buildsys list-tagged packages... - compare with installed packages (--installed?) - deb trees?@@ -7,3 +9,4 @@ - support epochs in file package list - diff isos - "--query-format"+- testsuite!!
pkgtreediff.cabal view
@@ -1,7 +1,7 @@ cabal-version: 1.18 name: pkgtreediff-version: 0.4.1-synopsis: Package tree diff tool+version: 0.5.0+synopsis: RPM package tree diff tool description: Tool for comparing RPM packages and versions in OS dist trees or instances. homepage: https://github.com/juhp/pkgtreediff bug-reports: https://github.com/juhp/pkgtreediff/issues@@ -15,7 +15,7 @@ extra-doc-files: README.md , CHANGELOG.md , TODO-tested-with: GHC == 8.8.4, GHC == 8.10.4,+tested-with: GHC == 8.8.4, GHC == 8.10.7, GHC == 9.0.1 GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5 source-repository head@@ -37,11 +37,14 @@ , http-client-tls , http-directory >= 0.1.4 && (< 0.1.6 || >= 0.1.8) , pkgtreediff+ , rpm-nvr , simple-cmd >= 0.1.4 , simple-cmd-args , text if impl(ghc<8.0) build-depends: semigroups+ if impl(ghc<8.4)+ build-depends: http-common < 0.8.3.4 ghc-options: -Wall -threaded default-language: Haskell2010@@ -52,9 +55,22 @@ hs-source-dirs: src build-depends: base < 5- , text+ , rpm-nvr >= 0.1.1 if impl(ghc<8.0) build-depends: semigroups - ghc-options: -Wall default-language: Haskell2010+ ghc-options: -Wall+ if impl(ghc >= 8.0)+ ghc-options: -Wcompat+ -Widentities+ -Wincomplete-uni-patterns+ -Wincomplete-record-updates+ -Wredundant-constraints+ if impl(ghc >= 8.2)+ ghc-options: -fhide-source-paths+ if impl(ghc >= 8.4)+ ghc-options: -Wmissing-export-lists+ -Wpartial-fields+ if impl(ghc >= 8.10)+ ghc-options: -Wunused-packages
src/Distribution/RPM/PackageTreeDiff.hs view
@@ -1,18 +1,11 @@ {-# LANGUAGE CPP #-}-{-# LANGUAGE OverloadedStrings #-} -- | A library for pkgtreediff for comparing trees of rpm packages module Distribution.RPM.PackageTreeDiff- (RpmPackage(..),- readRpmPkg,- showRpmPkg,- rpmPkgIdent,- appendArch,- dropRpmArch,- rpmPkgVerRel,- RpmPackageDiff(..),+ (NVRA(..),+ readNVRA,+ RPMPkgDiff(..), diffPkgs,- diffPkg, Ignore(..), Mode(..), ) where@@ -20,16 +13,19 @@ #if !MIN_VERSION_base(4,8,0) import Control.Applicative ((<$>)) #endif-import Data.Char-import Data.Maybe-#if !MIN_VERSION_base(4,11,0)-import Data.Semigroup ((<>))-#endif-import Data.Text (Text)-import qualified Data.Text as T+import Data.RPM.VerRel+import Data.RPM.NVRA+import Data.RPM.VerCmp (rpmVerCompare) -- | Mode describes the kind of summary generated by compareDirs-data Mode = AutoSummary | NoSummary | ShowSummary | Added | Deleted | Updated | RST+data Mode = AutoSummary+ | NoSummary+ | ShowSummary+ | Added+ | Deleted+ | Updated+ | Downgraded+ | RST deriving Eq -- | Ignore describes how comparison is done@@ -38,134 +34,39 @@ | IgnoreVersion -- ^ ignore differences in version deriving Eq -type Name = Text-type Arch = Text--data VersionRelease = VerRel Text Text- deriving Eq--instance Ord VersionRelease where- compare (VerRel v1 r1) (VerRel v2 r2) =- case rpmVerCompare v1 v2 of- EQ -> rpmVerCompare r1 r2- o -> o--data VerChunk = TildeChunk Int | CaretChunk Int | TxtChunk Text | IntChunk Int- deriving (Eq,Ord,Show)--data RpmCharCategory = RpmTilde | RpmCaret | RpmOther | RpmLatin | RpmDigit- deriving Eq--verChunk :: Text -> VerChunk-verChunk t | T.all isDigit t = (IntChunk . read . T.unpack) t-verChunk t | T.all (== '~') t = (TildeChunk . T.length) t-verChunk t | T.all (== '^') t = (CaretChunk . T.length) t-verChunk t = TxtChunk t--rpmVerCompare :: Text -> Text -> Ordering-rpmVerCompare v1 v2 | v1 == v2 = EQ-rpmVerCompare v1 v2 =- compareChunks (verList v1) (verList v2)- where- compareChunks [] [] = EQ- compareChunks (c:cs) (c':cs') | c == c' = compareChunks cs cs'- compareChunks ((TildeChunk n):_) ((TildeChunk n'):_) = compare n' n- compareChunks ((CaretChunk n):_) ((CaretChunk n'):_) = compare n' n- compareChunks ((TildeChunk _):_) _ = LT- compareChunks _ ((TildeChunk _):_) = GT- compareChunks ((CaretChunk _):_) _ = LT- compareChunks _ ((CaretChunk _):_) = GT- compareChunks _ [] = GT- compareChunks [] _ = LT- compareChunks (c:_) (c':_) = compare c c'--verList :: Text -> [VerChunk]-verList = map verChunk . filter (T.all (/= '.')) . T.groupBy (\ c1 c2 -> rpmCategory c1 == rpmCategory c2)--latinChars :: [Char]-latinChars = ['A'..'Z'] ++ ['a'..'z']--rpmCategory :: Char -> RpmCharCategory-rpmCategory c | isDigit c = RpmDigit-rpmCategory c | c `elem` latinChars = RpmLatin-rpmCategory '~' = RpmTilde-rpmCategory '^' = RpmCaret-rpmCategory _ = RpmOther---- eqVR True ignore release-eqVR :: Ignore -> VersionRelease -> VersionRelease -> Bool-eqVR IgnoreNone vr vr' = vr == vr'-eqVR IgnoreRelease (VerRel v _) (VerRel v' _) = v == v'-eqVR IgnoreVersion _ _ = True---- | Text for the version-release of an RpmPackage-rpmPkgVerRel :: RpmPackage -> Text-rpmPkgVerRel = txtVerRel . rpmVerRel- where- txtVerRel (VerRel v r) = v <> T.singleton '-' <> r---- | RPM package with name, version-release, and maybe architecture-data RpmPackage = RpmPkg {rpmName :: Name, rpmVerRel :: VersionRelease, rpmMArch :: Maybe Arch}- deriving (Eq, Ord)---- | Text identifier for an RPM package identified by name and arch-rpmPkgIdent :: RpmPackage -> Text-rpmPkgIdent p = rpmName p <> appendArch p---- | Helper to add an arch suffix-appendArch :: RpmPackage -> Text-appendArch p = maybe "" ("." <>) (rpmMArch p)---- | drop arch from RpmPackage-dropRpmArch :: RpmPackage -> RpmPackage-dropRpmArch (RpmPkg n vr _) = RpmPkg n vr Nothing---- | Render an RpmPackage-showRpmPkg :: RpmPackage -> Text-showRpmPkg p = rpmPkgIdent p <> T.pack " " <> rpmPkgVerRel p---- | Parse an RpmPackage-readRpmPkg :: Text -> RpmPackage-readRpmPkg t =- if compnts < 3 then error $ "Malformed rpm package name: " ++ T.unpack t- else RpmPkg name (VerRel ver rel) (Just arch)- where- compnts = length pieces- (nvr',arch) = T.breakOnEnd "." $ fromMaybe t $ T.stripSuffix ".rpm" t- pieces = reverse $ T.splitOn "-" $ T.dropEnd 1 nvr'- (rel:ver:emaN) = pieces- name = T.intercalate "-" $ reverse emaN---- | RpmPackageDiff type encodes how a particular rpm package differs between trees-data RpmPackageDiff = PkgUpdate RpmPackage RpmPackage- | PkgAdd RpmPackage- | PkgDel RpmPackage- | PkgArch RpmPackage RpmPackage+-- | RPMPkgDiff type encodes how a particular rpm package differs between trees+data RPMPkgDiff = PkgUpdate NVRA NVRA+ | PkgDowngrade NVRA NVRA+ | PkgAdd NVRA+ | PkgDel NVRA+ | PkgArch NVRA NVRA deriving Eq --- | Compare two lists of packages NVRs-diffPkgs :: Ignore -> [RpmPackage] -> [RpmPackage] -> [RpmPackageDiff]+-- | Compare two lists of packages NVRAs+diffPkgs :: Ignore -> [NVRA] -> [NVRA] -> [RPMPkgDiff] diffPkgs _ [] [] = [] diffPkgs ignore (p:ps) [] = PkgDel p : diffPkgs ignore ps [] diffPkgs ignore [] (p:ps) = PkgAdd p : diffPkgs ignore [] ps diffPkgs ignore (p1:ps1) (p2:ps2) =- case compareNames p1 p2 of+ case compare (rpmName p1) (rpmName p2) of LT -> PkgDel p1 : diffPkgs ignore ps1 (p2:ps2)- EQ -> let diff = diffPkg ignore p1 p2- diffs = diffPkgs ignore ps1 ps2- in if isJust diff then fromJust diff : diffs else diffs+ EQ -> case diffPkg of+ Just diff -> (diff :)+ Nothing -> id+ $ diffPkgs ignore ps1 ps2 GT -> PkgAdd p2 : diffPkgs ignore (p1:ps1) ps2---- | Compare two rpms of a package-diffPkg :: Ignore -> RpmPackage-> RpmPackage-> Maybe RpmPackageDiff-diffPkg ignore p1 p2 | rpmPkgIdent p1 == rpmPkgIdent p2 =- if eqVR ignore (rpmVerRel p1) (rpmVerRel p2)- then Nothing- else Just $ PkgUpdate p1 p2---diffPkg ignore p1 p2 | rpmName p1 == rpmName p2 =--- diffPkg ignore True (RpmPkg n1 v1) (RpmPkg n2 v2)-diffPkg _ p1 p2 | rpmName p1 == rpmName p2 && rpmPkgIdent p1 /= rpmPkgIdent p2 = Just $ PkgArch p1 p2-diffPkg _ _ _ = Nothing+ where+ diffPkg :: Maybe RPMPkgDiff+ diffPkg =+ if rpmArch p1 == rpmArch p2+ then case cmpVR ignore (rpmVerRel p1) (rpmVerRel p2) of+ LT -> Just $ PkgUpdate p1 p2+ EQ -> Nothing+ GT -> Just $ PkgDowngrade p1 p2+ else Just $ PkgArch p1 p2 -compareNames :: RpmPackage -> RpmPackage -> Ordering-compareNames p1 p2 = compare (rpmName p1) (rpmName p2)+-- cmpVR True ignore release+cmpVR :: Ignore -> VerRel -> VerRel -> Ordering+cmpVR IgnoreNone vr vr' = compare vr vr'+cmpVR IgnoreRelease (VerRel v _) (VerRel v' _) = rpmVerCompare v v'+cmpVR IgnoreVersion _ _ = EQ
src/Main.hs view
@@ -14,12 +14,11 @@ import Control.Monad.Extra (concatMapM) import Data.List import Data.Maybe+import Data.RPM.NVRA #if !MIN_VERSION_base(4,11,0) import Data.Semigroup ((<>)) #endif-import Data.Text (Text) import qualified Data.Text as T-import qualified Data.Text.IO as T import Network.HTTP.Client (managerResponseTimeout, newManager, responseTimeoutMicro) import Network.HTTP.Client.TLS (tlsManagerSettings)@@ -48,7 +47,7 @@ main = simpleCmdArgs (Just version) "Package tree comparison tool" "pkgtreediff compares the packages in two OS trees or instances" $- compareDirs <$> recursiveOpt <*> ignoreVR <*> ignoreArch <*> modeOpt <*> optional patternOpt <*> timeoutOpt <*> sourceArg "1" <*> sourceArg "2"+ compareDirs <$> recursiveOpt <*> optional subdirOpt <*> ignoreVR <*> modeOpt <*> optional patternOpt <*> timeoutOpt <*> sourceArg "1" <*> sourceArg "2" where sourceArg :: String -> Parser String sourceArg pos = strArg ("URL|DIR|FILE|KOJITAG|CMD" <> pos)@@ -57,13 +56,14 @@ modeOpt = flagWith' Added 'N' "new" "Show only added packages" <|> flagWith' Deleted 'D' "deleted" "Show only removed packages" <|>- flagWith' Updated 'U' "updated" "Show only updated packages" <|>+ flagWith' Updated 'U' "updated" "Show only upgraded packages" <|>+ flagWith' Downgraded 'u' "downgraded" "Show only downgraded packages" <|> flagWith' ShowSummary 's' "show-summary" ("Show summary of changes (default when >" <> show summaryThreshold <> " changes)") <|> flagWith' RST 'R' "rst" "Print summary in ReSTructured Text format" <|> flagWith AutoSummary NoSummary 'S' "no-summary" "Do not display summary" - ignoreArch :: Parser Bool- ignoreArch = switchWith 'A' "ignore-arch" "Ignore arch differences"+ -- ignoreArch :: Parser Bool+ -- ignoreArch = switchWith 'A' "ignore-arch" "Ignore arch differences" ignoreVR :: Parser Ignore ignoreVR =@@ -73,6 +73,9 @@ recursiveOpt :: Parser Bool recursiveOpt = switchWith 'r' "recursive" "Recursive down into subdirectories" + subdirOpt :: Parser String+ subdirOpt = strOptionWith 'd' "subdir" "SUBDIR" "Select specific subdir (eg x86_64 or source)"+ patternOpt :: Parser String patternOpt = strOptionWith 'p' "pattern" "PKGPATTERN" "Limit packages to glob matches" @@ -117,12 +120,12 @@ isHttp loc = "http:" `isPrefixOf` loc || "https:" `isPrefixOf` loc -- | Frontend for the pkgtreediff tool-compareDirs :: Bool -> Ignore -> Bool -> Mode -> Maybe String -> Int -> String -> String -> IO ()-compareDirs recursive ignore igArch mode mpattern timeout tree1 tree2 = do+compareDirs :: Bool -> Maybe String -> Ignore -> Mode -> Maybe String -> Int -> String -> String -> IO ()+compareDirs recursive msubdir ignore mode mpattern timeout tree1 tree2 = do (ps1,ps2) <- getTrees tree1 tree2 let diff = diffPkgs ignore ps1 ps2 if mode /= RST- then mapM_ T.putStrLn . mapMaybe (showPkgDiff mode) $ diff+ then mapM_ putStrLn . mapMaybe (showPkgDiff mode) $ diff else printRST diff when (mode /= NoSummary && isDefault mode) $ when (mode == ShowSummary || length diff > summaryThreshold) $ do@@ -130,6 +133,7 @@ (if mode /= RST then putStrLn else printRSTHeader) "Summary" let diffsum = summary diff putStrLn $ "Updated: " <> show (updateSum diffsum)+ putStrLn $ "Downgraded: " <> show (downgradeSum diffsum) putStrLn $ "Added: " <> show (newSum diffsum) putStrLn $ "Deleted: " <> show (delSum diffsum) putStrLn $ "Arch changed: " <> show (archSum diffsum)@@ -140,16 +144,19 @@ putStrLn name putStrLn $ replicate (length name) '~' putStrLn ""- printRSTElem = T.putStrLn . mappend "- "- printRSTDiffElem = printRSTElem . T.drop 2+ printRSTElem = putStrLn . mappend "- "+ printRSTDiffElem = printRSTElem . drop 2 printRST diff = do printRSTHeader "Updated" mapM_ printRSTElem $ mapMaybe (showPkgDiff mode) [x | x@(PkgUpdate _ _) <- diff]+ printRSTHeader "Downgraded"+ mapM_ printRSTElem $ mapMaybe (showPkgDiff mode) [x | x@(PkgDowngrade _ _) <- diff] printRSTHeader "Added" mapM_ printRSTDiffElem $ mapMaybe (showPkgDiff mode) [x | x@(PkgAdd _) <- diff] printRSTHeader "Removed" mapM_ printRSTDiffElem $ mapMaybe (showPkgDiff mode) [x | x@(PkgDel _) <- diff]- getTrees :: String -> String -> IO ([RpmPackage],[RpmPackage])++ getTrees :: String -> String -> IO ([NVRA],[NVRA]) getTrees t1 t2 = do when (t1 == t2) $ warning "Comparing the same tree!" src1 <- sourceType t1@@ -168,6 +175,7 @@ return (ps1,ps2) else concurrently act1 act2 + readPackages :: SourceType -> Maybe Manager -> String -> IO [NVRA] readPackages source mmgr loc = do fs <- case source of URL -> httpPackages True (fromJust mmgr) loc@@ -175,73 +183,87 @@ Dir -> dirPackages True loc File -> filePackages loc Cmd -> cmdPackages $ words loc- let ps = map ((if igArch then dropRpmArch else id) . readRpmPkg) $ filter (maybe (const True) (match . compile) mpattern . T.unpack) fs+ let ps = map readNVRA $ filter (maybe (const True) (match . compile) mpattern) fs return $ sort (nub ps) + httpPackages :: Bool -> Manager -> String -> IO [String] httpPackages recurse mgr url = do exists <- httpExists mgr url fs <- if exists- then filter (\ f -> "/" `T.isSuffixOf` f || ".rpm" `T.isSuffixOf` f) <$> httpDirectory mgr url+ then map T.unpack . filter (\ f -> "/" `T.isSuffixOf` f || ".rpm" `T.isSuffixOf` f) <$> httpDirectory mgr url else error' $ "Could not get " <> url- if (recurse || recursive) && all isDir fs then concatMapM (httpPackages False mgr) (map ((url </>) . T.unpack) fs) else return $ filter (not . isDir) fs+ if (recurse || recursive) && all isDir fs then concatMapM (httpPackages False mgr) (map (url </>) (filterSubdir fs)) else return $ filter (not . isDir) fs + filterSubdir :: [String] -> [String]+ filterSubdir fs =+ case msubdir of+ Just subdir | (subdir <> "/") `elem` fs -> [subdir]+ _ -> fs+ dirPackages recurse dir = do -- can replace with listDirectory after dropping ghc7 -- should really filter out ".rpm" though not common fs <- sort . filter (".rpm" `isSuffixOf`) <$> getDirectoryContents dir alldirs <- mapM doesDirectoryExist fs- if (recurse || recursive) && and alldirs then concatMapM (dirPackages False) (map (dir </>) fs) else return $ filter (not . isDir) $ map T.pack fs+ if (recurse || recursive) && and alldirs then concatMapM (dirPackages False) (map (dir </>) (filterSubdir fs)) else return $ filter (not . isDir) fs - isDir = ("/" `T.isSuffixOf`)+ isDir = ("/" `isSuffixOf`) filePackages file =- filter (not . T.isPrefixOf (T.pack "gpg-pubkey-")) . T.words <$> T.readFile file+ filter (not . isPrefixOf "gpg-pubkey-") . words <$> readFile file cmdPackages [] = error' "No command prefix given" cmdPackages (c:args) = -- use words since container seems to append '\r'- filter (not . T.isPrefixOf (T.pack "gpg-pubkey-")) . T.words . T.pack <$> cmd c args+ filter (not . isPrefixOf "gpg-pubkey-") . words <$> cmd c args - kojiPackages (tag, kojiUrl) = map (T.pack . Koji.kbNvr) <$> Koji.kojiListTaggedBuilds kojiUrl True tag+ kojiPackages (tag, kojiUrl) = map Koji.kbNvr <$> Koji.kojiListTaggedBuilds kojiUrl True tag isDefault :: Mode -> Bool isDefault m = m `elem` [AutoSummary, NoSummary, ShowSummary, RST] -showPkgDiff :: Mode -> RpmPackageDiff -> Maybe Text-showPkgDiff Added (PkgAdd p) = Just $ showRpmPkg p-showPkgDiff Deleted (PkgDel p) = Just $ showRpmPkg p-showPkgDiff Updated (PkgUpdate p1 p2) = Just $ showPkgUpdate p1 p2-showPkgDiff Updated (PkgArch p1 p2) = Just $ showArchChange p1 p2-showPkgDiff mode (PkgAdd p) | isDefault mode = Just $ "+ " <> showRpmPkg p-showPkgDiff mode (PkgDel p) | isDefault mode = Just $ "- " <> showRpmPkg p-showPkgDiff mode (PkgUpdate p1 p2) | isDefault mode = Just $ showPkgUpdate p1 p2-showPkgDiff mode (PkgArch p1 p2) | isDefault mode = Just $ "! " <> showArchChange p1 p2-showPkgDiff _ _ = Nothing--showPkgUpdate :: RpmPackage -> RpmPackage -> Text-showPkgUpdate p p' =- rpmPkgIdent p <> ": " <> rpmPkgVerRel p <> " -> " <> rpmPkgVerRel p'--showArchChange :: RpmPackage -> RpmPackage -> Text-showArchChange p p' =- rpmName p <> ": " <> rpmDetails p <> " -> " <> rpmDetails p'+showPkgDiff :: Mode -> RPMPkgDiff -> Maybe String+showPkgDiff mode diff =+ case (mode,diff) of+ (Added, PkgAdd p) -> Just $ showNVRA p+ (Deleted, PkgDel p) -> Just $ showNVRA p+ (Updated, PkgUpdate p1 p2) -> Just $ showPkgChange p1 p2+ (Updated, PkgArch p1 p2) -> Just $ showArchChange p1 p2+ (Downgraded, PkgDowngrade p1 p2) -> Just $ showPkgChange p1 p2+ _ -> if isDefault mode+ then case diff of+ PkgAdd p -> Just $ "+ " <> showNVRA p+ PkgDel p -> Just $ "- " <> showNVRA p+ PkgUpdate p1 p2 -> Just $ showPkgChange p1 p2+ PkgDowngrade p1 p2 -> Just $ "~ " <> showPkgChange p1 p2+ PkgArch p1 p2 -> Just $ "! " <> showArchChange p1 p2+ else Nothing where- rpmDetails :: RpmPackage -> Text- rpmDetails pkg = rpmPkgVerRel pkg <> appendArch pkg+ showPkgChange :: NVRA -> NVRA -> String+ showPkgChange p p' =+ showPkgIdent p <> ": " <> showPkgVerRel p <> " -> " <> showPkgVerRel p' -data DiffSum = DS {updateSum, newSum, delSum, archSum :: Int}+ showArchChange :: NVRA -> NVRA -> String+ showArchChange p p' =+ rpmName p <> ": " <> rpmDetails p <> " -> " <> rpmDetails p'+ where+ rpmDetails :: NVRA -> String+ rpmDetails pkg = showPkgVerRel pkg <> "." <> rpmArch pkg +data DiffSum = DS {updateSum, downgradeSum, newSum, delSum, archSum :: Int}+ emptyDS :: DiffSum-emptyDS = DS 0 0 0 0+emptyDS = DS 0 0 0 0 0 -summary :: [RpmPackageDiff] -> DiffSum+summary :: [RPMPkgDiff] -> DiffSum summary = foldl' countDiff emptyDS where- countDiff :: DiffSum -> RpmPackageDiff -> DiffSum+ countDiff :: DiffSum -> RPMPkgDiff -> DiffSum countDiff ds pd = case pd of PkgUpdate {} -> ds {updateSum = updateSum ds + 1}+ PkgDowngrade {} -> ds {downgradeSum = downgradeSum ds + 1} PkgAdd _ -> ds {newSum = newSum ds + 1} PkgDel _ -> ds {delSum = delSum ds + 1} PkgArch {} -> ds {archSum = archSum ds + 1}