{-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ExtendedDefaultRules #-} {-# LANGUAGE NoImplicitPrelude #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} module Main where import qualified Data.ByteString as B import qualified Data.ByteString.Internal as BI import qualified Data.ByteString.Lazy as BL import qualified Data.ByteString.Lazy.Internal as BLI import Data.Git import Data.Text.Lazy (Text) import qualified Data.Text.Lazy as T import Filesystem (isDirectory, removeTree) import Foreign.ForeignPtr import Foreign.Ptr import GHC.Conc import Prelude.General import Shelly import Subversion.Dump import System.Console.CmdArgs import System.Environment import System.Log.Logger default (Text) version :: String version = "0.0.1" copyright :: String copyright = "2012" hSubconvertSummary :: String hSubconvertSummary = "hsubconvert v" ++ version ++ ", (C) John Wiegley " ++ copyright data HSubconvert = HSubconvert { jobs :: Int , verbose :: Bool , debug :: Bool , files :: [String] } deriving (Data, Typeable, Show, Eq) hSubconvert :: HSubconvert hSubconvert = HSubconvert { jobs = def &= name "j" &= typ "INT" &= help "Run INT concurrent jobs (default: 4)" , verbose = def &= name "v" &= help "Report progress verbosely" , debug = def &= name "D" &= help "Report debug information" , files = def &= args } &= summary hSubconvertSummary &= program "hsubconvert" &= help "One-time, faithful conversion of Subversion repositories to Git" withRepository :: Text -> (Repository -> IO ()) -> IO () withRepository n f = do let p = fromText n exists <- isDirectory p when exists $ removeTree p f =<< createRepository p True main :: IO () main = do -- process command-line options mainArgs <- getArgs opts <- withArgs (if null mainArgs then ["--help"] else mainArgs) (cmdArgs hSubconvert) _ <- GHC.Conc.setNumCapabilities $ case jobs opts of 0 -> 4; x -> x when (verbose opts) $ updateGlobalLogger "hsubconvert" (setLevel INFO) when (debug opts) $ updateGlobalLogger "hsubconvert" (setLevel DEBUG) hSetBuffering stdout NoBuffering withRepository "test.git" $ \repo -> do file <- BL.readFile (head (files opts)) let revs = readSvnDump file (c, lastRev) <- foldM (\(co, _) rev -> do when (isJust co) $ reportProgress False (revNumber rev) (fromJust co) co' <- applyRevision repo co rev return (co', rev)) (Nothing, head revs) revs case c of Nothing -> putStrLn "No revisions were converted!" Just c' -> do reportProgress True (revNumber lastRev) c' cid <- objectId c' writeRef_ $ createRef "refs/heads/master" (RefTargetId cid) repo writeRef_ $ createRef "HEAD" (RefTargetSymbolic "refs/heads/master") repo putStr "\nConversion completed\n" where reportProgress :: Updatable a => Bool -> Int -> a -> IO () reportProgress force num obj | force || num < 10 = showProgress num obj | num < 100 = when (num `mod` 10 == 0) $ showProgress num obj | num < 1000 = when (num `mod` 100 == 0) $ showProgress num obj | otherwise = when (num `mod` 1000 == 0) $ showProgress num obj showProgress :: Updatable a => Int -> a -> IO () showProgress num _ = do putStr $ "Converting " ++ show num ++ "...\r" -- void (forkOS (update_ obj)) applyRevision :: Repository -> Maybe Commit -> Revision -> IO (Maybe Commit) applyRevision repo c rev = do case foldl' (\co op -> co `seq` foldOp co op) (Left (makeCommit c)) (revOperations rev) of Left _ -> return c Right x -> Just <$> x where makeCommit co = let nco = case co of Nothing -> createCommit repo Just p -> commitTree .~ p^.commitTree $ commitParents .~ [ObjRef p] $ createCommit repo sig = Signature { _signatureName = fromMaybe "Unknown" (revAuthor rev) , _signatureEmail = "unknown@unknown.org" , _signatureWhen = revDate rev } in commitAuthor .~ sig $ commitCommitter .~ sig $ commitLog .~ fromMaybe "" (revComment rev) $ nco foldOp :: Either Commit (IO Commit) -> Operation -> Either Commit (IO Commit) foldOp c' op = case opAction op of Add -> case opKind op of Directory -> c' File -> addFile op c' Change -> c' Replace -> c' Delete -> c' addFile op c' | BL.null (opContents op) = c' | otherwise = Right (case c' of Left f -> return f; Right x -> x >>= updateCommit (opFilePath op) (wrapBlob op repo) >>= update) opFilePath :: Operation -> FilePath opFilePath = fromText . T.pack . opPathname opBlob :: Operation -> Repository -> Blob opBlob op = createBlob (lazyToStrict (opContents op)) wrapBlob :: Operation -> Repository -> TreeEntry wrapBlob op repo = BlobEntry (ObjRef (opBlob op repo)) False lazyToStrict :: BL.ByteString -> B.ByteString lazyToStrict lb = BI.unsafeCreate len $ go lb where len = BLI.foldlChunks (\l sb -> l + B.length sb) 0 lb go BLI.Empty _ = return () go (BLI.Chunk (BI.PS fp s l) r) ptr = withForeignPtr fp $ \p -> do BI.memcpy ptr (p `plusPtr` s) (fromIntegral l) go r (ptr `plusPtr` l) -- Main.hs (hsubconvert) ends here