Work on git pack protocol, not done yet
This is a lot of code, better save now than sorry later when something gets deleted by mistake. Either way, the code will move later - once tested and organized properly - into its own package.
This commit is contained in:
parent
f7025f9c15
commit
7b9f6e9714
20 changed files with 1080 additions and 13 deletions
90
src/Data/Binary/Get/Local.hs
Normal file
90
src/Data/Binary/Get/Local.hs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Data.Binary.Get.Local
|
||||||
|
( getHexDigit
|
||||||
|
, getHex16
|
||||||
|
, getDecimal
|
||||||
|
--TODO i added more functions below, didnt export yet
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
-- | Read an ASCII character representing a hexadecimal digit, and convert to
|
||||||
|
-- the integral value of the digit (i.e. a number between 0 and 15).
|
||||||
|
getHexDigit :: Get Word8
|
||||||
|
getHexDigit =
|
||||||
|
let fromHex w
|
||||||
|
| 48 <= w && w <= 57 = return $ w - 48 -- 0-9
|
||||||
|
| 65 <= w && w <= 70 = return $ w - 55 -- A-F
|
||||||
|
| 97 <= w && w <= 102 = return $ w - 87 -- a-f
|
||||||
|
| otherwise = fail "Not an ASCII hex digit"
|
||||||
|
in getWord8 >>= fromHex
|
||||||
|
|
||||||
|
-- | Efficienty convert 'Word8' to 'Int'.
|
||||||
|
toInt :: Word8 -> Int
|
||||||
|
toInt w =
|
||||||
|
fromMaybe (error "Huh? Converting Word8 to Int failed!") $
|
||||||
|
toIntegralSized w
|
||||||
|
|
||||||
|
-- | Read 4 ASCII hex digits and parse them as a hex string into the integer it
|
||||||
|
-- represents. Since each hex digit is 4 bits, 4 such digits form a 16-bit
|
||||||
|
-- integer (but this function reads 4 bytes which are 32 bits).
|
||||||
|
--
|
||||||
|
-- The resulting 16-bit integer is returned as an 'Int' because it is used
|
||||||
|
-- below with a function which takes an 'Int' parameter.
|
||||||
|
getHex16 :: Get Int
|
||||||
|
getHex16 = do
|
||||||
|
let sl n = unsafeShiftL n . toInt
|
||||||
|
hh <- sl 12 <$> getHexDigit
|
||||||
|
h <- sl 8 <$> getHexDigit
|
||||||
|
l <- sl 4 <$> getHexDigit
|
||||||
|
ll <- toInt <$> getHexDigit
|
||||||
|
return $ hh .&. h .&. l .&. ll
|
||||||
|
|
||||||
|
-- Read a string of given size representing an integer in decimal, and parse
|
||||||
|
-- the integer.
|
||||||
|
getDecimal :: Num a => Int -> Get a
|
||||||
|
getDecimal len = do
|
||||||
|
s <- getByteString len
|
||||||
|
case fromDecimal s
|
||||||
|
Nothing -> fail "s doesn't represent a decimal integer"
|
||||||
|
Just n -> return n
|
||||||
|
|
||||||
|
-- | Get a word which satisfies the predicate, otherwise fail.
|
||||||
|
requireWord8 :: (Word8 -> Bool) -> Get Word8
|
||||||
|
requireWord8 p = do
|
||||||
|
w <- getWord8
|
||||||
|
if p w
|
||||||
|
then return w
|
||||||
|
else fail "Word doesn't satisfy predicate"
|
||||||
|
|
||||||
|
requireSpace :: Get ()
|
||||||
|
requireSpace = void $ requireWord8 (== 32)
|
||||||
|
|
||||||
|
attemptByteString :: ByteString -> Get Bool
|
||||||
|
attemptByteString s = fmap isJust . lookAheadM $ do
|
||||||
|
b <- getByteString $ length s
|
||||||
|
if b == s
|
||||||
|
then return $ Just b
|
||||||
|
else return Nothing
|
||||||
|
|
||||||
|
-- | Read a bytestring of the same length as the parameter, and fail if they
|
||||||
|
-- aren't equal. If equal, return the given value.
|
||||||
|
requireByteString :: ByteString -> a -> Get a
|
||||||
|
requireByteString s v = fmap isJust $ lookAheadM $ do
|
||||||
|
b <- getByteString $ length s
|
||||||
|
if b == s
|
||||||
|
then return v
|
||||||
|
else fail "Didn't get the expected bytestring"
|
62
src/Data/Binary/Put/Local.hs
Normal file
62
src/Data/Binary/Put/Local.hs
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Data.Binary.Put.Local
|
||||||
|
( putNull
|
||||||
|
, putLF
|
||||||
|
, putSpace
|
||||||
|
, putHexDigit
|
||||||
|
, putHex16
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
putNull :: Put
|
||||||
|
putNull = putWord8 0
|
||||||
|
|
||||||
|
putLF :: Put
|
||||||
|
putLF = putWord8 10
|
||||||
|
|
||||||
|
putSpace :: Put
|
||||||
|
putSpace = putWord8 32
|
||||||
|
|
||||||
|
-- | Efficiently convert an 'Int' between 0 and 127 to 'Word8'.
|
||||||
|
toWord8 :: Int -> Word8
|
||||||
|
toWord8 i =
|
||||||
|
fromMaybe (error "Converting Int to Word8 failed") $
|
||||||
|
toIntegralSized i
|
||||||
|
|
||||||
|
-- | Take an integral value of a hex digit (i.e. between 0 and 15). Put the
|
||||||
|
-- ASCII character representing the digit in lowecase hexadecimal.
|
||||||
|
putHexDigit :: Word8 -> Put
|
||||||
|
putHexDigit w
|
||||||
|
| 0 <= w && w <= 9 =
|
||||||
|
| 10 <= w && w <= 15 =
|
||||||
|
| otherwise =
|
||||||
|
|
||||||
|
-- | Takes a number which must be a 16-bit non-negative integer. Generates a
|
||||||
|
-- 4-byte ASCII hexadecimal representation of the number's value and puts it.
|
||||||
|
putHex16 :: Int -> Put
|
||||||
|
putHex16 n =
|
||||||
|
let (rem1, ll) = n `divMod` 16
|
||||||
|
(rem2, l) = rem1 `divMod` 16
|
||||||
|
(rem3, h) = rem2 `divMod` 16
|
||||||
|
(rem4, hh) = rem3 `divMod` 16
|
||||||
|
in if rem4 /= 0
|
||||||
|
then fail "Hex integer to put is too large, must be 16 bit"
|
||||||
|
else do
|
||||||
|
putHexDigit $ toWord8 hh
|
||||||
|
putHexDigit $ toWord8 h
|
||||||
|
putHexDigit $ toWord8 l
|
||||||
|
putHexDigit $ toWord8 ll
|
40
src/Data/ByteString/Local.hs
Normal file
40
src/Data/ByteString/Local.hs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Data.ByteString.Local
|
||||||
|
( fromDecimal
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Data.ByteString (ByteString)
|
||||||
|
|
||||||
|
import qualified Data.ByteString as B
|
||||||
|
|
||||||
|
-- | Given an ASCII string representing an integer in decimal, parse it and
|
||||||
|
-- return the number. Return 'Nothing' on invalid digit chars and on an empty
|
||||||
|
-- bytestring.
|
||||||
|
--
|
||||||
|
-- >>> fromDecimal "345"
|
||||||
|
-- Just 345
|
||||||
|
--
|
||||||
|
-- >>> fromDecimal "a1b2c3"
|
||||||
|
-- Nothing
|
||||||
|
fromDecimal :: Num a => ByteString -> Maybe a
|
||||||
|
fromDecimal s =
|
||||||
|
if (not . B.null) s && B.all (\ b -> 48 <= b && b <= 57) s
|
||||||
|
then Just $ B.foldl' (\ n b -> 10 * n + b - 48) 0 s
|
||||||
|
else Nothing
|
|
@ -15,7 +15,10 @@
|
||||||
|
|
||||||
-- | Git repo tools using the @hit@ package.
|
-- | Git repo tools using the @hit@ package.
|
||||||
module Data.Git.Local
|
module Data.Git.Local
|
||||||
( loadCommits
|
( resolveNameMaybe
|
||||||
|
, resolveName
|
||||||
|
, listReferences
|
||||||
|
, loadCommits
|
||||||
, NodeLabel
|
, NodeLabel
|
||||||
, EdgeLabel
|
, EdgeLabel
|
||||||
, CommitGraph
|
, CommitGraph
|
||||||
|
@ -32,21 +35,45 @@ import Prelude
|
||||||
|
|
||||||
import Control.Monad.IO.Class
|
import Control.Monad.IO.Class
|
||||||
import Data.Foldable (foldl', foldlM)
|
import Data.Foldable (foldl', foldlM)
|
||||||
|
import Data.Git.Named (RefName (..))
|
||||||
import Data.Git.Ref (Ref, toBinary)
|
import Data.Git.Ref (Ref, toBinary)
|
||||||
import Data.Git.Repository (getCommit, resolveRevision)
|
import Data.Git.Repository (getCommit, resolveRevision, branchList, tagList)
|
||||||
import Data.Git.Revision (Revision (..))
|
import Data.Git.Revision (Revision (..))
|
||||||
import Data.Git.Storage (Git)
|
import Data.Git.Storage (Git)
|
||||||
import Data.Git.Types (Commit (..))
|
import Data.Git.Types (Commit (..))
|
||||||
import Data.Graph.Inductive.Graph (Graph (mkGraph), Node)
|
import Data.Graph.Inductive.Graph (Graph (mkGraph), Node)
|
||||||
import Data.Graph.Inductive.PatriciaTree (Gr)
|
import Data.Graph.Inductive.PatriciaTree (Gr)
|
||||||
import Data.Hashable (Hashable (..))
|
import Data.Hashable (Hashable (..))
|
||||||
|
import Data.Maybe (catMaybes, fromMaybe)
|
||||||
import Data.Ord (Down (..))
|
import Data.Ord (Down (..))
|
||||||
|
|
||||||
import qualified Data.DList as D
|
import qualified Data.DList as D
|
||||||
import qualified Data.HashMap.Strict as M
|
import qualified Data.HashMap.Strict as M
|
||||||
|
import qualified Data.Set as S
|
||||||
|
|
||||||
import Data.Graph.Inductive.Local
|
import Data.Graph.Inductive.Local
|
||||||
|
|
||||||
|
-- | For a given ref name - HEAD or branch or tag - determine its ref hash.
|
||||||
|
resolveNameMaybe :: Git -> String -> IO (Maybe Ref)
|
||||||
|
resolveNameMaybe git name = resolveRevision git $ Revision name []
|
||||||
|
|
||||||
|
-- | For a given ref name - HEAD or branch or tag - determine its ref hash.
|
||||||
|
resolveName :: Git -> String -> IO Ref
|
||||||
|
resolveName git name = do
|
||||||
|
mref <- resolveNameMaybe git name
|
||||||
|
return $ fromMaybe (error "No such ref name in the repo") mref
|
||||||
|
|
||||||
|
-- | List the available references in a git repo, sorted by ref name. The list
|
||||||
|
-- includes HEAD, branches and tags.
|
||||||
|
listReferences :: Git -> IO [(Ref, String)]
|
||||||
|
listReferences git = do
|
||||||
|
branches <- S.mapMonotonic refNameRaw <$> branchList git
|
||||||
|
tags <- S.mapMonotonic refNameRaw <$> tagList git
|
||||||
|
let names = S.toAscList $ S.insert "HEAD" $ S.union branches tags
|
||||||
|
mentries <-
|
||||||
|
traverse (\ name -> fmap (,name) <$> resolveNameMaybe git name) names
|
||||||
|
return $ catMaybes mentries
|
||||||
|
|
||||||
instance Hashable Ref where
|
instance Hashable Ref where
|
||||||
hashWithSalt salt = hashWithSalt salt . toBinary
|
hashWithSalt salt = hashWithSalt salt . toBinary
|
||||||
hash = hash . toBinary
|
hash = hash . toBinary
|
||||||
|
|
308
src/GitPackProto.hs
Normal file
308
src/GitPackProto.hs
Normal file
|
@ -0,0 +1,308 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module GitPackProto
|
||||||
|
( RepoSpec (..)
|
||||||
|
, Action (..)
|
||||||
|
, parseExec
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Control.Applicative ((<|>))
|
||||||
|
import Data.Attoparsec.Text
|
||||||
|
import Data.Binary.Get
|
||||||
|
import Data.Binary.Put
|
||||||
|
import Data.Bits
|
||||||
|
import Data.ByteString (ByteString, unsnoc)
|
||||||
|
import Data.Maybe (fromMaybe)
|
||||||
|
import Data.Text (Text)
|
||||||
|
import Data.Word
|
||||||
|
|
||||||
|
-- What is going on
|
||||||
|
--
|
||||||
|
-- When SSH authentication succeeds, you receive a request from the SSH client
|
||||||
|
-- and need to respond. This module handles the following at the moment:
|
||||||
|
--
|
||||||
|
-- [~] Parse an Execute request using attoparsec into a Vervis action to run
|
||||||
|
-- [ ] Using the binary package, implement the git pack protocol
|
||||||
|
|
||||||
|
--hexdig :: Parser ?
|
||||||
|
--nul :: Parser ?
|
||||||
|
--zeroId :: Parser ?
|
||||||
|
--objId :: Parser ?
|
||||||
|
--hexdig :: Parser ?
|
||||||
|
|
||||||
|
{-data RefName
|
||||||
|
= RefNameHead
|
||||||
|
| RefNamePath [ByteString]
|
||||||
|
|
||||||
|
refname :: Parser ByteString
|
||||||
|
refname =
|
||||||
|
let refnameHead = string "HEAD"
|
||||||
|
refsec =
|
||||||
|
refnameHier = do
|
||||||
|
string "refs/"
|
||||||
|
refsec `sepBy` char '/'
|
||||||
|
in refnameHead <|> refnameHier-}
|
||||||
|
|
||||||
|
data RepoRef = RepoRef Text Text Text
|
||||||
|
|
||||||
|
data RepoSpec
|
||||||
|
= SpecUserProjRepo Text Text Text
|
||||||
|
| SpecProjRepo Text Text
|
||||||
|
| SpecUserRepo Text Text
|
||||||
|
| SpecRepo Text
|
||||||
|
deriving Show
|
||||||
|
|
||||||
|
data Action = UploadPack RepoSpec deriving Show
|
||||||
|
|
||||||
|
repoSpecP :: Parser RepoSpec
|
||||||
|
repoSpecP =
|
||||||
|
SpecRepo <$> msep *> part
|
||||||
|
<|> SpecProjRepo <$> msep *> part <* sep <*> part
|
||||||
|
<|> SpecUserRepo <$> home *> part <* sep <*> part
|
||||||
|
<|> SpecUserProjRepo <$> msh *> part <* sep <*> part <* sep <*> part
|
||||||
|
where
|
||||||
|
part = takeWhile1 $ \ c -> c /= '/' && c /= '\''
|
||||||
|
sep = char '/'
|
||||||
|
msep = optional sep
|
||||||
|
home = char '~'
|
||||||
|
msh = optional $ satisfy $ \ c -> c == '/' || c == '~'
|
||||||
|
|
||||||
|
actionP :: Parser Action
|
||||||
|
actionP = UploadPack <$> ("git-upload-pack '" *> repoSpecP <* char '\'')
|
||||||
|
|
||||||
|
parseExec :: Text -> Either String Action
|
||||||
|
parseExec input = parseOnly (actionP <* endOfInput) input
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- Git pack protocol, using the 'binary' package
|
||||||
|
--
|
||||||
|
-- I /can/ use attoparsec instead. But I'm not sure yet which is better here.
|
||||||
|
-- Since I never used either, I'll just try them, learn and experiment in the
|
||||||
|
-- process, and eventually I'll be able to make an educated decision.
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
data PktLine = DataPkt ByteString | FlushPkt
|
||||||
|
|
||||||
|
getPktLine :: Bool -> Get PktLine
|
||||||
|
getPktLine stripLF = do
|
||||||
|
pktLen <- getHex16
|
||||||
|
if | pktLen == 0 -> return FlushPkt
|
||||||
|
| pktLen > 65524 -> fail "pkt-len is above the maximum allowed"
|
||||||
|
| pktLen <= 4 -> fail "pkt-len is below the possible minimum"
|
||||||
|
| otherwise -> do
|
||||||
|
let len = pktLen - 4
|
||||||
|
payload <- getByteString len
|
||||||
|
case (stripLF, unsnoc payload) of
|
||||||
|
(True, Just (r, 10)) -> return $ DataPkt r
|
||||||
|
_ -> return $ DataPkt payload
|
||||||
|
|
||||||
|
|
||||||
|
putPktLine :: Bool -> PktLine -> Put
|
||||||
|
putPktLine _ FlushPkt = putByteString "0000"
|
||||||
|
putPktLine addLF (DataPkt b) =
|
||||||
|
let len = B.length b + bool 0 1 addLF
|
||||||
|
in if | len == 0 = fail "tried to put an empty pkt-line"
|
||||||
|
| len > 65520 = fail "payload bigger than maximal pkt-len"
|
||||||
|
| otherwise = do
|
||||||
|
putHex16 $ len + 4
|
||||||
|
putByteString b
|
||||||
|
when addLF $ putWord8 10
|
||||||
|
|
||||||
|
data PktLine' a = DataPkt' a | FlushPkt'
|
||||||
|
|
||||||
|
getPktLine' :: (Int -> Get a) -> Get (PktLine' a)
|
||||||
|
getPktLine' getData = do
|
||||||
|
pktLen <- getHex16
|
||||||
|
if | pktLen == 0 -> return FlushPkt
|
||||||
|
| pktLen > 65524 -> fail "pkt-len is above the maximum allowed"
|
||||||
|
| pktLen <= 4 -> fail "pkt-len is below the possible minimum"
|
||||||
|
| otherwise -> do
|
||||||
|
let len = pktLen - 4
|
||||||
|
payload <- isolate len $ getData len
|
||||||
|
return $ DataPkt payload
|
||||||
|
|
||||||
|
putPktLine' :: Bool -> (a -> (Int, Put)) -> PktLine' a -> Put
|
||||||
|
putPktLine' _ _ FlushPkt = putByteString "0000"
|
||||||
|
putPktLine' addLF lenPut (DataPkt payload) =
|
||||||
|
let (len, putPayload) = first (bool id (+ 1) addLF) $ lenPut payload
|
||||||
|
in if | len == 0 = fail "tried to put an empty pkt-line"
|
||||||
|
| len > 65520 = fail "payload bigger than maximal pkt-len"
|
||||||
|
| otherwise = do
|
||||||
|
putHex16 $ len + 4
|
||||||
|
putPayload
|
||||||
|
when addLF $ putWord8 10
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
-- | A typeclass similar to 'Binary', which takes dynamic data lengths into
|
||||||
|
-- account.
|
||||||
|
--
|
||||||
|
-- Putting a value also returns the number of bytes that are being put. This is
|
||||||
|
-- useful for cases where you need to send the size of a data chunk as part of
|
||||||
|
-- the chunk, which is somewhat common in low-level network protocols.
|
||||||
|
--
|
||||||
|
-- In the same manner, getting a value can take a length limit into account.
|
||||||
|
-- For example, if you are parsing a network packet of known size you can (and
|
||||||
|
-- perhaps sometimes you must) use the length to determine how many bytes you
|
||||||
|
-- still need to read. It also needs to return how many bytes it read.
|
||||||
|
class LengthBinary a where
|
||||||
|
lenPut :: a -> PutM Int
|
||||||
|
lenGet :: Int -> Get (Int, a)
|
||||||
|
|
||||||
|
instance LengthBinary a => Binary a where
|
||||||
|
put = void lenPut
|
||||||
|
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
-- Advertize refs
|
||||||
|
-------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
-- steps for parsing last part of the line: take all remaining chars first.
|
||||||
|
-- then remove last LF is present, and operate on the result...
|
||||||
|
|
||||||
|
symRefP :: Parser SymRef
|
||||||
|
symRefP =
|
||||||
|
SymRefHead <$> string "HEAD"
|
||||||
|
<|> SymRefBranch <$> ("refs/heads/" *> takeWhile1
|
||||||
|
|
||||||
|
headBS :: ByteString
|
||||||
|
headBS = "HEAD"
|
||||||
|
|
||||||
|
headLen :: ByteString
|
||||||
|
headLen = B.length headBS
|
||||||
|
|
||||||
|
branchPrefix :: ByteString
|
||||||
|
branchPrefix = "refs/heads/"
|
||||||
|
|
||||||
|
branchPrefixLen :: Int
|
||||||
|
branchPrefixLen = B.length branchPrefix
|
||||||
|
|
||||||
|
tagPrefix :: ByteString
|
||||||
|
tagPrefix = "refs/tags/"
|
||||||
|
|
||||||
|
tagPrefixLen :: Int
|
||||||
|
tagPrefixLen = B.length tagPrefix
|
||||||
|
|
||||||
|
instance SizedBinary SymRef where
|
||||||
|
sizePut SymRefHead = do
|
||||||
|
putByteString headBS
|
||||||
|
return headLen
|
||||||
|
sizePut (SymRefBranch b) = do
|
||||||
|
putByteString branchPrefix
|
||||||
|
putByteString b
|
||||||
|
return $ branchPrefixLen + B.length b
|
||||||
|
sizePut (SymRefTag b) = do
|
||||||
|
putByteString tagPrefix
|
||||||
|
putByteString b
|
||||||
|
return $ tagPrefixLen + B.length b
|
||||||
|
sizeGet lim =
|
||||||
|
let getHead =
|
||||||
|
if lim == headLen
|
||||||
|
then do
|
||||||
|
head <- getByteString headLen
|
||||||
|
if head == headBS
|
||||||
|
then return (lim, SymRefHead)
|
||||||
|
else fail "4-byte symref that isn't HEAD"
|
||||||
|
getBranch =
|
||||||
|
if lim > branchPrefixLen
|
||||||
|
then do
|
||||||
|
prefix <- getByteString branchPrefixLen
|
||||||
|
if prefix == branchPrefix
|
||||||
|
then do
|
||||||
|
name <- getByteString $ lim - branchPrefixLen
|
||||||
|
return (lim, SymRefBranch name)
|
||||||
|
else fail "symref too short to be a branch"
|
||||||
|
getTag =
|
||||||
|
if lim > tagPrefixLen
|
||||||
|
then do
|
||||||
|
prefix <- getByteString tagPrefixLen
|
||||||
|
if prefix == tagPrefix
|
||||||
|
then do
|
||||||
|
name <- getByteString $ lim - tagPrefixLen
|
||||||
|
return (lim, SymRefTag name)
|
||||||
|
else fail "symref too short to be a tag"
|
||||||
|
in getHead <|> getTag <|> getBranch
|
||||||
|
|
||||||
|
newtype ObjId = ObjId Ref
|
||||||
|
|
||||||
|
instance SizedBinary ObjId where
|
||||||
|
sizePut (ObjId ref) = do
|
||||||
|
let hex = toHex ref
|
||||||
|
putByteString hex
|
||||||
|
return $ B.length hex -- should be 40
|
||||||
|
sizeGet lim =
|
||||||
|
if lim >= 40
|
||||||
|
then do
|
||||||
|
hex <- getByteString 40
|
||||||
|
return (40, fromHex hex)
|
||||||
|
else fail "Not enough bytes to read ObjId"
|
||||||
|
|
||||||
|
data RefAd = RefAd
|
||||||
|
{ refAdId :: ObjId
|
||||||
|
, refAdSym :: SymRef
|
||||||
|
, refAdName :: ByteString
|
||||||
|
}
|
||||||
|
|
||||||
|
data Space = Space
|
||||||
|
|
||||||
|
instance SizedBinary Space where
|
||||||
|
sizePut Space = do
|
||||||
|
putWord8 32
|
||||||
|
return 1
|
||||||
|
sizeGet lim =
|
||||||
|
if lim >= 1
|
||||||
|
then do
|
||||||
|
w <- getWord8
|
||||||
|
if w == 32
|
||||||
|
then return (1, Space)
|
||||||
|
else fail "Read a byte that isn't space"
|
||||||
|
else fail "No bytes left to read"
|
||||||
|
|
||||||
|
(.+.) :: (Applicative f, Num a) => f a -> f a -> f a
|
||||||
|
(.+.) = liftA2 (+)
|
||||||
|
|
||||||
|
infixl 6 .+.
|
||||||
|
|
||||||
|
instance SizedBinary RefAd where
|
||||||
|
sizePut ad =
|
||||||
|
lenPut (refAdId ad)
|
||||||
|
.+. lenPut Space
|
||||||
|
.+. lenPut (refAdName ad)
|
||||||
|
sizeGet lim = do
|
||||||
|
(r, oid) <- sizeGet lim
|
||||||
|
let lim' = lim - r
|
||||||
|
(r', Space) <- sizeGet lim'
|
||||||
|
let lim'' = lim' - r'
|
||||||
|
(r'', sym) - sizeGet lim''
|
||||||
|
|
||||||
|
if lim > tagPrefixLen
|
||||||
|
then do
|
||||||
|
prefix <- getByteString tagPrefixLen
|
||||||
|
if prefix == tagPrefix
|
||||||
|
then do
|
||||||
|
name <- getByteString $ lim - tagPrefixLen
|
||||||
|
return (lim, SymRefTag name)
|
||||||
|
else fail "symref too short to be a tag"
|
||||||
|
|
||||||
|
-- pack protocol sequence
|
||||||
|
--
|
||||||
|
-- (1) Send the ref list
|
||||||
|
-- (2) Wait for input
|
||||||
|
-- (2a) If got flush-pkt, close channel
|
||||||
|
-- (2b) TODO CONTINUE
|
29
src/GitPackProto2.hs
Normal file
29
src/GitPackProto2.hs
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
{-# LANGUAGE MultiWayIf #-}
|
||||||
|
|
||||||
|
module GitPackProto2 where
|
||||||
|
|
||||||
|
import Prelude
|
||||||
|
|
||||||
|
import Data.Binary.Put
|
||||||
|
|
||||||
|
|
||||||
|
-- algo TODO REVISE TO PERFECTION
|
||||||
|
--
|
||||||
|
-- - send ref discovery
|
||||||
|
-- - receive update request OR flush-pkt which means finish?
|
||||||
|
-- - verify all listed objids in want lines appeared in ref discovery
|
20
src/Network/Git/Local.hs
Normal file
20
src/Network/Git/Local.hs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Network.Git.Local
|
||||||
|
(
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
47
src/Network/Git/Local/Get.hs
Normal file
47
src/Network/Git/Local/Get.hs
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | Helpers for getting git pack protocol elements.
|
||||||
|
module Network.Git.Local.Get
|
||||||
|
( requireFlushPkt
|
||||||
|
, attemptFlushPkt
|
||||||
|
, getDataPkt
|
||||||
|
--TODO export more stuff i added below
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
requireFlushPkt :: Get ()
|
||||||
|
requireFlushPkt = requireByteString "0000"
|
||||||
|
|
||||||
|
attemptFlushPkt :: Get Bool
|
||||||
|
attemptFlushPkt = attemptByteString "0000"
|
||||||
|
|
||||||
|
getDataPkt :: (Int -> Get a) -> Get a
|
||||||
|
getDataPkt getPayload = do
|
||||||
|
pktLen <- getHex16
|
||||||
|
if | pktLen == 0 -> fail "Expected regular pkt-line, got flush-pkt"
|
||||||
|
| pktLen > 65524 -> fail "pkt-len is above the maximum allowed"
|
||||||
|
| pktLen <= 4 -> fail "pkt-len is below the possible minimum"
|
||||||
|
| otherwise ->
|
||||||
|
let len = pktLen - 4
|
||||||
|
in isolate len $ getPayload len
|
||||||
|
|
||||||
|
getObjId :: Get ObjId
|
||||||
|
getObjId = ObjId . fromHex <$> getByteString 40
|
||||||
|
|
||||||
|
getCapabilities :: Int -> Get [Capability]
|
||||||
|
getCapabilities n = do
|
||||||
|
getByteString n
|
||||||
|
return []
|
63
src/Network/Git/Local/Put.hs
Normal file
63
src/Network/Git/Local/Put.hs
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | Helpers for putting git pack protocol elements.
|
||||||
|
module Network.Git.Local.Put
|
||||||
|
( -- * Object ID
|
||||||
|
zeroObjId
|
||||||
|
, putObjId
|
||||||
|
-- * Capability
|
||||||
|
, putCapabilities
|
||||||
|
, lenCapabilities
|
||||||
|
-- * Pkt Line
|
||||||
|
, putFlushPkt
|
||||||
|
, putDataPkt
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
zeroObjId :: ObjId
|
||||||
|
zeroObjId = ObjId $ fromHex $ B.replicate 40 48 -- 40 times '0'
|
||||||
|
|
||||||
|
putObjId :: ObjId -> Put
|
||||||
|
putObjId (ObjId ref) = putByteString $ toHex ref
|
||||||
|
|
||||||
|
putCapability :: Capability -> Put
|
||||||
|
putCapability Capability = putByteString "dummy"
|
||||||
|
|
||||||
|
lenCapability :: Capability -> Int
|
||||||
|
lenCapability Capability = 5
|
||||||
|
|
||||||
|
putCapabilities :: [Capability] -> Put
|
||||||
|
putCapabilities [] = return ()
|
||||||
|
putCapabilities (c:cs) = do
|
||||||
|
putCapability c
|
||||||
|
traverse_ (\ d -> putSpace >> putCapability d) cs
|
||||||
|
|
||||||
|
lenCapabilities :: [Capability] -> Int
|
||||||
|
lenCapabilities [] = 0
|
||||||
|
lenCapabilities (c:cs) = lenCapability c + sum (map ((+ 1) . lenCapability) cs)
|
||||||
|
|
||||||
|
putFlushPkt :: Put
|
||||||
|
putFlushPkt = putByteString "0000"
|
||||||
|
|
||||||
|
putDataPkt :: Bool -> Int -> Put -> Put
|
||||||
|
putDataPkt addLF payloadLen payloadPut =
|
||||||
|
let len = bool id (+1) addLF $ payloadLen
|
||||||
|
in if | len == 0 = fail "tried to put an empty pkt-line"
|
||||||
|
| len > 65520 = fail "payload bigger than maximal pkt-len"
|
||||||
|
| otherwise = do
|
||||||
|
putHex16 $ len + 4
|
||||||
|
payloadPut
|
||||||
|
when addLF $ putLF
|
22
src/Network/Git/Local/RefDiscovery.hs
Normal file
22
src/Network/Git/Local/RefDiscovery.hs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | When communication starts, the server sends the client the list of refs in
|
||||||
|
-- the local repo. This is called ref advertisement. From the client side, it's
|
||||||
|
-- called ref discovery.
|
||||||
|
module Network.Git.Local.RefDiscovery
|
||||||
|
(
|
||||||
|
)
|
||||||
|
where
|
67
src/Network/Git/Local/RefDiscovery/Put.hs
Normal file
67
src/Network/Git/Local/RefDiscovery/Put.hs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Network.Git.Local.RefDiscovery.Put
|
||||||
|
( putRefDiscover
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
putSymRef :: SymRef -> Put
|
||||||
|
putSymRef SymRefHead = putByteString "HEAD"
|
||||||
|
putSymRef (SymRefBranch b) = do
|
||||||
|
putByteString "refs/heads/"
|
||||||
|
putByteString b
|
||||||
|
putSymRef (SymRefTag b p) = do
|
||||||
|
putByteString "refs/tags/"
|
||||||
|
putByteString b
|
||||||
|
when p $ putByteString "^{}"
|
||||||
|
|
||||||
|
putRefAd :: RefAd -> Put
|
||||||
|
putRefAd ad = do
|
||||||
|
putObjId $ refAdId ad
|
||||||
|
putSpace
|
||||||
|
putByteString $ refAdName ad
|
||||||
|
|
||||||
|
lenRefAd :: RefAd -> Int
|
||||||
|
lenRefAd ad = 40 + 1 + B.length (refAdName ad)
|
||||||
|
|
||||||
|
putRefAdLine :: RefAd -> Put
|
||||||
|
putRefAdLine ad = putDataPkt True (lenRefAd ad) $ putRefAd ad
|
||||||
|
|
||||||
|
putRefAdCapaLine :: RefAd -> [Capability] Put
|
||||||
|
putRefAdCapaLine ad caps =
|
||||||
|
putDataPkt True (lenRefAd ad + 1 + lenCapabilities caps) $ do
|
||||||
|
putRefAd ad
|
||||||
|
putNull
|
||||||
|
putCapabilities caps
|
||||||
|
|
||||||
|
putDummyRefAdCapaLine :: [Capability] -> Put
|
||||||
|
putDummyRefAdCapaLine = putRefAdCapaLine $ RefAd
|
||||||
|
{ refAdId = zeroObjId
|
||||||
|
, refAdSym = SymRefOther
|
||||||
|
, refAdName = "capabilities^{}"
|
||||||
|
}
|
||||||
|
|
||||||
|
-- TODO: declare capabilities
|
||||||
|
-- TODO: peel annotated tags (somewhere else, so that this list already gets
|
||||||
|
-- the peeled tags)
|
||||||
|
putRefDiscover :: RefDiscover -> Put
|
||||||
|
putRefDiscover (RefDiscover [] caps) = do
|
||||||
|
putDummyRefAdCapaLine caps
|
||||||
|
putFlushPkt
|
||||||
|
putRefDiscover (ReDiscover (a:as) caps) = do
|
||||||
|
putRefAdCapaLine a caps
|
||||||
|
traverse_ putRefAdLine as
|
||||||
|
putFlushPkt
|
45
src/Network/Git/Local/RefDiscovery/Types.hs
Normal file
45
src/Network/Git/Local/RefDiscovery/Types.hs
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Network.Git.Local.RefDiscovery.Types
|
||||||
|
(
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
-- | A symbolic reference which refers to an object.
|
||||||
|
data SymRef
|
||||||
|
-- | The current branch.
|
||||||
|
= SymRefHead
|
||||||
|
-- | A branch with the given name.
|
||||||
|
| SymRefBranch ByteString
|
||||||
|
-- | A tag with the given name, and whether it's a peeled tag.
|
||||||
|
| SymRefTag ByteString Bool
|
||||||
|
-- | Something else.
|
||||||
|
-- | SymRefOther
|
||||||
|
|
||||||
|
-- | A ref advertisement. Used by one side to tell the other which refs it has
|
||||||
|
-- locally.
|
||||||
|
data RefAd = RefAd
|
||||||
|
{ refAdId :: ObjId
|
||||||
|
, refAdSym :: SymRef
|
||||||
|
, refAdName :: ByteString
|
||||||
|
}
|
||||||
|
|
||||||
|
-- | A message which allows the client to discover what the server side has and
|
||||||
|
-- supports.
|
||||||
|
data RefDiscover = RefDiscover
|
||||||
|
{ rdAds :: [RefAd]
|
||||||
|
, rdCaps :: [Capability]
|
||||||
|
}
|
41
src/Network/Git/Local/ShallowUpdate/Put.hs
Normal file
41
src/Network/Git/Local/ShallowUpdate/Put.hs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Network.Git.Local.ShallowUpdate.Put
|
||||||
|
( putShallowUpdate
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
putShallow :: ObjId -> Put
|
||||||
|
putShallow oid = do
|
||||||
|
let len = 7 + 1 + 40
|
||||||
|
putDataPkt True len $ do
|
||||||
|
putByteString "shallow"
|
||||||
|
putSpace
|
||||||
|
putObjId oid
|
||||||
|
|
||||||
|
putUnshallow :: ObjId -> Put
|
||||||
|
putUnshallow oid = do
|
||||||
|
let len = 9 + 1 + 40
|
||||||
|
putDataPkt True len $ do
|
||||||
|
putByteString "unshallow"
|
||||||
|
putSpace
|
||||||
|
putObjId oid
|
||||||
|
|
||||||
|
putShallowUpdate :: ShallowUpdate -> Put
|
||||||
|
putShallowUpdate su = do
|
||||||
|
traverse_ putShallow $ suShallows su
|
||||||
|
traverse_ putUnshallow $ suUnshallows su
|
||||||
|
putFlushPkt
|
24
src/Network/Git/Local/ShallowUpdate/Types.hs
Normal file
24
src/Network/Git/Local/ShallowUpdate/Types.hs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Network.Git.Local.ShallowUpdate.Types
|
||||||
|
( ShallowUpdate (..)
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
data ShallowUpdate = ShallowUpdate
|
||||||
|
{ suShallows :: [ObjId]
|
||||||
|
, suUnshallows :: [ObjId]
|
||||||
|
}
|
27
src/Network/Git/Local/Types.hs
Normal file
27
src/Network/Git/Local/Types.hs
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Network.Git.Local.Types
|
||||||
|
(
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
-- | A git object identifier. This is a SHA-1 hash. Its common textual
|
||||||
|
-- representation is a 40-byte ASCII hexadecimal string.
|
||||||
|
newtype ObjId = ObjId Ref
|
||||||
|
|
||||||
|
-- | A git protocol capability. The server uses this to tell the client what it
|
||||||
|
-- does and doesn't support.
|
||||||
|
data Capability = Capability
|
22
src/Network/Git/Local/UploadRequest.hs
Normal file
22
src/Network/Git/Local/UploadRequest.hs
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
-- | After the client gets the advertised refs, it decides whether it needs to
|
||||||
|
-- receive updates from the server. If yes, it sends a request which specifies
|
||||||
|
-- exactly what it wants to receive.
|
||||||
|
module Network.Git.Local.UploadRequest
|
||||||
|
(
|
||||||
|
)
|
||||||
|
where
|
77
src/Network/Git/Local/UploadRequest/Get.hs
Normal file
77
src/Network/Git/Local/UploadRequest/Get.hs
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Network.Git.Local.UploadRequest.Get
|
||||||
|
( getUploadRequest
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
getFirstWant :: Get ([Capability], ObjId)
|
||||||
|
getFirstWant = getDataPkt $ \ len -> do
|
||||||
|
requireByteString "want"
|
||||||
|
requireSpace
|
||||||
|
oid <- getObjId
|
||||||
|
caps <- getCapabilities $ len - 45
|
||||||
|
return (caps, oid)
|
||||||
|
|
||||||
|
attemptTaggedObjId :: ByteString -> Get (Maybe ObjId)
|
||||||
|
attemptTaggedObjId s = lookAheahM $ getDataPkt $ \ _len -> do
|
||||||
|
b <- getByteString $ length s
|
||||||
|
if b == s
|
||||||
|
then do
|
||||||
|
requireSpace
|
||||||
|
Just <$> getObjId
|
||||||
|
else return Nothing
|
||||||
|
|
||||||
|
-- unfoldM is from the monad-loops package
|
||||||
|
getWants :: Get ([Capability], [ObjId])
|
||||||
|
getWants = do
|
||||||
|
(caps, oid) <- getFirstWant
|
||||||
|
oids <- unfoldM $ attemptTaggedObjId "want"
|
||||||
|
return (caps, oid:oids)
|
||||||
|
|
||||||
|
getShallows :: Get [ObjId]
|
||||||
|
getShallows = unfoldM $ attemptTaggedObjId "shallow"
|
||||||
|
|
||||||
|
attemptDepth :: Get (Maybe Int)
|
||||||
|
attemptDepth = lookAheadM $ getDataPkt $ \ len -> do
|
||||||
|
b <- getByteString 6
|
||||||
|
if b == "deepen"
|
||||||
|
then do
|
||||||
|
requireSpace
|
||||||
|
d <- getByteString $ len - 7
|
||||||
|
let mn = case B.unsnoc d of
|
||||||
|
Just (i, 10) -> fromDecimal i
|
||||||
|
_ -> fromDecimal d
|
||||||
|
case mn of
|
||||||
|
Nothing -> fail "invalid depth string"
|
||||||
|
Just n -> return $ Just n
|
||||||
|
else return Nothing
|
||||||
|
|
||||||
|
getUploadRequest :: Get UploadRequest
|
||||||
|
getUploadRequest = do
|
||||||
|
(caps, oids) <- getWants
|
||||||
|
shls <- getShallows
|
||||||
|
mdepth <- attemptDepth
|
||||||
|
requireFlushPkt
|
||||||
|
return UploadRequest
|
||||||
|
{ urCaps = caps
|
||||||
|
, urWants = oids
|
||||||
|
, urShallows = shls
|
||||||
|
, urDepth = case mdepth of
|
||||||
|
Nothing -> Nothing
|
||||||
|
Just 0 -> Nothing
|
||||||
|
Just d -> Just d
|
||||||
|
}
|
28
src/Network/Git/Local/UploadRequest/Types.hs
Normal file
28
src/Network/Git/Local/UploadRequest/Types.hs
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
{- This file is part of Vervis.
|
||||||
|
-
|
||||||
|
- Written in 2016 by fr33domlover <fr33domlover@riseup.net>.
|
||||||
|
-
|
||||||
|
- ♡ Copying is an act of love. Please copy, reuse and share.
|
||||||
|
-
|
||||||
|
- The author(s) have dedicated all copyright and related and neighboring
|
||||||
|
- rights to this software to the public domain worldwide. This software is
|
||||||
|
- distributed without any warranty.
|
||||||
|
-
|
||||||
|
- You should have received a copy of the CC0 Public Domain Dedication along
|
||||||
|
- with this software. If not, see
|
||||||
|
- <http://creativecommons.org/publicdomain/zero/1.0/>.
|
||||||
|
-}
|
||||||
|
|
||||||
|
module Network.Git.Local.UploadRequest.Types
|
||||||
|
( UploadRequest (..)
|
||||||
|
)
|
||||||
|
where
|
||||||
|
|
||||||
|
-- | Using this request, the client specifies which git data it wants from the
|
||||||
|
-- server.
|
||||||
|
data UploadRequest = UploadRequest
|
||||||
|
{ urCaps :: [Capability]
|
||||||
|
, urWants :: [ObjId]
|
||||||
|
, urShallows :: [ObjId]
|
||||||
|
, urDepth :: Maybe Int
|
||||||
|
}
|
|
@ -25,10 +25,10 @@ import Control.Monad.IO.Class (liftIO)
|
||||||
import Control.Monad.Logger
|
import Control.Monad.Logger
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import Control.Monad.Trans.Reader (ReaderT (runReaderT), ask)
|
import Control.Monad.Trans.Reader (ReaderT (runReaderT), ask)
|
||||||
import Data.ByteString.Char8 (ByteString, unpack)
|
import Data.ByteString (ByteString)
|
||||||
import Data.ByteString.Lazy (fromStrict)
|
import Data.ByteString.Lazy (fromStrict)
|
||||||
import Data.Foldable (find)
|
import Data.Foldable (find)
|
||||||
import Data.Text (Text, pack)
|
import Data.Text (Text, pack, unpack)
|
||||||
import Database.Persist
|
import Database.Persist
|
||||||
import Database.Persist.Sql (SqlBackend, ConnectionPool, runSqlPool)
|
import Database.Persist.Sql (SqlBackend, ConnectionPool, runSqlPool)
|
||||||
import Network.SSH
|
import Network.SSH
|
||||||
|
@ -43,15 +43,15 @@ import Vervis.Settings
|
||||||
-- TODO:
|
-- TODO:
|
||||||
-- [x] Implement serious logging (info, warning, error, etc.) with
|
-- [x] Implement serious logging (info, warning, error, etc.) with
|
||||||
-- monad-logger, maybe see how loggin works in the scaffolding
|
-- monad-logger, maybe see how loggin works in the scaffolding
|
||||||
-- [ ] See which git commands darcsden SSH supports and see if I can implement
|
-- [ ] See which git commands gitolite SSH supports and see if I can implement
|
||||||
-- them with Hit (i think it was git upload-pack)
|
-- them with Hit (i think it was git upload-pack)
|
||||||
|
|
||||||
type ChannelB = LoggingT (ReaderT ConnectionPool IO)
|
type ChannelBase = LoggingT (ReaderT ConnectionPool IO)
|
||||||
type SessionB = LoggingT (ReaderT ConnectionPool IO)
|
type SessionBase = LoggingT (ReaderT ConnectionPool IO)
|
||||||
type Backend = SqlBackend
|
type Backend = SqlBackend
|
||||||
|
|
||||||
type Channel = ChannelT ChannelB
|
type Channel = ChannelT ChannelBase
|
||||||
type Session = SessionT SessionB ChannelB
|
type Session = SessionT SessionBase ChannelBase
|
||||||
type SshChanDB = ReaderT Backend Channel
|
type SshChanDB = ReaderT Backend Channel
|
||||||
type SshSessDB = ReaderT Backend Session
|
type SshSessDB = ReaderT Backend Session
|
||||||
|
|
||||||
|
@ -68,7 +68,7 @@ runSessDB action = do
|
||||||
pool <- lift . lift $ ask
|
pool <- lift . lift $ ask
|
||||||
runSqlPool action pool
|
runSqlPool action pool
|
||||||
|
|
||||||
chanFail :: Bool -> ByteString -> Channel ()
|
chanFail :: Bool -> Text -> Channel ()
|
||||||
chanFail wantReply msg = do
|
chanFail wantReply msg = do
|
||||||
channelError $ unpack msg
|
channelError $ unpack msg
|
||||||
when wantReply channelFail
|
when wantReply channelFail
|
||||||
|
@ -98,10 +98,31 @@ authorize (PublicKey name key) = do
|
||||||
$logInfoS src "Auth succeeded"
|
$logInfoS src "Auth succeeded"
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
data Action = UploadPack () deriving Show
|
||||||
|
|
||||||
|
detectAction :: ChannelRequest -> Maybe Action
|
||||||
|
detectAction _ = Nothing
|
||||||
|
|
||||||
|
runAction :: Bool -> Action -> Channel (Maybe Text)
|
||||||
|
runAction _wantReply action =
|
||||||
|
case action of
|
||||||
|
UploadPack repo -> return $ Just "Doesn't work yet"
|
||||||
|
|
||||||
handle :: Bool -> ChannelRequest -> Channel ()
|
handle :: Bool -> ChannelRequest -> Channel ()
|
||||||
handle wantReply request = do
|
handle wantReply request = do
|
||||||
$logDebugS src $ pack $ show request
|
$logDebugS src $ pack $ show request
|
||||||
chanFail wantReply "I don't execute any commands yet, come back later"
|
case detectAction request of
|
||||||
|
Nothing -> err "Unsupported request"
|
||||||
|
Just act -> do
|
||||||
|
$logDebugS src $ pack $ show act
|
||||||
|
res <- runAction wantReply act
|
||||||
|
case res of
|
||||||
|
Nothing -> do
|
||||||
|
when wantReply channelSuccess
|
||||||
|
channelDone
|
||||||
|
Just msg -> err msg
|
||||||
|
where
|
||||||
|
err = chanFail wantReply
|
||||||
|
|
||||||
ready :: LogFunc -> IO ()
|
ready :: LogFunc -> IO ()
|
||||||
ready = runLoggingT $ $logInfoS src "SSH server component starting"
|
ready = runLoggingT $ $logInfoS src "SSH server component starting"
|
||||||
|
@ -110,7 +131,7 @@ mkConfig
|
||||||
:: AppSettings
|
:: AppSettings
|
||||||
-> ConnectionPool
|
-> ConnectionPool
|
||||||
-> LogFunc
|
-> LogFunc
|
||||||
-> IO (Config SessionB ChannelB)
|
-> IO (Config SessionBase ChannelBase)
|
||||||
mkConfig settings pool logFunc = do
|
mkConfig settings pool logFunc = do
|
||||||
keyPair <- keyPairFromFile $ appSshKeyFile settings
|
keyPair <- keyPairFromFile $ appSshKeyFile settings
|
||||||
return $ Config
|
return $ Config
|
||||||
|
|
|
@ -34,11 +34,17 @@ flag library-only
|
||||||
default: False
|
default: False
|
||||||
|
|
||||||
library
|
library
|
||||||
exposed-modules: Data.ByteString.Char8.Local
|
exposed-modules: Data.Binary.Get.Local
|
||||||
|
Data.Binary.Put.Local
|
||||||
|
Data.ByteString.Char8.Local
|
||||||
|
Data.ByteString.Local
|
||||||
Data.Char.Local
|
Data.Char.Local
|
||||||
Data.Git.Local
|
Data.Git.Local
|
||||||
Data.Graph.Inductive.Local
|
Data.Graph.Inductive.Local
|
||||||
Data.List.Local
|
Data.List.Local
|
||||||
|
Network.Git.Local
|
||||||
|
Network.Git.Local.Get
|
||||||
|
Network.Git.Local.Put
|
||||||
Network.SSH.Local
|
Network.SSH.Local
|
||||||
Vervis.Application
|
Vervis.Application
|
||||||
Vervis.Field.Key
|
Vervis.Field.Key
|
||||||
|
@ -95,6 +101,7 @@ library
|
||||||
build-depends: aeson
|
build-depends: aeson
|
||||||
, base
|
, base
|
||||||
, base64-bytestring
|
, base64-bytestring
|
||||||
|
, binary
|
||||||
, blaze-html
|
, blaze-html
|
||||||
, bytestring
|
, bytestring
|
||||||
, case-insensitive
|
, case-insensitive
|
||||||
|
|
Loading…
Reference in a new issue