diff --git a/ChangeLog.md b/ChangeLog.md
--- a/ChangeLog.md
+++ b/ChangeLog.md
@@ -1,5 +1,10 @@
 # ChangeLog for mega-sdist
 
+## 0.4.0.0
+
+* Move over to pantry for getting package information
+* Ignore whitespace changes [haskell/hackage-server#815](https://github.com/haskell/hackage-server/issues/815)
+
 ## 0.3.3.2
 
 * Compatibility with newer http-conduit [stackage#4215](https://github.com/commercialhaskell/stackage/issues/4215)
diff --git a/mega-sdist.cabal b/mega-sdist.cabal
--- a/mega-sdist.cabal
+++ b/mega-sdist.cabal
@@ -1,13 +1,13 @@
 cabal-version: 1.12
 
--- This file has been generated from package.yaml by hpack version 0.30.0.
+-- This file has been generated from package.yaml by hpack version 0.31.1.
 --
 -- see: https://github.com/sol/hpack
 --
--- hash: a87f87ff54b42f68d7c3609041447872449a12e5f9a731d1d1401b3542cc5cc0
+-- hash: 904dabc820ef679d24b468245d2ace4571b1c8e6c495cd42270b8d7fe6439d2b
 
 name:           mega-sdist
-version:        0.3.3.2
+version:        0.4.0.0
 synopsis:       Handles uploading to Hackage from mega repos
 description:    Please see the description on Github at <https://github.com/snoyberg/mega-sdist#readme>
 category:       Distribution
@@ -31,13 +31,12 @@
   build-depends:
       base >=4 && <5
     , bytestring
-    , conduit >=1.3
-    , conduit-extra
-    , http-conduit >=2.3
     , optparse-simple
+    , pantry-tmp
+    , path
+    , path-io
     , rio
     , rio-orphans
-    , tar-conduit >=0.2.1
     , yaml
   other-modules:
       Paths_mega_sdist
diff --git a/mega-sdist.hs b/mega-sdist.hs
--- a/mega-sdist.hs
+++ b/mega-sdist.hs
@@ -5,32 +5,27 @@
 {-# LANGUAGE TemplateHaskell #-}
 import RIO
 import RIO.Orphans
-import Conduit
+import Pantry hiding (Package (..))
 import RIO.Directory
 import RIO.FilePath
 import qualified RIO.Map as Map
 import qualified RIO.HashMap as HM
-import Network.HTTP.Simple
-import Data.Conduit.Tar as Tar
-import Data.Conduit.Zlib (ungzip)
 import RIO.Process
-import Data.Conduit.Binary (sinkFileCautious)
 import Data.Yaml (Value (..), decodeEither')
 import Options.Applicative.Simple hiding (header, value)
 import qualified Paths_mega_sdist as Paths (version)
 import qualified RIO.ByteString.Lazy as BL
 import qualified Data.ByteString.Lazy.Char8 as BL8
-import Data.Semigroup (Max (..), Option (..))
-import qualified Data.Version
-import Text.ParserCombinators.ReadP (readP_to_S)
 import qualified RIO.Text as T
 import qualified RIO.List as L
-import RIO.Text.Partial (splitOn)
+import Path (parseAbsDir)
+import Path.IO (resolveFile')
 
 data App = App
   { appLogFunc :: !LogFunc
   , appProcessContext :: !ProcessContext
   , appResourceMap :: !ResourceMap
+  , appPantryConfig :: !PantryConfig
   }
 instance HasLogFunc App where
   logFuncL = lens appLogFunc (\x y -> x { appLogFunc = y })
@@ -38,16 +33,8 @@
   processContextL = lens appProcessContext (\x y -> x { appProcessContext = y })
 instance HasResourceMap App where
   resourceMapL = lens appResourceMap (\x y -> x { appResourceMap = y })
-
-getUrlHackage :: MonadIO m => Package -> m Request
-getUrlHackage (Package _fp (PackageName a) (Version b)) =
-    liftIO $ parseRequest $ T.unpack $ mconcat
-        [ "https://s3.amazonaws.com/hackage.fpcomplete.com/package/"
-        , a
-        , "-"
-        , b
-        , ".tar.gz"
-        ]
+instance HasPantryConfig App where
+  pantryConfigL = lens appPantryConfig (\x y -> x { appPantryConfig = y })
 
 getPaths :: MonadIO m => Value -> m [FilePath]
 getPaths value = maybe (error $ "getPaths failed: " ++ show value) return $ do
@@ -92,13 +79,23 @@
 
     lo <- logOptionsHandle stdout verbose
     pc <- mkDefaultProcessContext
-    withLogFunc lo $ \lf -> withResourceMap $ \rm -> do
-      let app = App
-            { appLogFunc = lf
-            , appProcessContext = pc
-            , appResourceMap = rm
-            }
-      runRIO app $ main2 args
+    stack <- liftIO $ getAppUserDataDirectory "stack"
+    root <- liftIO $ parseAbsDir $ stack </> "pantry"
+    withLogFunc lo $ \lf -> withResourceMap $ \rm ->
+      runRIO lf $
+      withPantryConfig
+        root
+        defaultHackageSecurityConfig
+        HpackBundled
+        8
+        $ \pantryConfig -> do
+            let app = App
+                  { appLogFunc = lf
+                  , appProcessContext = pc
+                  , appResourceMap = rm
+                  , appPantryConfig = pantryConfig
+                  }
+            runRIO app $ main2 args
 
 main2 :: Args -> RIO App ()
 main2 Args {..} = do
@@ -152,8 +149,8 @@
         Nothing -> do
             logInfo "\nNo version bumps required, good to go!"
             when toTag $ forM_ (maybe [] Map.keys $ Map.lookup DoesNotExist m)
-              $ \(Package _fp (PackageName name) (Version version)) ->
-                let ident = T.unpack $ mconcat [name, "-", version]
+              $ \(Package _fp name version) ->
+                let ident = packageIdentifierString $ PackageIdentifier name version
                     msg = "Release: " <> ident
                  in proc "git" ["tag", "-s", ident, "-m", msg] $ \pc -> do
                       logInfo $ display pc
@@ -178,70 +175,9 @@
    -> RIO App (Map Status (Map Package (Maybe Diff)))
 go getDiffs fp = do
     package <- parsePackage fp
-    localFileHackage <- getHackageFile package
-    localFileExists <- doesFileExist localFileHackage
-    let handleFile :: RIO App (Status, Maybe Diff)
-        handleFile = do
-            let v = packageVersion package
-            (isDiff, mdiff) <- compareTGZ getDiffs (packageName package) localFileHackage v fp v
-            return $ if isDiff then (NeedsVersionBump, mdiff) else (NoChanges, Nothing)
-    (status, mdiff) <-
-        if localFileExists
-            then handleFile
-            else do
-              reqH <- getUrlHackage package
-              httpSink reqH $ \resH -> do
-                case () of
-                  ()
-                    | getResponseStatusCode resH `elem` [403, 404] -> do
-                        mdiff <-
-                          if getDiffs
-                            then do
-                                mlatest <- lift $ getLatestVersion $ packageName package
-                                case mlatest of
-                                  Nothing -> return Nothing
-                                  Just (latest, latestv) -> do
-                                    (isDiff, mdiff) <- lift $ compareTGZ getDiffs (packageName package) latest latestv fp (packageVersion package)
-                                    return $ if isDiff then mdiff else Nothing
-                            else return Nothing
-                        return (DoesNotExist, mdiff)
-                    | getResponseStatusCode resH == 403 -> return (DoesNotExist, Nothing)
-                    | getResponseStatusCode resH == 200 -> do
-                        createDirectoryIfMissing True $ takeDirectory localFileHackage
-                        sinkFileCautious localFileHackage
-                        lift handleFile
-                    | otherwise -> error $ "Invalid status code: " ++ show (getResponseStatus resH)
+    (status, mdiff) <- compareTGZ getDiffs (packageName package) (packageVersion package) fp
     return $ Map.singleton status $ Map.singleton package mdiff
 
--- | Get the filepath for the latest version of a package from
--- Hackage, if it exists at all.
-getLatestVersion :: PackageName -> RIO App (Maybe (FilePath, Version))
-getLatestVersion name = do
-    stack <- getAppUserDataDirectory "stack"
-    let indexTar = stack </> "indices" </> "Hackage" </> "00-index.tar"
-    mversion <- runConduitRes
-        $ sourceFile indexTar
-       .| untarChunks
-       .| withEntries yield
-       .| foldMapC (parseVersionNumber name)
-    case mversion of
-        Option Nothing -> return Nothing
-        Option (Just (Max version)) -> do
-            let p = Package "" name $ toTextVersion version
-            fp <- getHackageFile p
-            req <- getUrlHackage p
-            httpSink req $ \res ->
-              if getResponseStatusCode res == 200
-                then do
-                  createDirectoryIfMissing True $ takeDirectory fp
-                  sinkFileCautious fp
-                  return $ Just (fp, toTextVersion version)
-                else error $ "Could not download from Hackage: " ++ show p
-
-newtype PackageName = PackageName { unPackageName :: Text }
-    deriving (Show, Eq, Ord)
-newtype Version = Version { unVersion :: Text }
-    deriving (Show, Eq, Ord)
 data Package = Package
     { packageFile :: !FilePath
     , packageName :: !PackageName
@@ -249,108 +185,69 @@
     }
     deriving (Show, Eq, Ord)
 
-toTextVersion :: Data.Version.Version -> Version
-toTextVersion = Version . T.pack . Data.Version.showVersion
-
-parseVersionNumber :: PackageName
-                   -- ^ target package we care about
-                   -> Tar.Header
-                   -> Option (Max Data.Version.Version)
-parseVersionNumber pn header = Option $ fmap Max $ do
-    [name, version, dotcabal] <- Just $ splitOn "/" $ T.pack fp
-    guard $ PackageName name == pn
-    guard $ name <> ".cabal" == dotcabal
-    listToMaybe $ map fst
-                $ filter (null . snd)
-                $ readP_to_S Data.Version.parseVersion
-                $ T.unpack version
-  where
-    fp = headerFilePath header
-
 parsePackage :: MonadThrow m => FilePath -> m Package
 parsePackage fp =
     case T.stripSuffix ".tar.gz" $ T.pack $ takeFileName fp of
         Nothing -> error $ "Does not end with .tar.gz: " ++ fp
-        Just s -> do
-            let s' = T.reverse s
-                (b', a') = T.break (== '-') s'
-                a = T.reverse $ T.drop 1 a'
-                b = T.reverse b'
-            return $ Package fp (PackageName a) (Version b)
+        Just s ->
+            case parsePackageIdentifier $ T.unpack s of
+                Nothing -> error $ "Invalid package identifier: " ++ T.unpack s
+                Just (PackageIdentifier name version) -> pure $ Package fp name version
 
 sayPackage :: Package -> RIO App ()
-sayPackage (Package _ (PackageName name) (Version version)) =
-  logInfo $ display name <> "-" <> display version
-
-getHackageFile :: MonadIO m => Package -> m FilePath
-getHackageFile (Package _fp (PackageName a') (Version b')) = do
-    stack <- getAppUserDataDirectory "stack"
-    return $ stack </> "indices" </> "Hackage" </> "packages" </> a </> b </>
-                concat [a, "-", b, ".tar.gz"]
-  where
-    a = T.unpack a'
-    b = T.unpack b'
+sayPackage (Package _ name version) =
+  logInfo $ fromString $ packageIdentifierString $ PackageIdentifier name version
 
 compareTGZ :: Bool -- ^ get diffs?
            -> PackageName
-           -> FilePath
-           -- ^ old tarball
            -> Version
-           -- ^ old version
+           -- ^ version
            -> FilePath
            -- ^ new tarball
-           -> Version
-           -- ^ new version
-           -> RIO App (Bool, Maybe Diff)
-compareTGZ getDiffs pn a av b bv = do
-    a' <- getContents a
-    b' <- getContents b
-    let isDiff = a' /= b'
-    mdiff <-
-        if getDiffs && isDiff
-            then withSystemTempDirectory "diff" $ \diff -> do
-                let fill dir x = forM_ (Map.toList x) $ \(fp, bs) -> do
-                      let fp' = dir </> fp
-                      createDirectoryIfMissing True $ takeDirectory fp'
-                      BL.writeFile fp' bs
-                fill (diff </> "old") a'
-                fill (diff </> "new") b'
-                let toNV v = T.unpack $ mconcat
-                        [ unPackageName pn
-                        , "-"
-                        , unVersion v
-                        ]
-                (_, out) <- withWorkingDir diff $ proc "diff"
-                    [ "-ruN"
-                    , "old" </> toNV av
-                    , "new" </> toNV bv
-                    ]
-                    readProcessStdout
-                return $ Just out
-            else return Nothing
-    return (a' /= b', mdiff)
-  where
-    getContents :: FilePath -> RIO App (Map FilePath LByteString)
-    getContents fp = handleAny (onErr fp) $ runConduitRes
-         $ sourceFile fp
-        .| ungzip
-        .| untarChunks
-        .| withEntries addEntry
-        .| foldC
+           -> RIO App (Status, Maybe Diff)
+compareTGZ getDiffs pn v b = withSystemTempDirectory "diff" $ \diff -> do
+    oldDest <- parseAbsDir $ diff </> "old"
+    newDest <- parseAbsDir $ diff </> "new"
+    bAbs <- resolveFile' b
 
-    onErr fp e = do
-      logInfo $
-        "Error opening tarball: " <>
-        fromString fp <>
-        ", " <>
-        display e
-      return mempty
+    let pirOrig = PackageIdentifierRevision pn v (CFIRevision (Revision 0))
+    let mkPli pir = RPLIHackage pir Nothing
+    exists <- (True <$ unpackPackageLocationRaw oldDest (mkPli pirOrig)) `catch` \e ->
+      case e of
+        UnknownHackagePackage{} -> do
+          mpir <- getLatestHackageVersion YesRequireHackageIndex pn UsePreferredVersions
+          for_ mpir $ unpackPackageLocationRaw oldDest . mkPli
+          pure False
+        _ -> throwIO e
 
-    addEntry header
-        | headerFileType header == FTNormal = do
-            lbs <- sinkLazy
-            yield $ Map.singleton (headerFilePath header) lbs
-        | otherwise = return ()
+    unpackPackageLocationRaw newDest $ RPLIArchive
+      RawArchive
+        { raLocation = ALFilePath $ ResolvedPath (RelFilePath $ fromString b) bAbs
+        , raHash = Nothing
+        , raSize = Nothing
+        , raSubdir = ""
+        }
+      RawPackageMetadata
+        { rpmName = Nothing
+        , rpmVersion = Nothing
+        , rpmCabal = Nothing
+        , rpmTreeKey = Nothing
+        }
+
+    (ec, out) <- withWorkingDir diff $ proc "diff"
+      [ "-ruNw"
+      , "old"
+      , "new"
+      ]
+      readProcessStdout
+    let status
+          | ec == ExitSuccess = NoChanges
+          | exists = NeedsVersionBump
+          | otherwise = DoesNotExist
+        mdiff
+          | getDiffs && ec /= ExitSuccess = Just out
+          | otherwise = Nothing
+    return (status, mdiff)
 
 colorize :: LByteString -> LByteString
 colorize =
