module Nix.Diff.Transformations where

import qualified Patience
import Data.Generics.Uniplate.Data ( transformBi )

import Nix.Diff.Types
import qualified Data.Set as Set
import Data.Text (Text)

{-| In large diffs there may be a lot of derivations
    that doesn't change at all, but changed some of
    its nested inputs, that was already compared.
    This case will produce "stairs" of useless reports:
    ```
    • The input derivation named `a` differs
      - /nix/store/j1jmbxd74kzianaywml2nw1ja31a00r5-a.drv:{out}
      + /nix/store/ww51c2dha7m5l5qjzh2rblicsamkrh62-a.drv:{out}
      • The input derivation named `b` differs
        - /nix/store/j1jmbxd74kzianaywml2nw1ja31a00r5-b.drv:{out}
        + /nix/store/ww51c2dha7m5l5qjzh2rblicsamkrh62-b.drv:{out}
        • The input derivation named `c` differs
          • These two derivations have already been compared
    ```
    This transformation will fold all these subtrees of diff
    into one OnlyAlreadyComparedBelow.
-}
foldAlreadyComparedSubTrees :: DerivationDiff -> DerivationDiff
foldAlreadyComparedSubTrees :: DerivationDiff -> DerivationDiff
foldAlreadyComparedSubTrees DerivationDiff
dd = case DerivationDiff
dd of
  DerivationDiff
DerivationsAreTheSame -> DerivationDiff
dd
  DerivationDiff
AlreadyCompared -> DerivationDiff
dd
  OnlyAlreadyComparedBelow{} -> DerivationDiff
dd
  NamesDontMatch{} -> DerivationDiff
dd
  OutputsDontMatch{} -> DerivationDiff
dd
  DerivationDiff{Maybe EnvironmentDiff
Maybe ArgumentsDiff
Maybe (Changed Text)
InputsDiff
SourcesDiff
OutputsDiff
Changed OutputStructure
outputStructure :: Changed OutputStructure
outputsDiff :: OutputsDiff
platformDiff :: Maybe (Changed Text)
builderDiff :: Maybe (Changed Text)
argumentsDiff :: Maybe ArgumentsDiff
sourcesDiff :: SourcesDiff
inputsDiff :: InputsDiff
envDiff :: Maybe EnvironmentDiff
$sel:outputStructure:DerivationsAreTheSame :: DerivationDiff -> Changed OutputStructure
$sel:outputsDiff:DerivationsAreTheSame :: DerivationDiff -> OutputsDiff
$sel:platformDiff:DerivationsAreTheSame :: DerivationDiff -> Maybe (Changed Text)
$sel:builderDiff:DerivationsAreTheSame :: DerivationDiff -> Maybe (Changed Text)
$sel:argumentsDiff:DerivationsAreTheSame :: DerivationDiff -> Maybe ArgumentsDiff
$sel:sourcesDiff:DerivationsAreTheSame :: DerivationDiff -> SourcesDiff
$sel:inputsDiff:DerivationsAreTheSame :: DerivationDiff -> InputsDiff
$sel:envDiff:DerivationsAreTheSame :: DerivationDiff -> Maybe EnvironmentDiff
..} -> if
      | OutputsDiff Maybe (Changed (Map Text (DerivationOutput StorePath Text)))
Nothing [] <- OutputsDiff
outputsDiff
      , Maybe (Changed Text)
Nothing <- Maybe (Changed Text)
platformDiff
      , Maybe (Changed Text)
Nothing <- Maybe (Changed Text)
builderDiff
      , Maybe ArgumentsDiff
Nothing <- Maybe ArgumentsDiff
argumentsDiff
      , SourcesDiff Maybe (Changed (Set Text))
Nothing [] <- SourcesDiff
sourcesDiff
      , InputsDiff Maybe (Changed (Set Text))
Nothing [InputDerivationsDiff]
inputs <- InputsDiff
inputsDiff'
      , (InputDerivationsDiff -> Bool) -> [InputDerivationsDiff] -> Bool
forall (t :: * -> *) a. Foldable t => (a -> Bool) -> t a -> Bool
all InputDerivationsDiff -> Bool
alreadyComparedBelow [InputDerivationsDiff]
inputs
      , Maybe EnvironmentDiff -> Bool
envSkippedOrUnchanged Maybe EnvironmentDiff
envDiff
          -> Changed OutputStructure -> DerivationDiff
OnlyAlreadyComparedBelow Changed OutputStructure
outputStructure

      | Bool
