mirror of
https://github.com/Smaug123/managed-git
synced 2025-10-09 01:38:43 +00:00
116 lines
3.4 KiB
Forth
116 lines
3.4 KiB
Forth
namespace Git
|
|
|
|
open System.IO
|
|
open System.Security.Cryptography
|
|
open Ionic.Zlib
|
|
|
|
type EncodedObject =
|
|
{
|
|
Header : Header
|
|
Content : byte array
|
|
}
|
|
|
|
[<RequireQualifiedAccess>]
|
|
module EncodedObject =
|
|
let encode (o : Git.Object) : EncodedObject =
|
|
let contents =
|
|
match o with
|
|
| Object.Blob c -> Blob.encode c
|
|
| Object.Tree entries -> Tree.encode entries
|
|
|
|
{
|
|
Header =
|
|
match o with
|
|
| Object.Blob _ ->
|
|
Header.Blob contents.Length
|
|
| Object.Tree _ ->
|
|
Header.Tree contents.Length
|
|
Content = contents
|
|
}
|
|
|
|
let decode (e : EncodedObject) : Git.Object =
|
|
match e.Header with
|
|
| Header.Tree _ ->
|
|
Tree.decode e.Content
|
|
|> Object.Tree
|
|
| Header.Blob _ ->
|
|
Blob.decode e.Content
|
|
|> Object.Blob
|
|
|
|
let hash (o : EncodedObject) : Hash =
|
|
use hasher = SHA1.Create ()
|
|
let content = Array.concat [| Header.toBytes o.Header ; o.Content |]
|
|
|
|
hasher.ComputeHash content
|
|
|> Hash.ofBytes
|
|
|
|
let private compress (o : EncodedObject) (dest : Stream) : unit =
|
|
let toWrite =
|
|
[| Header.toBytes o.Header ; o.Content |]
|
|
|> Array.concat
|
|
|
|
use ms = new MemoryStream(toWrite)
|
|
use ds = new Ionic.Zlib.ZlibStream(dest, CompressionMode.Compress)
|
|
ms.CopyTo ds
|
|
|
|
/// Read the header of the stream seeked to the beginning of the content.
|
|
let private consumeHeader (s : BinaryReader) : Header =
|
|
let rec bytes () : byte seq =
|
|
seq {
|
|
let newByte = s.Read ()
|
|
if newByte < 0 then failwith "ran out of bytes"
|
|
elif newByte > 0 then
|
|
yield (byte newByte)
|
|
yield! bytes ()
|
|
// stop reading the header at the 0 byte
|
|
}
|
|
|
|
match bytes () |> Seq.toArray |> Header.ofBytes with
|
|
| None ->
|
|
failwith "malformed header"
|
|
| Some b -> b
|
|
|
|
let private uncompress (s : Stream) : EncodedObject =
|
|
use ms = new MemoryStream ()
|
|
use ds = new Ionic.Zlib.ZlibStream(s, CompressionMode.Decompress)
|
|
ds.CopyTo ms
|
|
ms.Seek(0L, SeekOrigin.Begin) |> ignore
|
|
|
|
use r = new BinaryReader(ms)
|
|
let header = consumeHeader r
|
|
let expectedLength =
|
|
match header with
|
|
| Header.Blob i -> i
|
|
| Header.Tree i -> i
|
|
let result =
|
|
{
|
|
Header = header
|
|
Content = r.ReadBytes expectedLength
|
|
}
|
|
if r.PeekChar () <> -1 then failwith "unexpectedly not at end"
|
|
result
|
|
|
|
let write (r : Repository) (o : EncodedObject) : Hash =
|
|
let hash = hash o
|
|
let hashStr = Hash.toString hash
|
|
let objectName = hashStr.[2..]
|
|
let subdir = hashStr.[0..1]
|
|
|
|
let d = Repository.createSubdir (Repository.objectDir r) subdir
|
|
use filestream = r.Fs.File.Create (r.Fs.Path.Combine (d.FullName, objectName))
|
|
|
|
compress o filestream
|
|
|
|
hash
|
|
|
|
let catFile (r : Repository) (hash : Hash) : EncodedObject =
|
|
let hash = hash |> Hash.toString
|
|
let objectName = hash.[2..]
|
|
let subdir = hash.[0..1]
|
|
|
|
use filestream =
|
|
r.Fs.Path.Combine ((Repository.objectDir r).FullName, subdir, objectName)
|
|
|> r.Fs.File.OpenRead
|
|
|
|
uncompress filestream
|