This commit is contained in:
Smaug123
2020-05-02 22:15:40 +01:00
parent efffb52e92
commit 4f81045680
8 changed files with 246 additions and 12 deletions

View File

@@ -20,6 +20,7 @@
<Compile Include="TestInit.fs" />
<Compile Include="TestBlob.fs" />
<Compile Include="TestTree.fs" />
<Compile Include="TestFromGitBook.fs" />
</ItemGroup>
<ItemGroup>

View File

@@ -9,7 +9,7 @@ open System.IO.Abstractions.TestingHelpers
[<TestFixture>]
module TestObject =
[<Test>]
let hashFromDocs () =
let ``Commit hash from Git Book`` () =
let t = "what is up, doc?".ToCharArray () |> Array.map byte
Object.Blob t
@@ -19,7 +19,7 @@ module TestObject =
|> shouldEqual "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
[<Test>]
let writeFromDocs () =
let ``Write the commit hash to a file`` () =
let t = "what is up, doc?".ToCharArray () |> Array.map byte
let b =
Object.Blob t
@@ -34,6 +34,7 @@ module TestObject =
b
|> EncodedObject.write repo
|> ignore
let backIn =
EncodedObject.catFile repo (EncodedObject.hash b)

222
Git.Test/TestFromGitBook.fs Normal file
View File

@@ -0,0 +1,222 @@
namespace Git.Test
open System
open System.IO
open System.IO.Abstractions.TestingHelpers
open NUnit.Framework
open FsUnitTyped
open Git
[<TestFixture>]
module TestFromGitBook =
[<Test>]
let ``Test ch 10.2 up to Commit`` () =
let fs = MockFileSystem ()
let dir = fs.Path.GetTempFileName ()
let versionDir = fs.DirectoryInfo.FromDirectoryName (dir + "_test")
versionDir.Create()
let repo = match Repository.init versionDir with | Ok r -> r | Error e -> failwithf "Oh no: %+A" e
// Directory structure is correct:
let objectsDir = fs.Path.Combine (Repository.gitDir(repo).FullName, "objects") |> fs.DirectoryInfo.FromDirectoryName
objectsDir.EnumerateDirectories ()
|> Seq.map (fun d -> d.Name)
|> Seq.toList
|> List.sort
|> shouldEqual [
"info"
"pack"
]
objectsDir.EnumerateFiles ("*", SearchOption.AllDirectories)
|> shouldBeEmpty
// Write our first object
let h =
"test content\n".ToCharArray ()
|> Array.map byte
|> Object.Blob
|> EncodedObject.encode
|> EncodedObject.write repo
h
|> shouldEqual (Hash.ofString "d670460b4b4aece5915caf5c68d12f560a9fe3e4")
// Check that it's appeared
objectsDir.EnumerateFiles ("*", SearchOption.AllDirectories)
|> Seq.map (fun f -> f.Directory.Name, f.Name)
|> Seq.exactlyOne
|> shouldEqual ("d6", "70460b4b4aece5915caf5c68d12f560a9fe3e4")
// Read it back in
match EncodedObject.catFile repo h |> EncodedObject.decode with
| Object.Blob b ->
b
|> Array.map char
|> String
|> shouldEqual "test content\n"
| s -> failwithf "Oh no: +%A" s
// Version control
// TODO - add helper methods for dealing with file contents
let h1 =
"version 1\n".ToCharArray ()
|> Array.map byte
|> Object.Blob
|> EncodedObject.encode
|> EncodedObject.write repo
h1
|> shouldEqual (Hash.ofString "83baae61804e65cc73a7201a7252750c76066a30")
let h2 =
"version 2\n".ToCharArray ()
|> Array.map byte
|> Object.Blob
|> EncodedObject.encode
|> EncodedObject.write repo
h2
|> shouldEqual (Hash.ofString "1f7a7a472abf3dd9643fd615f6da379c4acb3e3a")
objectsDir.EnumerateFiles ("*", SearchOption.AllDirectories)
|> Seq.map (fun f -> f.Directory.Name, f.Name)
|> Seq.toList
|> List.sort
|> shouldEqual [
"1f", "7a7a472abf3dd9643fd615f6da379c4acb3e3a"
"83", "baae61804e65cc73a7201a7252750c76066a30"
"d6", "70460b4b4aece5915caf5c68d12f560a9fe3e4"
]
match EncodedObject.catFile repo h1 |> EncodedObject.decode with
| Object.Blob b ->
b
|> Array.map char
|> String
|> shouldEqual "version 1\n"
| s -> failwithf "Oh no: +%A" s
match EncodedObject.catFile repo h2 |> EncodedObject.decode with
| Object.Blob b ->
b
|> Array.map char
|> String
|> shouldEqual "version 2\n"
| s -> failwithf "Oh no: +%A" s
// TODO - implement the staging area and then test it here
// Add to the tree
let tree1 =
[
{
Mode = 100644
Name = "test.txt"
Hash = h1
}
]
|> Object.Tree
|> EncodedObject.encode
|> EncodedObject.write repo
tree1 |> shouldEqual (Hash.ofString "d8329fc1cc938780ffdd9f94e0d364e0ea74f579")
match EncodedObject.catFile repo tree1 |> EncodedObject.decode with
| Object.Tree t ->
t
|> List.exactlyOne
|> shouldEqual {
Mode = 100644
Name = "test.txt"
Hash = h1
}
| s -> failwithf "Oh no: +%A" s
let newHash =
"new file\n".ToCharArray ()
|> Array.map byte
|> Object.Blob
|> EncodedObject.encode
|> EncodedObject.write repo
newHash |> shouldEqual (Hash.ofString "fa49b077972391ad58037050f2a75f74e3671e92")
let tree2 =
[
{
Mode = 100644
Name = "new.txt"
Hash = newHash
}
{
Mode = 100644
Name = "test.txt"
Hash = h2
}
]
|> Object.Tree
|> EncodedObject.encode
|> EncodedObject.write repo
tree2 |> shouldEqual (Hash.ofString "0155eb4229851634a0f03eb265b69f5a2d56f341")
match EncodedObject.catFile repo tree2 |> EncodedObject.decode with
| Object.Tree t ->
t
|> shouldEqual [
{
Mode = 100644
Name = "new.txt"
Hash = newHash
}
{
Mode = 100644
Name = "test.txt"
Hash = h2
}
]
| s -> failwithf "Oh no: +%A" s
// and the prefix one
let tree3 =
[
{
Mode = 40000
Name = "bak"
Hash = tree1
}
{
Mode = 100644
Name = "new.txt"
Hash = newHash
}
{
Mode = 100644
Name = "test.txt"
Hash = h2
}
]
|> Object.Tree
|> EncodedObject.encode
|> EncodedObject.write repo
tree3 |> shouldEqual (Hash.ofString "3c4e9cd789d88d8d89c1073707c3585e41b0e614")
match EncodedObject.catFile repo tree3 |> EncodedObject.decode with
| Object.Tree t ->
t
|> shouldEqual [
{
Mode = 40000
Name = "bak"
Hash = tree1
}
{
Mode = 100644
Name = "new.txt"
Hash = newHash
}
{
Mode = 100644
Name = "test.txt"
Hash = h2
}
]
| s -> failwithf "Oh no: +%A" s
// TODO: the section on commits

