mirror of
https://github.com/Smaug123/managed-git
synced 2025-10-06 16:28:42 +00:00
Trees
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
<Compile Include="TestInit.fs" />
|
||||
<Compile Include="TestBlob.fs" />
|
||||
<Compile Include="TestTree.fs" />
|
||||
<Compile Include="TestFromGitBook.fs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@@ -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
222
Git.Test/TestFromGitBook.fs
Normal 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
|
@@ -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")
|
||||
|
@@ -52,6 +52,7 @@ module TestTree =
|
||||
|
||||
b
|
||||
|> EncodedObject.write repo
|
||||
|> ignore
|
||||
|
||||
let backIn =
|
||||
EncodedObject.catFile repo (EncodedObject.hash b)
|
||||
|
@@ -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..]
|
||||
|
@@ -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>
|
||||
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user