otherwise -> DerivationDiff
          { Changed OutputStructure
outputStructure :: Changed OutputStructure
$sel:outputStructure:DerivationsAreTheSame :: Changed OutputStructure
outputStructure
          , OutputsDiff
outputsDiff :: OutputsDiff
$sel:outputsDiff:DerivationsAreTheSame :: OutputsDiff
outputsDiff
          , Maybe (Changed Text)
platformDiff :: Maybe (Changed Text)
$sel:platformDiff:DerivationsAreTheSame :: Maybe (Changed Text)
platformDiff
          , Maybe (Changed Text)
builderDiff :: Maybe (Changed Text)
$sel:builderDiff:DerivationsAreTheSame :: Maybe (Changed Text)
builderDiff
          , Maybe ArgumentsDiff
argumentsDiff :: Maybe ArgumentsDiff
$sel:argumentsDiff:DerivationsAreTheSame :: Maybe ArgumentsDiff
argumentsDiff
          , SourcesDiff
sourcesDiff :: SourcesDiff
$sel:sourcesDiff:DerivationsAreTheSame :: SourcesDiff
sourcesDiff
          , $sel:inputsDiff:DerivationsAreTheSame :: InputsDiff
inputsDiff = InputsDiff
inputsDiff'
          , Maybe EnvironmentDiff
envDiff :: Maybe EnvironmentDiff
$sel:envDiff:DerivationsAreTheSame :: Maybe EnvironmentDiff
envDiff
          }
    where
      inputsDiff' :: InputsDiff
inputsDiff' = (DerivationDiff -> DerivationDiff) -> InputsDiff -> InputsDiff
transformNestedDerivationDiffs
            DerivationDiff -> DerivationDiff
foldAlreadyComparedSubTrees
            InputsDiff
inputsDiff

{-| If packages deep in the dependency graph have been changed, many other
   derivations will also change in an uninteresting manner. This can lead to
   hundreds or thousands of lines of output like this:

   ```
   • The input derivation named `bash-5.2p32` differs
     • These two derivations have already been compared
   • The input derivation named `ensure-newer-sources-hook` differs
     • These two derivations have already been compared
   • The input derivation named `pypa-install-hook` differs
     • These two derivations have already been compared
   ```

   This transformation will fold sequences of `OneDerivationDiff` like this
   into a single `ManyDerivationsAlreadyComparedDiff`.
-}
foldManyInputDerivationsAlreadyCompared :: DerivationDiff -> DerivationDiff
foldManyInputDerivationsAlreadyCompared :: DerivationDiff -> DerivationDiff
foldManyInputDerivationsAlreadyCompared DerivationDiff
dd = case DerivationDiff
dd of
  DerivationDiff
DerivationsAreTheSame -> DerivationDiff
dd
  DerivationDiff
AlreadyCompared -> DerivationDiff
dd
  OnlyAlreadyComparedBelow{} -> DerivationDiff
dd
  NamesDontMatch{} -> DerivationDiff
dd
  OutputsDontMatch{} -> DerivationDiff
dd
  DerivationDiff{Maybe EnvironmentDiff
Maybe ArgumentsDiff
Maybe (Changed Text)
InputsDiff
SourcesDiff
OutputsDiff
Changed OutputStructure
$sel:outputStructure:DerivationsAreTheSame :: DerivationDiff -> Changed OutputStructure
$sel:outputsDiff:DerivationsAreTheSame :: DerivationDiff -> OutputsDiff
$sel:platformDiff:DerivationsAreTheSame :: DerivationDiff -> Maybe (Changed Text)
$sel:builderDiff:DerivationsAreTheSame :: DerivationDiff -> Maybe (Changed Text)
$sel:argumentsDiff:DerivationsAreTheSame :: DerivationDiff -> Maybe ArgumentsDiff
$sel:sourcesDiff:DerivationsAreTheSame :: DerivationDiff -> SourcesDiff
$sel:inputsDiff:DerivationsAreTheSame :: DerivationDiff -> InputsDiff
$sel:envDiff:DerivationsAreTheSame :: DerivationDiff -> Maybe EnvironmentDiff
outputStructure :: Changed OutputStructure
outputsDiff :: OutputsDiff
platformDiff :: Maybe (Changed Text)
builderDiff :: Maybe (Changed Text)
argumentsDiff :: Maybe ArgumentsDiff
sourcesDiff :: SourcesDiff
inputsDiff :: InputsDiff
envDiff :: Maybe EnvironmentDiff
..} ->
    let inputsDiff' :: InputsDiff
inputsDiff' = (DerivationDiff -> DerivationDiff) -> InputsDiff -> InputsDiff
transformNestedDerivationDiffs
                        DerivationDiff -> DerivationDiff
foldManyInputDerivationsAlreadyCompared
                        InputsDiff
inputsDiff

        helper :: [Text] -> [InputDerivationsDiff] -> [InputDerivationsDiff]
        helper :: [Text] -> [InputDerivationsDiff] -> [InputDerivationsDiff]
