svndump (empty) → 0.1.0
raw patch · 9 files changed
+380/−0 lines, 9 filesdep +basedep +bytestringdep +filepathsetup-changed
Dependencies added: base, bytestring, filepath, parsec, svndump, text, zlib
Files
- .gitignore +1/−0
- LICENSE +28/−0
- README.md +1/−0
- Setup.hs +8/−0
- src/Subversion.hs +5/−0
- src/Subversion/Dump.hs +223/−0
- svndump.cabal +72/−0
- test/test-cooked.hs +21/−0
- test/test-raw.hs +21/−0
+ .gitignore view
@@ -0,0 +1,1 @@+/dist/
+ LICENSE view
@@ -0,0 +1,28 @@+Copyright (c) 2003-2009, John Wiegley. All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are+met:++- Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++- Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++- Neither the name of New Artisans LLC nor the names of its+ contributors may be used to endorse or promote products derived from+ this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,1 @@+A Haskell library for reading Subversion dump files.
+ Setup.hs view
@@ -0,0 +1,8 @@+#!/usr/bin/runhaskell++module Main (main) where++import Distribution.Simple++main :: IO ()+main = defaultMain
+ src/Subversion.hs view
@@ -0,0 +1,5 @@+module Subversion+ ( module Subversion.Dump+ ) where++import Subversion.Dump
+ src/Subversion/Dump.hs view
@@ -0,0 +1,223 @@+{-# LANGUAGE OverloadedStrings #-}++module Subversion.Dump+ ( RevDate+ , Revision(..)++ , OpKind(..)+ , OpAction(..)+ , Operation(..)++ , FieldMap+ , Entry(..)++ , readSvnDumpRaw+ , readSvnDump+ ) where++{-| This is a parser for Subversion dump files. The objective is to convert a+dump file into a series of data structures representing that same information.+It uses `Data.ByteString.Lazy` to reading the file, and `Data.Text` to+represent text fields which may contain Unicode characters. -}++--import Debug.Trace+import Control.Applicative hiding (many, (<|>))+import Control.Monad+import qualified Data.ByteString.Lazy as B+--import qualified Data.ByteString.Lazy.Char8 as BC+import qualified Data.List as L+--import qualified Data.Map as M+import Data.Maybe+import Data.Text.Lazy hiding (map, count)+import Data.Text.Lazy.Encoding as E+import System.FilePath+import Text.Parsec+import Text.Parsec.ByteString.Lazy as PB++import Prelude hiding (getContents)++default (Data.Text.Lazy.Text)++{- At the topmost level, a dump file is simple an in-order, linear list of+revisions, where each revisions consist of a series of "operation nodes" that+represent the changes made by that revision to the repository. The author+name and revision comment are decoded from UTF8. -}++type RevDate = Text++data Revision = Revision { revNumber :: Int+ , revDate :: RevDate+ , revAuthor :: Maybe Text+ , revComment :: Maybe Text+ , revOperations :: [Operation] }+ deriving Show++{- Each node reflects the changes to a single file. Note that branches don't+need to be considered separately, since in Subversion, all files are stored+within a single filesystem. Branches are something the user applies "after+the fact" by using specially named paths, such as "foo/branches". The file's+contents are not decoded, as we have no way of knowing what the intended+encoding should be -- or even if there is in, in the case of binary files.++`opContentLength` is provided as a separate member to avoid reading in the+full contents of the operation solely to determine its length. This way, you+can inspect the length while deferring the content read if you don't need+it. -}++data OpKind = File | Directory deriving (Show, Enum, Eq)+data OpAction = Add | Change | Replace | Delete deriving (Show, Enum, Eq)++data Operation = Operation { opKind :: OpKind+ , opAction :: OpAction+ , opPathname :: FilePath+ , opContents :: B.ByteString+ , opContentLength :: Int+ , opChecksumMD5 :: Maybe String+ , opChecksumSHA1 :: Maybe String+ , opCopyFromRev :: Maybe Int+ , opCopyFromPath :: Maybe FilePath }+ deriving Show++{- A further note is needed on `opCopyFromRev` and `opCopyFromPath`, since these+two represent the only real complexity in a dump file. Basically what they+say is that there is no `opContents` record for this `Operation`. Rather, the+contents to be taken from another file in a past revision. Since this+historical information would be expensive to maintain, `Operation` only+provides the data given by the dump file, and it is left as an analytical pass+on this data to build the structures necessary to figure out what those+contents would have been.++So, with our structures defined, we're ready to read in the file. Since we+don't know what each element will be yet (revisions are interspersed with+nodes), we read them first into the much more general Node structure. -}++{-| Reads a dump file from a ByteString in the IO monad into a list of+ Revision values. This is the "cooked" parallel of `readSvnDumpRaw`. -}+readSvnDump :: B.ByteString -> IO (Either ParseError [Revision])+readSvnDump io = do+ result <- readSvnDumpRaw io+ return $ map processRevs <$> (L.groupBy sameRev <$> result)++ where sameRev _ y = isNothing $+ L.lookup "Revision-number" (entryTags y)+ getField f n x = L.lookup n (f x)+ getField' f n x = fromMaybe "" (getField f n x)+ tagM = getField entryTags+ propM = getField entryProps+ tag = getField' entryTags+ prop = getField' entryProps++ processRevs [] = error "Unexpected"+ processRevs (rev:ops) =+ Revision {+ revNumber = read $ tag "Revision-number" rev+ , revDate = parseDate $ prop "svn:date" rev+ , revAuthor = propM "svn:author" rev+ , revComment = propM "svn:log" rev+ , revOperations = map processOp ops }++ processOp op =+ Operation {+ opKind = getOpKind $ tag "Node-kind" op+ , opAction = getOpAction $ tag "Node-action" op+ , opPathname = tag "Node-path" op+ , opContents = entryBody op+ , opContentLength = read $ tag "Text-content-length" op+ , opCopyFromRev = read <$>+ tagM "Node-copyfrom-rev" op+ , opCopyFromPath = tagM "Node-copyfrom-path" op+ , opChecksumMD5 = tagM "Text-content-md5" op+ , opChecksumSHA1 = tagM "Text-content-sha1" op }++ getOpKind kind = case kind of+ "file" -> File+ "dir" -> Directory+ _ -> error "Unexpected"++ getOpAction kind = case kind of+ "add" -> Add+ "delete" -> Delete+ "change" -> Change+ "replace" -> Replace+ _ -> error "Unexpected"++type FieldMap a = [(String, a)]++data Entry = Entry { entryTags :: FieldMap String+ , entryProps :: FieldMap Text+ , entryBody :: B.ByteString }+ deriving Show++readSvnDumpRaw :: B.ByteString -> IO (Either ParseError [Entry])+readSvnDumpRaw dump = return $ parse parseSvnDump "" dump++{- These are the Parsec parsers for the various parts of the input file. -}++parseTag :: PB.Parser (String, String)+parseTag = (,) <$> fieldKey <* char ':' <* space+ <*> fieldValue <* newline+ where+ fieldKey = (:) <$> letter <*> many fieldChar+ fieldChar = letter <|> digit <|> oneOf "-_"+ fieldValue = many1 (noneOf "\n")++parseIndicator :: PB.Parser (Char, Integer)+parseIndicator = (,) <$> oneOf "KV" <* space+ <*> (read <$> many1 digit <* newline)++readTextRange :: Integer -> PB.Parser B.ByteString+readTextRange len = do+ input <- getInput+ let value = B.take (fromIntegral len) input+ setInput $ B.drop (fromIntegral len) input+ return value++--readTextRange' :: Integer -> PB.Parser B.ByteString+--readTextRange' len = BC.pack <$> count (fromIntegral len) anyChar++parseSpecValue :: Char -> PB.Parser Text+parseSpecValue expected = do+ (kind, len) <- parseIndicator+ when (kind /= expected) $ unexpected "Unexpected spec value char"+ value <- readTextRange len+ --trace ("Value: " ++ (show value)) $ return ()+ _ <- newline+ return $ E.decodeUtf8 value++parseProperty :: PB.Parser (String, Text)+parseProperty = (,) <$> (unpack <$> parseSpecValue 'K')+ <*> parseSpecValue 'V'++parseEntry :: PB.Parser Entry+parseEntry = do+ fields <- many1 parseTag <* newline++ props <- case L.lookup "Prop-content-length" fields of+ Nothing -> return []+ Just _ -> many parseProperty <* string "PROPS-END\n"++ body <- case L.lookup "Text-content-length" fields of+ Nothing -> return B.empty+ Just len -> readTextRange (read len)++ _ <- many newline <?> "entry-terminating newline"++ return Entry { entryTags = fields+ , entryProps = props+ , entryBody = body }++parseHeader :: PB.Parser ()+parseHeader = do+ _ <- string "SVN-fs-dump-format-version: 2\n\n"+ <?> "Dump file starts without a recognizable tag"+ _ <- string "UUID: " <* many1 (hexDigit <|> char '-')+ <* newline <* newline+ return ()++parseSvnDump :: PB.Parser [Entry]+parseSvnDump = parseHeader >> many parseEntry++parseDate :: Text -> RevDate+parseDate = id++-- SvnDump.hs ends here
+ svndump.cabal view
@@ -0,0 +1,72 @@+name: svndump+category: Subversion+version: 0.1.0+license: BSD3+cabal-version: >= 1.8+license-file: LICENSE+author: John Wiegley+maintainer: John Wiegley <johnw@newartisans.com>+stability: provisional+homepage: http://github.com/jwiegley/svndump/+bug-reports: http://github.com/jwiegley/svndump/issues+copyright: Copyright (C) 2012 John Wiegley+synopsis: Library for reading Subversion dump files+description:+ A library for parsing Subversion dump files. The objective is to convert a+ dump file into a series of data structures representing that same+ information. It uses `Data.ByteString.Lazy` to reading the file, and+ `Data.Text` to represent text fields which may contain Unicode characters.++build-type: Simple+tested-with: GHC == 7.4.2+extra-source-files:+ .gitignore+ LICENSE+ README.md++source-repository head+ type: git+ location: git://github.com/jwiegley/svndump.git++library+ build-depends:+ base >= 4.3 && < 5,+ parsec >= 3.1.3,+ filepath >= 1.3,+ bytestring >= 0.9 && < 0.10,+ text >= 0.11 && < 0.12++ exposed-modules:+ Subversion+ Subversion.Dump++ ghc-options: -Wall -fwarn-tabs -O2+ hs-source-dirs: src++-- Test the raw dump file parser+test-suite test-raw+ type: exitcode-stdio-1.0+ main-is: test-raw.hs++ build-depends:+ base >= 4.3 && < 5,+ bytestring >= 0.9 && < 0.10,+ zlib >= 0.5 && < 0.6,+ svndump++ ghc-options: -Wall -Werror -O2+ hs-source-dirs: test++-- Test the raw dump file parser+test-suite test-cooked+ type: exitcode-stdio-1.0+ main-is: test-cooked.hs++ build-depends:+ base >= 4.3 && < 5,+ bytestring >= 0.9 && < 0.10,+ zlib >= 0.5 && < 0.6,+ svndump++ ghc-options: -Wall -Werror -O2+ hs-source-dirs: test
+ test/test-cooked.hs view
@@ -0,0 +1,21 @@+module Main where++import qualified Codec.Compression.GZip as GZip+import qualified Data.ByteString.Lazy as B+import Subversion.Dump+import System.Exit++main :: IO ()+main = do+ file <- B.readFile "data/cunit.dump.gz"+ dump <- readSvnDump $ GZip.decompress file+ case dump of+ Left _ -> exitFailure+ Right xs -> do+ let len = length xs+ putStrLn $ show len ++ " cooked entries found, expecting 157"+ if len == 157+ then exitSuccess+ else exitFailure++-- raw-parser.hs ends here
+ test/test-raw.hs view
@@ -0,0 +1,21 @@+module Main where++import qualified Codec.Compression.GZip as GZip+import qualified Data.ByteString.Lazy as B+import Subversion.Dump+import System.Exit++main :: IO ()+main = do+ file <- B.readFile "data/cunit.dump.gz"+ dump <- readSvnDumpRaw $ GZip.decompress file+ case dump of+ Left _ -> exitFailure+ Right xs -> do+ let len = length xs+ putStrLn $ show len ++ " raw entries found, expecting 1950"+ if len == 1950+ then exitSuccess+ else exitFailure++-- raw-parser.hs ends here