packages feed

pkgtreediff 0.5.0 → 0.6.0

raw patch · 7 files changed

+212/−125 lines, 7 filesdep +bytestringdep +http-typesdep ~basedep ~http-directorydep ~simple-cmdPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, http-types

Dependency ranges changed: base, http-directory, simple-cmd

API changes (from Hackage documentation)

- Distribution.RPM.PackageTreeDiff: Added :: Mode
- Distribution.RPM.PackageTreeDiff: AutoSummary :: Mode
- Distribution.RPM.PackageTreeDiff: Deleted :: Mode
- Distribution.RPM.PackageTreeDiff: Downgraded :: Mode
- Distribution.RPM.PackageTreeDiff: NoSummary :: Mode
- Distribution.RPM.PackageTreeDiff: RST :: Mode
- Distribution.RPM.PackageTreeDiff: ShowSummary :: Mode
- Distribution.RPM.PackageTreeDiff: Updated :: Mode
- Distribution.RPM.PackageTreeDiff: data Mode
- Distribution.RPM.PackageTreeDiff: instance GHC.Classes.Eq Distribution.RPM.PackageTreeDiff.Mode

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@ # Changelog +# 0.6.0 (2022-10-27)+- significant processing performance improvement: drop expensive nub+- allow multiple mode options and separate summary & rst options+- add support for package list files via http+- fix local filesystem recursion+- initial testsuite+ ## 0.5.0 (2021-11-03) - use rpm-nvr library for NVRA type (API changes) and rpmVerCompare   to order ver-rel's
README.md view
@@ -5,82 +5,103 @@ [![Stackage Lts](http://stackage.org/package/pkgtreediff/badge/lts)](http://stackage.org/lts/package/pkgtreediff) [![Stackage Nightly](http://stackage.org/package/pkgtreediff/badge/nightly)](http://stackage.org/nightly/package/pkgtreediff) -`pkgtreediff` compares the NVRs (name-version-release) of RPM packages in OS package trees and/or installations:+`pkgtreediff` compares the NVRs (name-version-release) of RPM packages+in OS package trees and/or installations:  - An OS tree can be referenced by an url or directory containing a tree of rpm files.-- A file containing a list(s) of rpm NVRs can also be compared.+- A text file/url containing a list of rpm NVRs can also be compared. - Commands can also be used to get installed RPMs, eg:   - `"rpm -qa"`   - `"ssh myhost rpm -qa"`   - `"podman run --rm myimage rpm -qa"`+  - `"sudo dnf -q repoquery --qf '%{name}-%{version}-%{release}.%{arch}'"` - A koji tag: koji://tag@KojiHubUrl (or koji://tag@fedora)  ## Usage examples -### Containers+### Help+```shellsession+$ pkgtreediff --version+0.6.0+$ pkgtreediff -h+Package tree comparison tool +Usage: pkgtreediff [--version] [-r|--recursive] [-d|--subdir SUBDIR]+                   [(-R|--ignore-release) | (-V|--ignore-version)]+                   [(-N|--new) | (-D|--deleted) | (-U|--updated) |+                     (-u|--downgraded)]+                   [(-s|--show-summary) | (-S|--no-summary)] [-R|--rst]+                   [-p|--pattern PKGPATTERN] [-t|--timeout SECONDS]+                   URL|DIR|FILE|KOJITAG|CMD1 URL|DIR|FILE|KOJITAG|CMD2+  pkgtreediff compares the packages in two OS trees or instances++Available options:+  -h,--help                Show this help text+  --version                Show version+  -r,--recursive           Recursive down into subdirectories+  -d,--subdir SUBDIR       Select specific subdir (eg x86_64 or source)+  -R,--ignore-release      Only show version changes (ignore release)+  -V,--ignore-version      Only show package changes (ignore version-release)+  -N,--new                 Show only added packages+  -D,--deleted             Show only removed packages+  -U,--updated             Show only upgraded packages+  -u,--downgraded          Show only downgraded packages+  -s,--show-summary        Show summary of changes (default when >20 changes)+  -S,--no-summary          Do not display summary+  -R,--rst                 Use ReSTructured Text format+  -p,--pattern PKGPATTERN  Limit packages to glob matches+  -t,--timeout SECONDS     Maximum seconds to wait for http response before+                           timing out (default 30)+```++### Containers Compare the content of two rpm based containers versions, filtering for new added packages:  ```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+$ pkgtreediff --new "podman run --rm fedora:36 rpm -qa" "podman run --rm fedora:37 rpm -qa"+libb2-0.98.1-7.fc37.x86_64 ```  ### Package trees -Package source changes between Fedora 33 and 34 Server at GA (ignoring release bumps):+Package source changes between Fedora 35 and 36 Server at GA+(ignoring release bumps):  ```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-:-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+$ pkgtreediff --ignore-release https://download.fedoraproject.org/pub/fedora/linux/releases/{35,36}/Server/source/tree/Packages/+ModemManager.src: 1.18.2-1.fc35 -> 1.18.6-1.fc36+NetworkManager.src: 1.32.12-1.fc35 -> 1.36.4-1.fc36+PackageKit.src: 1.2.4-3.fc35 -> 1.2.5-1.fc36+abrt.src: 2.14.6-9.fc35 -> 2.15.1-1.fc36 : :-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+- tree-1.8.0-7.fc35.src++ tree-pkg-2.0.1-2.fc36.src+tzdata.src: 2021b-1.fc35 -> 2022a-1.fc36 :-zchunk.src: 1.1.5-3.fc33 -> 1.1.9-2.fc34-zstd.src: 1.4.5-5.fc33 -> 1.4.9-1.fc34+xorg-x11-xauth.src: 1.1-9.fc35 -> 1.1.1-2.fc36++ xrdb-1.2.1-3.fc36.src+xxhash.src: 0.8.0-4.fc35 -> 0.8.1-2.fc36+yelp.src: 41.1-1.fc35 -> 42.1-1.fc36+yelp-xsl.src: 41.0-1.fc35 -> 42.0-1.fc36+zchunk.src: 1.1.15-2.fc35 -> 1.2.1-1.fc36+zstd.src: 1.5.0-2.fc35 -> 1.5.2-1.fc36  Summary-Updated: 330+Updated: 293 Downgraded: 0-Added: 63-Deleted: 98+Added: 36+Deleted: 21 Arch changed: 0-Total packages: 932 -> 897+Total packages: 915 -> 930 ``` -### Hosts+For very large repos like Everything it should be faster+to use a repoquery command. +### Hosts Compare the packages on local and another host:- ``` pkgtreediff "rpm -qa" "ssh otherhost rpm -qa" ```@@ -88,15 +109,13 @@ Any types of sources can be compared, together with the use of flags.  ### Koji- Compare koji tags using the `koji://tag@kojihub` syntax:- ``` pkgtreediff koji://dist-c8-updates-build@centos koji://dist-c8_1-updates-build@centos ``` -Please avoid using koji tag to compare full releases as it is more efficient to-query URL or CMD trees.+Please avoid using koji tags to compare full releases as it is more efficient+to query URL or CMD trees.  ## Builds pkgtreediff is packaged in Fedora.@@ -107,10 +126,9 @@ ([more details](https://copr.fedorainfracloud.org/coprs/petersen/pkgtreediff/monitor/detailed)).  ## RPM version ordering+RPM version ordering is somewhat [complicated](https://blog.jasonantman.com/2014/07/how-yum-and-rpm-compare-versions/).+For example "1.2.1~rc1" < "1.2.1" (greater for ^).+ 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" (greater for ^).
TODO view
@@ -9,4 +9,6 @@ - support epochs in file package list - diff isos - "--query-format"-- testsuite!!+- more tests++- diff via repoquery
pkgtreediff.cabal view
@@ -1,21 +1,22 @@ cabal-version:       1.18 name:                pkgtreediff-version:             0.5.0+version:             0.6.0 synopsis:            RPM package tree diff tool-description:         Tool for comparing RPM packages and versions in OS dist trees or instances.+description:         Tool for comparing RPM packages and version-releases+                     in OS dist trees or instances. homepage:            https://github.com/juhp/pkgtreediff bug-reports:         https://github.com/juhp/pkgtreediff/issues license:             GPL-3 license-file:        LICENSE author:              Jens Petersen maintainer:          juhpetersen@gmail.com-copyright:           2019-2021 Jens Petersen+copyright:           2019-2022 Jens Petersen category:            Utility build-type:          Simple extra-doc-files:     README.md                    , CHANGELOG.md                    , TODO-tested-with:         GHC == 8.8.4, GHC == 8.10.7, GHC == 9.0.1+tested-with:         GHC == 8.8.4, GHC == 8.10.7, GHC == 9.0.2, GHC == 9.2.4                      GHC == 8.0.2, GHC == 8.2.2, GHC == 8.4.4, GHC == 8.6.5  source-repository head@@ -28,6 +29,7 @@    build-depends:       async                      , base < 5+                     , bytestring                      , directory                      , extra                      , filepath@@ -35,7 +37,8 @@                      , Glob                      , http-client >= 0.5.0                      , http-client-tls-                     , http-directory >= 0.1.4 && (< 0.1.6 || >= 0.1.8)+                     , http-directory >= 0.1.10+                     , http-types                      , pkgtreediff                      , rpm-nvr                      , simple-cmd >= 0.1.4@@ -50,7 +53,6 @@   default-language:    Haskell2010  library-  build-depends:       base < 5   exposed-modules:     Distribution.RPM.PackageTreeDiff   hs-source-dirs:      src @@ -74,3 +76,15 @@                        -Wpartial-fields   if impl(ghc >= 8.10)     ghc-options:       -Wunused-packages++test-suite test+    main-is: tests.hs+    type: exitcode-stdio-1.0+    hs-source-dirs: test++    default-language: Haskell2010++    ghc-options:   -Wall+    build-depends: base >= 4 && < 5,+                   simple-cmd+    build-tool-depends: pkgtreediff:pkgtreediff
src/Distribution/RPM/PackageTreeDiff.hs view
@@ -2,12 +2,12 @@  -- | A library for pkgtreediff for comparing trees of rpm packages module Distribution.RPM.PackageTreeDiff-  (NVRA(..),-   readNVRA,-   RPMPkgDiff(..),-   diffPkgs,+  (RPMPkgDiff(..),    Ignore(..),-   Mode(..),+   diffPkgs,+   -- * from rpm-nvr+   NVRA(..),+   readNVRA   ) where  #if !MIN_VERSION_base(4,8,0)@@ -16,17 +16,6 @@ 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-          | Downgraded-          | RST-  deriving Eq  -- | Ignore describes how comparison is done data Ignore = IgnoreNone    -- ^ do not ignore version or release
src/Main.hs view
@@ -12,6 +12,7 @@ import Control.Concurrent.Async (concurrently) import Control.Monad import Control.Monad.Extra (concatMapM)+import qualified Data.ByteString.Char8 as B import Data.List import Data.Maybe import Data.RPM.NVRA@@ -19,12 +20,15 @@ import Data.Semigroup ((<>)) #endif import qualified Data.Text as T-import Network.HTTP.Client (managerResponseTimeout, newManager,-                            responseTimeoutMicro)+import qualified Data.Text.Lazy as TL+import Data.Text.Lazy.Encoding (decodeUtf8)+import Network.HTTP.Client (httpLbs, managerResponseTimeout, newManager,+                            parseRequest, responseBody, responseTimeoutMicro) import Network.HTTP.Client.TLS (tlsManagerSettings) import Network.HTTP.Directory+import Network.HTTP.Types (hContentType) import SimpleCmdArgs-import System.Directory (doesDirectoryExist, getDirectoryContents)+import System.Directory (doesDirectoryExist, listDirectory) import System.FilePath ((</>)) import System.FilePath.Glob (compile, match) #if !MIN_VERSION_simple_cmd(0,2,0)@@ -43,11 +47,32 @@ import Distribution.RPM.PackageTreeDiff import Paths_pkgtreediff (version) +data Summary = AutoSummary+             | NoSummary+             | ShowSummary+  deriving Eq++data Mode = Added+          | Deleted+          | Updated+          | Downgraded+  deriving Eq+ main :: IO () main =   simpleCmdArgs (Just version) "Package tree comparison tool"   "pkgtreediff compares the packages in two OS trees or instances" $-    compareDirs <$> recursiveOpt <*> optional subdirOpt <*> ignoreVR <*> modeOpt  <*> optional patternOpt <*> timeoutOpt <*> sourceArg "1" <*> sourceArg "2"+  compareDirs+    <$> recursiveOpt+    <*> optional subdirOpt+    <*> ignoreVR+    <*> many modeOpt+    <*> summaryOpt+    <*> switchWith 'R' "rst" "Use ReSTructured Text format"+    <*> optional patternOpt+    <*> timeoutOpt+    <*> sourceArg "1"+    <*> sourceArg "2"   where     sourceArg :: String -> Parser String     sourceArg pos = strArg ("URL|DIR|FILE|KOJITAG|CMD" <> pos)@@ -57,9 +82,10 @@       flagWith' Added 'N' "new" "Show only added packages" <|>       flagWith' Deleted 'D' "deleted" "Show only removed packages" <|>       flagWith' Updated 'U' "updated" "Show only upgraded packages" <|>-      flagWith' Downgraded 'u' "downgraded" "Show only downgraded packages" <|>+      flagWith' Downgraded 'u' "downgraded" "Show only downgraded packages"++    summaryOpt =       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@@ -120,18 +146,19 @@     isHttp loc = "http:" `isPrefixOf` loc || "https:" `isPrefixOf` loc  -- | Frontend for the pkgtreediff tool-compareDirs :: Bool -> Maybe String -> Ignore -> Mode -> Maybe String -> Int -> String -> String -> IO ()-compareDirs recursive msubdir ignore mode mpattern timeout tree1 tree2 = do+compareDirs :: Bool -> Maybe String -> Ignore -> [Mode] -> Summary -> Bool+            -> Maybe String -> Int -> String -> String -> IO ()+compareDirs recursive msubdir ignore modes summary rst mpattern timeout tree1 tree2 = do   (ps1,ps2) <- getTrees tree1 tree2   let diff = diffPkgs ignore ps1 ps2-  if mode /= RST-    then mapM_ putStrLn . mapMaybe (showPkgDiff mode) $ diff-    else printRST diff-  when (mode /= NoSummary && isDefault mode) $-    when (mode == ShowSummary || length diff > summaryThreshold) $ do+  if rst+    then printRST diff+    else mapM_ putStrLn . mapMaybe (showPkgDiff modes) $ diff+  when (summary /= NoSummary && null modes) $+    when (summary == ShowSummary || length diff > summaryThreshold) $ do     putStrLn ""-    (if mode /= RST then putStrLn else printRSTHeader) "Summary"-    let diffsum = summary diff+    (if rst then printRSTHeader else putStrLn) "Summary"+    let diffsum = changesummary diff     putStrLn $ "Updated: " <> show (updateSum diffsum)     putStrLn $ "Downgraded: " <> show (downgradeSum diffsum)     putStrLn $ "Added: " <> show (newSum diffsum)@@ -148,13 +175,13 @@     printRSTDiffElem = printRSTElem . drop 2     printRST diff = do       printRSTHeader "Updated"-      mapM_ printRSTElem $ mapMaybe (showPkgDiff mode) [x | x@(PkgUpdate _ _) <- diff]+      mapM_ printRSTElem $ mapMaybe (showPkgDiff modes) [x | x@(PkgUpdate _ _) <- diff]       printRSTHeader "Downgraded"-      mapM_ printRSTElem $ mapMaybe (showPkgDiff mode) [x | x@(PkgDowngrade _ _) <- diff]+      mapM_ printRSTElem $ mapMaybe (showPkgDiff modes) [x | x@(PkgDowngrade _ _) <- diff]       printRSTHeader "Added"-      mapM_ printRSTDiffElem $ mapMaybe (showPkgDiff mode) [x | x@(PkgAdd _) <- diff]+      mapM_ printRSTDiffElem $ mapMaybe (showPkgDiff modes) [x | x@(PkgAdd _) <- diff]       printRSTHeader "Removed"-      mapM_ printRSTDiffElem $ mapMaybe (showPkgDiff mode) [x | x@(PkgDel _) <- diff]+      mapM_ printRSTDiffElem $ mapMaybe (showPkgDiff modes) [x | x@(PkgDel _) <- diff]      getTrees :: String -> String -> IO ([NVRA],[NVRA])     getTrees t1 t2 = do@@ -183,16 +210,26 @@               Dir -> dirPackages True loc               File -> filePackages loc               Cmd -> cmdPackages $ words loc-      let ps = map readNVRA $ filter (maybe (const True) (match . compile) mpattern) fs-      return $ sort (nub ps)+      let ps = map readNVRA $ maybe id (filter . (match . compile)) mpattern fs+      return $ sort ps      httpPackages :: Bool -> Manager -> String -> IO [String]     httpPackages recurse mgr url = do       exists <- httpExists mgr url-      fs <- if exists-            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 </>) (filterSubdir fs)) else return $ filter (not . isDir) fs+      fs <-+        if exists+        then do+          mcontenttype <- lookup hContentType <$> httpFileHeaders mgr url+          if mcontenttype == Just (B.pack "text/plain; charset=UTF-8")+            then do+            request <- parseRequest url+            body <- responseBody <$> httpLbs request mgr+            return $ (filter (".rpm" `isSuffixOf`) . map TL.unpack . TL.lines . decodeUtf8) body+            else 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 </>) (filterSubdir fs))+        else return $ filter (not . isDir) fs      filterSubdir :: [String] -> [String]     filterSubdir fs =@@ -201,11 +238,12 @@         _ -> 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+      fs <- map (dir </>) . sort <$> listDirectory dir       alldirs <- mapM doesDirectoryExist fs-      if (recurse || recursive) && and alldirs then concatMapM (dirPackages False) (map (dir </>) (filterSubdir fs)) else return $ filter (not . isDir) fs+      if (recurse || recursive) && and alldirs+        then concatMapM (dirPackages False) (filterSubdir fs)+        -- FIXME strictly should limit to files+        else return $ filter (".rpm" `isSuffixOf`) fs      isDir = ("/" `isSuffixOf`) @@ -219,26 +257,22 @@      kojiPackages (tag, kojiUrl) = map Koji.kbNvr <$> Koji.kojiListTaggedBuilds kojiUrl True tag -isDefault :: Mode -> Bool-isDefault m = m `elem` [AutoSummary, NoSummary, ShowSummary, RST]--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+showPkgDiff :: [Mode] -> RPMPkgDiff -> Maybe String+showPkgDiff modes diff =+  case diff of+    PkgAdd p -> maybeShowDiff Added "+ " $ showNVRA p+    PkgDel p -> maybeShowDiff Deleted "- " $ showNVRA p+    PkgUpdate p1 p2 -> maybeShowDiff Updated "" $ showPkgChange p1 p2+    PkgArch p1 p2 -> maybeShowDiff Updated "" $ showArchChange p1 p2+    PkgDowngrade p1 p2 -> maybeShowDiff Downgraded "~ " $ showPkgChange p1 p2   where+    maybeShowDiff :: Mode -> String -> String -> Maybe String+    maybeShowDiff m prefix ds+      | null modes || (m `elem` modes && length modes > 1)+      = Just $ prefix ++ ds+      | modes == [m] = Just ds+      | otherwise = Nothing+     showPkgChange :: NVRA -> NVRA -> String     showPkgChange p p' =       showPkgIdent p <> ": " <> showPkgVerRel p <> " -> " <> showPkgVerRel p'@@ -255,8 +289,8 @@ emptyDS :: DiffSum emptyDS = DS 0 0 0 0 0 -summary :: [RPMPkgDiff] -> DiffSum-summary =+changesummary :: [RPMPkgDiff] -> DiffSum+changesummary =   foldl' countDiff emptyDS   where     countDiff :: DiffSum -> RPMPkgDiff -> DiffSum
+ test/tests.hs view
@@ -0,0 +1,23 @@+import SimpleCmd+import System.IO++program :: [String] -> IO ()+program test = do+  timeIO $ cmdLog "pkgtreediff" test+  putStrLn ""++tests :: [[String]]+tests =+  [+    ["-r", "test/files/35", "test/files/36"]+  -- FIXME until native repoquerying use dnf-repo+  , ["--ignore-release",+     "dnf-repo -q -d * -u http://download.fedoraproject.org/pub/fedora/linux/releases/35/Everything/x86_64/os/ repoquery --qf %{name}-%{version}-%{release}.%{arch}",+     "dnf-repo -q -d * -u http://download.fedoraproject.org/pub/fedora/linux/releases/36/Everything/x86_64/os/ repoquery --qf %{name}-%{version}-%{release}.%{arch}"]+  ]++main :: IO ()+main = do+  hSetBuffering stdout NoBuffering+  mapM_ program tests+  putStrLn $ "\n" ++ show (length tests) ++ " command tests run"