helper [] [] = []
        helper [Text]
names [] =
          [ManyDerivationsAlreadyComparedDiff
            { $sel:drvNames:OneDerivationDiff :: Set Text
drvNames = [Text] -> Set Text
forall a. Ord a => [a] -> Set a
Set.fromList [Text]
names
            }]
        helper [Text]
names (InputDerivationsDiff
input:[InputDerivationsDiff]
inputs) =
          case InputDerivationsDiff
input of
            OneDerivationDiff
              { Text
drvName :: Text
$sel:drvName:OneDerivationDiff :: InputDerivationsDiff -> Text
drvName
              , $sel:drvDiff:OneDerivationDiff :: InputDerivationsDiff -> DerivationDiff
drvDiff = DerivationDiff
AlreadyCompared
              } -> [Text] -> [InputDerivationsDiff] -> [InputDerivationsDiff]
helper
                    (Text
drvNameText -> [Text] -> [Text]
forall a. a -> [a] -> [a]
:[Text]
names)
                    [InputDerivationsDiff]
inputs
            OneDerivationDiff{} -> InputDerivationsDiff
input InputDerivationsDiff
-> [InputDerivationsDiff] -> [InputDerivationsDiff]
forall a. a -> [a] -> [a]
: [Text] -> [InputDerivationsDiff] -> [InputDerivationsDiff]
helper [Text]
names [InputDerivationsDiff]
inputs
            SomeDerivationsDiff{} -> InputDerivationsDiff
input InputDerivationsDiff
-> [InputDerivationsDiff] -> [InputDerivationsDiff]
forall a. a -> [a] -> [a]
: [Text] -> [InputDerivationsDiff] -> [InputDerivationsDiff]
helper [Text]
names [InputDerivationsDiff]
inputs
            ManyDerivationsAlreadyComparedDiff{} -> [Char] -> [InputDerivationsDiff]
forall a. HasCallStack => [Char] -> a
error [Char]
"unreachable"

     in DerivationDiff
       { $sel:inputsDiff:DerivationsAreTheSame :: InputsDiff
inputsDiff =
           InputsDiff
inputsDiff'
             { inputDerivationDiffs =
                 helper [] inputsDiff'.inputDerivationDiffs
             }
       , Maybe EnvironmentDiff
Maybe ArgumentsDiff
Maybe (Changed Text)
SourcesDiff
OutputsDiff
Changed OutputStructure
$sel:outputStructure:DerivationsAreTheSame :: Changed OutputStructure
$sel:outputsDiff:DerivationsAreTheSame :: OutputsDiff
$sel:platformDiff:DerivationsAreTheSame :: Maybe (Changed Text)
$sel:builderDiff:DerivationsAreTheSame :: Maybe (Changed Text)
$sel:argumentsDiff:DerivationsAreTheSame :: Maybe ArgumentsDiff
$sel:sourcesDiff:DerivationsAreTheSame :: SourcesDiff
$sel:envDiff:DerivationsAreTheSame :: Maybe EnvironmentDiff
outputStructure :: Changed OutputStructure
outputsDiff :: OutputsDiff
platformDiff :: Maybe (Changed Text)
builderDiff :: Maybe (Changed Text)
argumentsDiff :: Maybe ArgumentsDiff
sourcesDiff :: SourcesDiff
envDiff :: Maybe EnvironmentDiff
..
       }

{-| This transformation is most useful for
    --json output, because it will sqash a lot of
    `{"content":"  ","type":"Both"},{"content":"When","type":"Both"},{"content":" ","type":"Both"},{"content":"in","type":"Both"},{"content":" ","type":"Both"}`
    into one
    `{"content":"  When in ","type":"Both"}`
    block.

    To understand this problem clearer, see `golden-tests/expected-outputs/json`
    and `golden-tests/expected-outputs/json-squashed`.

    _Warning_: this transformation can break some parts of printing in
    human readable mode.
-}
squashSourcesAndEnvsDiff :: DerivationDiff -> DerivationDiff
squashSourcesAndEnvsDiff :: DerivationDiff -> DerivationDiff
squashSourcesAndEnvsDiff = (TextDiff -> TextDiff) -> DerivationDiff -> DerivationDiff
forall from to. Biplate from to => (to -> to) -> from -> from
transformBi
    \(TextDiff [Item Text]
x) -> [Item Text] -> TextDiff
TextDiff ([Item Text] -> [Item Text]
forall {a}. Semigroup a => [Item a] -> [Item a]
squashDiff [Item Text]
x)
  where
    squashDiff :: [Item a] -> [Item a]
squashDiff (Patience.Old a
a : Patience.Old a
b : [Item a]
xs) =
      [Item a] -> [Item a]