View File

@@ -10,7 +10,7 @@ open Git
module TestInit =
[<Test>]
let Test1 () =
let ``test initialisation`` () =
let fs = MockFileSystem ()
let dir = fs.Path.GetTempFileName ()
let gitDir = fs.DirectoryInfo.FromDirectoryName (dir + "_test")

View File

@@ -52,6 +52,7 @@ module TestTree =
b
|> EncodedObject.write repo
|> ignore
let backIn =
EncodedObject.catFile repo (EncodedObject.hash b)

View File

@@ -1,9 +1,8 @@
namespace Git
open System
open System.IO
open System.Security.Cryptography
open System.IO.Compression
open Ionic.Zlib
type EncodedObject =
{
@@ -51,7 +50,7 @@ module EncodedObject =
|> Array.concat
use ms = new MemoryStream(toWrite)
use ds = new DeflateStream(dest, CompressionMode.Compress)
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.
@@ -73,7 +72,7 @@ module EncodedObject =
let private uncompress (s : Stream) : EncodedObject =
use ms = new MemoryStream ()
use ds = new DeflateStream(s, CompressionMode.Decompress)
use ds = new Ionic.Zlib.ZlibStream(s, CompressionMode.Decompress)
ds.CopyTo ms
ms.Seek(0L, SeekOrigin.Begin) |> ignore
@@ -91,16 +90,19 @@ module EncodedObject =
if r.PeekChar () <> -1 then failwith "unexpectedly not at end"
result
let write (r : Repository) (o : EncodedObject) : unit =
let hash = hash o |> Hash.toString
let objectName = hash.[2..]
let subdir = hash.[0..1]
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..]

View File

@@ -16,6 +16,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Ionic.Zlib" Version="1.9.1.5" />
<PackageReference Include="Ionic.Zlib.Core" Version="1.0.0" />
<PackageReference Include="System.IO.Abstractions" Version="11.0.4" />
</ItemGroup>

View File

@@ -16,7 +16,7 @@ type InitFailure =
[<RequireQualifiedAccess>]
module Repository =
let internal gitDir (r : Repository) : IDirectoryInfo =
let gitDir (r : Repository) : IDirectoryInfo =
r.Fs.Path.Combine(r.Directory.FullName, ".git") |> r.Fs.DirectoryInfo.FromDirectoryName
let internal objectDir (r : Repository) : IDirectoryInfo =
@@ -29,6 +29,11 @@ module Repository =
output.Create ()
output
let make (dir : IDirectoryInfo) : Repository option =
if dir.Exists && dir.EnumerateDirectories () |> Seq.map (fun i -> i.Name) |> Seq.contains ".git" then
Some { Directory = dir }
else None
let init (dir : IDirectoryInfo) : Result<Repository, InitFailure> =
if not dir.Exists then Error DirectoryDoesNotExist
elif not <| Seq.isEmpty (dir.EnumerateDirectories ".git") then Error AlreadyGit