mirror of
https://github.com/Smaug123/managed-git
synced 2025-10-07 00:38:43 +00:00
Trees
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
<Compile Include="TestInit.fs" />
|
<Compile Include="TestInit.fs" />
|
||||||
<Compile Include="TestBlob.fs" />
|
<Compile Include="TestBlob.fs" />
|
||||||
<Compile Include="TestTree.fs" />
|
<Compile Include="TestTree.fs" />
|
||||||
|
<Compile Include="TestFromGitBook.fs" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
@@ -9,7 +9,7 @@ open System.IO.Abstractions.TestingHelpers
|
|||||||
[<TestFixture>]
|
[<TestFixture>]
|
||||||
module TestObject =
|
module TestObject =
|
||||||
[<Test>]
|
[<Test>]
|
||||||
let hashFromDocs () =
|
let ``Commit hash from Git Book`` () =
|
||||||
let t = "what is up, doc?".ToCharArray () |> Array.map byte
|
let t = "what is up, doc?".ToCharArray () |> Array.map byte
|
||||||
|
|
||||||
Object.Blob t
|
Object.Blob t
|
||||||
@@ -19,7 +19,7 @@ module TestObject =
|
|||||||
|> shouldEqual "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
|
|> shouldEqual "bd9dbf5aae1a3862dd1526723246b20206e5fc37"
|
||||||
|
|
||||||
[<Test>]
|
[<Test>]
|
||||||
let writeFromDocs () =
|
let ``Write the commit hash to a file`` () =
|
||||||
let t = "what is up, doc?".ToCharArray () |> Array.map byte
|
let t = "what is up, doc?".ToCharArray () |> Array.map byte
|
||||||
let b =
|
let b =
|
||||||
Object.Blob t
|
Object.Blob t
|
||||||
@@ -34,6 +34,7 @@ module TestObject =
|
|||||||
|
|
||||||
b
|
b
|
||||||
|> EncodedObject.write repo
|
|> EncodedObject.write repo
|
||||||
|
|> ignore
|
||||||
|
|
||||||
let backIn =
|
let backIn =
|
||||||
EncodedObject.catFile repo (EncodedObject.hash b)
|
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 =
|
module TestInit =
|
||||||
|
|
||||||
[<Test>]
|
[<Test>]
|
||||||
let Test1 () =
|
let ``test initialisation`` () =
|
||||||
let fs = MockFileSystem ()
|
let fs = MockFileSystem ()
|
||||||
let dir = fs.Path.GetTempFileName ()
|
let dir = fs.Path.GetTempFileName ()
|
||||||
let gitDir = fs.DirectoryInfo.FromDirectoryName (dir + "_test")
|
let gitDir = fs.DirectoryInfo.FromDirectoryName (dir + "_test")
|
||||||
|
@@ -52,6 +52,7 @@ module TestTree =
|
|||||||
|
|
||||||
b
|
b
|
||||||
|> EncodedObject.write repo
|
|> EncodedObject.write repo
|
||||||
|
|> ignore
|
||||||
|
|
||||||
let backIn =
|
let backIn =
|
||||||
EncodedObject.catFile repo (EncodedObject.hash b)
|
EncodedObject.catFile repo (EncodedObject.hash b)
|
||||||
|
@@ -1,9 +1,8 @@
|
|||||||
namespace Git
|
namespace Git
|
||||||
|
|
||||||
open System
|
|
||||||
open System.IO
|
open System.IO
|
||||||
open System.Security.Cryptography
|
open System.Security.Cryptography
|
||||||
open System.IO.Compression
|
open Ionic.Zlib
|
||||||
|
|
||||||
type EncodedObject =
|
type EncodedObject =
|
||||||
{
|
{
|
||||||
@@ -51,7 +50,7 @@ module EncodedObject =
|
|||||||
|> Array.concat
|
|> Array.concat
|
||||||
|
|
||||||
use ms = new MemoryStream(toWrite)
|
use ms = new MemoryStream(toWrite)
|
||||||
use ds = new DeflateStream(dest, CompressionMode.Compress)
|
use ds = new Ionic.Zlib.ZlibStream(dest, CompressionMode.Compress)
|
||||||
ms.CopyTo ds
|
ms.CopyTo ds
|
||||||
|
|
||||||
/// Read the header of the stream seeked to the beginning of the content.
|
/// 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 =
|
let private uncompress (s : Stream) : EncodedObject =
|
||||||
use ms = new MemoryStream ()
|
use ms = new MemoryStream ()
|
||||||
use ds = new DeflateStream(s, CompressionMode.Decompress)
|
use ds = new Ionic.Zlib.ZlibStream(s, CompressionMode.Decompress)
|
||||||
ds.CopyTo ms
|
ds.CopyTo ms
|
||||||
ms.Seek(0L, SeekOrigin.Begin) |> ignore
|
ms.Seek(0L, SeekOrigin.Begin) |> ignore
|
||||||
|
|
||||||
@@ -91,16 +90,19 @@ module EncodedObject =
|
|||||||
if r.PeekChar () <> -1 then failwith "unexpectedly not at end"
|
if r.PeekChar () <> -1 then failwith "unexpectedly not at end"
|
||||||
result
|
result
|
||||||
|
|
||||||
let write (r : Repository) (o : EncodedObject) : unit =
|
let write (r : Repository) (o : EncodedObject) : Hash =
|
||||||
let hash = hash o |> Hash.toString
|
let hash = hash o
|
||||||
let objectName = hash.[2..]
|
let hashStr = Hash.toString hash
|
||||||
let subdir = hash.[0..1]
|
let objectName = hashStr.[2..]
|
||||||
|
let subdir = hashStr.[0..1]
|
||||||
|
|
||||||
let d = Repository.createSubdir (Repository.objectDir r) subdir
|
let d = Repository.createSubdir (Repository.objectDir r) subdir
|
||||||
use filestream = r.Fs.File.Create (r.Fs.Path.Combine (d.FullName, objectName))
|
use filestream = r.Fs.File.Create (r.Fs.Path.Combine (d.FullName, objectName))
|
||||||
|
|
||||||
compress o filestream
|
compress o filestream
|
||||||
|
|
||||||
|
hash
|
||||||
|
|
||||||
let catFile (r : Repository) (hash : Hash) : EncodedObject =
|
let catFile (r : Repository) (hash : Hash) : EncodedObject =
|
||||||
let hash = hash |> Hash.toString
|
let hash = hash |> Hash.toString
|
||||||
let objectName = hash.[2..]
|
let objectName = hash.[2..]
|
||||||
|
@@ -16,6 +16,8 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<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" />
|
<PackageReference Include="System.IO.Abstractions" Version="11.0.4" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@@ -16,7 +16,7 @@ type InitFailure =
|
|||||||
|
|
||||||
[<RequireQualifiedAccess>]
|
[<RequireQualifiedAccess>]
|
||||||
module Repository =
|
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
|
r.Fs.Path.Combine(r.Directory.FullName, ".git") |> r.Fs.DirectoryInfo.FromDirectoryName
|
||||||
|
|
||||||
let internal objectDir (r : Repository) : IDirectoryInfo =
|
let internal objectDir (r : Repository) : IDirectoryInfo =
|
||||||
@@ -29,6 +29,11 @@ module Repository =
|
|||||||
output.Create ()
|
output.Create ()
|
||||||
output
|
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> =
|
let init (dir : IDirectoryInfo) : Result<Repository, InitFailure> =
|
||||||
if not dir.Exists then Error DirectoryDoesNotExist
|
if not dir.Exists then Error DirectoryDoesNotExist
|
||||||
elif not <| Seq.isEmpty (dir.EnumerateDirectories ".git") then Error AlreadyGit
|
elif not <| Seq.isEmpty (dir.EnumerateDirectories ".git") then Error AlreadyGit
|
||||||
|
Reference in New Issue
Block a user