squashDiff (a -> Item a
forall a. a -> Item a
Patience.Old (a
a a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
b) Item a -> [Item a] -> [Item a]
forall a. a -> [a] -> [a]
: [Item a]
xs)
    squashDiff (Patience.New a
a : Patience.New a
b : [Item a]
xs) =
      [Item a] -> [Item a]
squashDiff (a -> Item a
forall a. a -> Item a
Patience.New (a
a a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
b) Item a -> [Item a] -> [Item a]
forall a. a -> [a] -> [a]
: [Item a]
xs)
    squashDiff (Patience.Both a
a a
_ : Patience.Both a
b a
_ : [Item a]
xs) =
      let ab :: a
ab = a
a a -> a -> a
forall a. Semigroup a => a -> a -> a
<> a
b in [Item a] -> [Item a]
squashDiff (a -> a -> Item a
forall a. a -> a -> Item a
Patience.Both a
ab a
ab Item a -> [Item a] -> [Item a]
forall a. a -> [a] -> [a]
: [Item a]
xs)
    squashDiff (Item a
x : [Item a]
xs) = Item a
x Item a -> [Item a] -> [Item a]
forall a. a -> [a] -> [a]
: [Item a] -> [Item a]
squashDiff [Item a]
xs
    squashDiff [] = []

-- ** Helpers

transformNestedDerivationDiffs
  :: (DerivationDiff -> DerivationDiff)
  -> InputsDiff
  -> InputsDiff
transformNestedDerivationDiffs :: (DerivationDiff -> DerivationDiff) -> InputsDiff -> InputsDiff
transformNestedDerivationDiffs DerivationDiff -> DerivationDiff
f InputsDiff{[InputDerivationsDiff]
Maybe (Changed (Set Text))
$sel:inputDerivationDiffs:InputsDiff :: InputsDiff -> [InputDerivationsDiff]
inputExtraNames :: Maybe (Changed (Set Text))
inputDerivationDiffs :: [InputDerivationsDiff]
$sel:inputExtraNames:InputsDiff :: InputsDiff -> Maybe (Changed (Set Text))
..} = InputsDiff
  { Maybe (Changed (Set Text))
inputExtraNames :: Maybe (Changed (Set Text))
$sel:inputExtraNames:InputsDiff :: Maybe (Changed (Set Text))
inputExtraNames
  , $sel:inputDerivationDiffs:InputsDiff :: [InputDerivationsDiff]
inputDerivationDiffs = (InputDerivationsDiff -> InputDerivationsDiff)
-> [InputDerivationsDiff] -> [InputDerivationsDiff]
forall a b. (a -> b) -> [a] -> [b]
map InputDerivationsDiff -> InputDerivationsDiff
changeDerivation [InputDerivationsDiff]
inputDerivationDiffs
  }
  where
    changeDerivation :: InputDerivationsDiff -> InputDerivationsDiff
changeDerivation InputDerivationsDiff
idd = case InputDerivationsDiff
idd of
      OneDerivationDiff Text
name DerivationDiff
dd ->
        Text -> DerivationDiff -> InputDerivationsDiff
OneDerivationDiff Text
name (DerivationDiff -> DerivationDiff
f DerivationDiff
dd)
      SomeDerivationsDiff {} -> InputDerivationsDiff
idd
      ManyDerivationsAlreadyComparedDiff {} -> InputDerivationsDiff
idd

envSkippedOrUnchanged :: Maybe EnvironmentDiff -> Bool
envSkippedOrUnchanged :: Maybe EnvironmentDiff -> Bool
envSkippedOrUnchanged = \case
  Maybe EnvironmentDiff
Nothing -> Bool
True
  Just EnvironmentDiff
EnvironmentsAreEqual -> Bool
True
  Maybe EnvironmentDiff
_ -> Bool
False

alreadyComparedBelow :: InputDerivationsDiff -> Bool
alreadyComparedBelow :: InputDerivationsDiff -> Bool
alreadyComparedBelow = \case
  OneDerivationDiff Text
_ DerivationDiff
AlreadyCompared -> Bool
True
  OneDerivationDiff Text
_ OnlyAlreadyComparedBelow{} -> Bool
True
  InputDerivationsDiff
_ -> Bool
False

transformIf :: Bool -> (DerivationDiff -> DerivationDiff) -> DerivationDiff -> DerivationDiff
transformIf :: Bool
-> (DerivationDiff -> DerivationDiff)
-> DerivationDiff
-> DerivationDiff
transformIf Bool
False DerivationDiff -> DerivationDiff
_ = DerivationDiff -> DerivationDiff
forall a. a -> a
id
transformIf Bool
True DerivationDiff -> DerivationDiff
f = DerivationDiff -> DerivationDiff
f