{"id":18009912,"url":"https://github.com/kekyo/gitreader","last_synced_at":"2025-03-26T14:31:40.386Z","repository":{"id":153928959,"uuid":"628923166","full_name":"kekyo/GitReader","owner":"kekyo","description":"Lightweight Git local repository traversal library for .NET/.NET Core/.NET Framework.","archived":false,"fork":false,"pushed_at":"2024-05-29T00:44:04.000Z","size":5778,"stargazers_count":74,"open_issues_count":1,"forks_count":7,"subscribers_count":3,"default_branch":"main","last_synced_at":"2024-10-30T03:48:43.216Z","etag":null,"topics":["csharp","dotnet","fsharp","git","managed","repository"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kekyo.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-17T09:01:54.000Z","updated_at":"2024-07-23T09:44:18.000Z","dependencies_parsed_at":"2023-10-30T03:33:21.655Z","dependency_job_id":"f82de32c-ce09-4281-bb6a-174e1464b9c1","html_url":"https://github.com/kekyo/GitReader","commit_stats":null,"previous_names":[],"tags_count":24,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FGitReader","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FGitReader/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FGitReader/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kekyo%2FGitReader/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kekyo","download_url":"https://codeload.github.com/kekyo/GitReader/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245670742,"owners_count":20653413,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["csharp","dotnet","fsharp","git","managed","repository"],"created_at":"2024-10-30T02:11:33.636Z","updated_at":"2025-03-26T14:31:40.374Z","avatar_url":"https://github.com/kekyo.png","language":"C#","readme":"# GitReader\n\nLightweight Git local repository traversal library.\n\n![GitReader](Images/GitReader.100.png)\n\n# Status\n\n[![Project Status: Active – The project has reached a stable, usable state and is being actively developed.](https://www.repostatus.org/badges/latest/active.svg)](https://www.repostatus.org/#active)\n\n|Target|Pakcage|\n|:----|:----|\n|Any|[![NuGet GitReader](https://img.shields.io/nuget/v/GitReader.svg?style=flat)](https://www.nuget.org/packages/GitReader)|\n|F# binding|[![NuGet FSharp.GitReader](https://img.shields.io/nuget/v/FSharp.GitReader.svg?style=flat)](https://www.nuget.org/packages/FSharp.GitReader)|\n\n----\n\n[![Japanese language](Images/Japanese.256.png)](https://github.com/kekyo/GitReader/blob/main/README_ja.md)\n\n## What is this?\n\nHave you ever wanted to access information about your local Git repository in .NET?\nExplore and tag branches, get commit dates and contributor information, and read commit directory structures and files.\n\nGitReader is written only managed code Git local repository traversal library for a wide range of .NET environments.\nIt is lightweight, has a concise, easy-to-use interface, does not depend any other libraries, and does not contain native libraries,\nmaking it suitable for any environment.\n\nExample:\n\n```csharp\nusing GitReader;\nusing GitReader.Structures;\n\n// Open repository (With high-level interface)\nusing var repository =\n    await Repository.Factory.OpenStructureAsync(\n        \"/home/kekyo/Projects/YourOwnLocalGitRepo\");\n\n// Found current head.\nif (repository.Head is { } head)\n{\n    Console.WriteLine($\"Name: {head.Name}\");\n\n    // Get head commit.\n    var commit = await head.GetHeadCommitAsync();\n\n    Console.WriteLine($\"Hash: {commit.Hash}\");\n    Console.WriteLine($\"Author: {commit.Author}\");\n    Console.WriteLine($\"Committer: {commit.Committer}\");\n    Console.WriteLine($\"Subject: {commit.Subject}\");\n    Console.WriteLine($\"Body: {commit.Body}\");\n}\n```\n\nIt has the following features:\n\n* It provides information on Git branches, tags, and commits.\n* Branch tree traversal.\n* Read only interface makes immutability.\n* Both high-level and primitive interfaces ready.\n* Fully asynchronous operation without any sync-over-async implementation.\n* Only contains 100% managed code. Independent of any external libraries other than the BCL and its compliant libraries.\n* Reliable zlib decompression using the .NET standard deflate implementation.\n\nThis library was designed from the ground up to replace `libgit2sharp`, on which [RelaxVersioner](https://github.com/kekyo/CenterCLR.RelaxVersioner) depended.\nIt primarily fits the purpose of easily extracting commit information from a Git repository.\n\n### Target .NET platforms\n\n* .NET 9.0 to 5.0\n* .NET Core 3.1 to 2.0\n* .NET Standard 2.1 to 1.6\n* .NET Framework 4.8.1 to 3.5\n\n### F# specialized binding\n\nF# 5.0 or upper, it contains F# friendly signature definition.\n(Asynchronous operations with `Async` type, elimination of null values with `Option`, etc.)\n\n* .NET 9.0 to 5.0\n* .NET Core 3.1 to 2.0\n* .NET Standard 2.1, 2.0\n* .NET Framework 4.8.1 to 4.6.1\n\nNote: All target framework variations are tested only newest it.\n\n----\n\n## How to use\n\nInstall [GitReader](https://www.nuget.org/packages/GitReader) from NuGet.\n\n* Install [FSharp.GitReader](https://www.nuget.org/packages/FSharp.GitReader) when you need to use with F#.\n  It has F# friendly signature definition.\n  You can freely use the same version of a package to switch back and forth between C# and F#,\n  as long as the runtime instances are compatible.\n\nGitReader has high-level interfaces and primitive interfaces.\n\n* The high-level interface is an interface that abstracts the Git repository.\n  Easy to handle without knowing the internal structure of Git.\n  It is possible to retrieve branch, tag, and commit information, and to read files (Blobs) at a glance.\n* The primitive interface is an interface that exposes the internal structure of the Git repository as it is,\n  It is simple to handle if you know the internal structure of Git,\n  and it offers high performance in asynchronous processing.\n\n### Sample Code\n\nComprehensive sample code can be found in the [samples directory](/samples).\nThe following things are minimal code fragments.\n\n----\n\n## Samples (High-level interfaces)\n\nThe high-level interface is easily referenced by automatically reading much of the information tied to a commit.\n\n### Get current head commit\n\n```csharp\nusing GitReader;\nusing GitReader.Structures;\n\nusing StructuredRepository repository =\n    await Repository.Factory.OpenStructureAsync(\n        \"/home/kekyo/Projects/YourOwnLocalGitRepo\");\n\n// Found current head\nif (repository.Head is Branch head)\n{\n    Console.WriteLine($\"Name: {head.Name}\");\n\n    // Get the commit that this HEAD points to:\n    Commit commit = await head.GetHeadCommitAsync();\n\n    Console.WriteLine($\"Hash: {commit.Hash}\");\n    Console.WriteLine($\"Author: {commit.Author}\");\n    Console.WriteLine($\"Committer: {commit.Committer}\");\n    Console.WriteLine($\"Subject: {commit.Subject}\");\n    Console.WriteLine($\"Body: {commit.Body}\");\n}\n```\n\n### Get a commit directly\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\") is Commit commit)\n{\n    Console.WriteLine($\"Hash: {commit.Hash}\");\n    Console.WriteLine($\"Author: {commit.Author}\");\n    Console.WriteLine($\"Committer: {commit.Committer}\");\n    Console.WriteLine($\"Subject: {commit.Subject}\");\n    Console.WriteLine($\"Body: {commit.Body}\");\n}\n```\n\n### Get a branch head commit\n\n```csharp\nBranch branch = repository.Branches[\"develop\"];\n\nConsole.WriteLine($\"Name: {branch.Name}\");\nConsole.WriteLine($\"IsRemote: {branch.IsRemote}\");\n\nCommit commit = await branch.GetHeadCommitAsync();\n\nConsole.WriteLine($\"Hash: {commit.Hash}\");\nConsole.WriteLine($\"Author: {commit.Author}\");\nConsole.WriteLine($\"Committer: {commit.Committer}\");\nConsole.WriteLine($\"Subject: {commit.Subject}\");\nConsole.WriteLine($\"Body: {commit.Body}\");\n```\n\nUse `BranchesAll` instead of `Branches` when there may be branches with the same name but different references.\nUsing `Branches` will give you only the first branch available from all branches of the same name.\n\n### Get a tag\n\n```csharp\nTag tag = repository.Tags[\"1.2.3\"];\n\nConsole.WriteLine($\"Name: {tag.Name}\");\nConsole.WriteLine($\"Type: {tag.Type}\");\nConsole.WriteLine($\"ObjectHash: {tag.ObjectHash}\");\n\n// If present the annotation?\nif (tag.HasAnnotation)\n{\n    // Get tag annotation.\n    Annotation annotation = await tag.GetAnnotationAsync();\n\n    Console.WriteLine($\"Tagger: {annotation.Tagger}\");\n    Console.WriteLine($\"Message: {annotation.Message}\");\n}\n\n// If tag is a commit tag?\nif (tag.Type == ObjectTypes.Commit)\n{\n    // Get the commit indicated by the tag.\n    Commit commit = await tag.GetCommitAsync();\n\n    // ...\n}\n```\n\n### Get related branches and tags from a commit\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\") is Commit commit)\n{\n    // The ReadOnlyArray\u003cT\u003e class is used to protect the inner array.\n    // Usage is the same as for general collections such as List\u003cT\u003e.\n    ReadOnlyArray\u003cBranch\u003e branches = commit.Branches;\n    ReadOnlyArray\u003cTag\u003e tags = commit.Tags;\n\n    // ...\n}\n```\n\n### Enumerate branches\n\n```csharp\nforeach (Branch branch in repository.Branches.Values)\n{\n    Console.WriteLine($\"Name: {branch.Name}\");\n    Console.WriteLine($\"IsRemote: {branch.IsRemote}\");\n\n    Commit commit = await branch.GetHeadCommitAsync();\n\n    Console.WriteLine($\"Hash: {commit.Hash}\");\n    Console.WriteLine($\"Author: {commit.Author}\");\n    Console.WriteLine($\"Committer: {commit.Committer}\");\n    Console.WriteLine($\"Subject: {commit.Subject}\");\n    Console.WriteLine($\"Body: {commit.Body}\");\n}\n```\n\n### Enumerate tags\n\n```csharp\nforeach (Tag tag in repository.Tags.Values)\n{\n    Console.WriteLine($\"Name: {tag.Name}\");\n    Console.WriteLine($\"Type: {tag.Type}\");\n    Console.WriteLine($\"ObjectHash: {tag.ObjectHash}\");\n}\n```\n\n### Enumerate stashes\n\n```csharp\nforeach (Stash stash in repository.Stashes)\n{\n    Console.WriteLine($\"Commit: {stash.Commit.Hash}\");\n    Console.WriteLine($\"Committer: {stash.Committer}\");\n    Console.WriteLine($\"Message: {stash.Message}\");\n}\n```\n\n### Get parent commits\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"6961a50ef3ad4e43ed9774daffd8457d32cf5e75\") is Commit commit)\n{\n    Commit[] parents = await commit.GetParentCommitsAsync();\n\n    foreach (Commit parent in parents)\n    {\n        Console.WriteLine($\"Hash: {parent.Hash}\");\n        Console.WriteLine($\"Author: {parent.Author}\");\n        Console.WriteLine($\"Committer: {parent.Committer}\");\n        Console.WriteLine($\"Subject: {parent.Subject}\");\n        Console.WriteLine($\"Body: {parent.Body}\");\n    }\n}\n```\n\n### Get commit tree information\n\nTree information is the tree structure of directories and files that are placed when a commit is checked out.\nThe code shown here does not actually 'check out', but reads these structures as information.\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"6961a50ef3ad4e43ed9774daffd8457d32cf5e75\") is Commit commit)\n{\n    TreeRoot treeRoot = await commit.GetTreeRootAsync();\n\n    foreach (TreeEntry entry in treeRoot.Children)\n    {\n        Console.WriteLine($\"Hash: {entry.Hash}\");\n        Console.WriteLine($\"Name: {entry.Name}\");\n        Console.WriteLine($\"Modes: {entry.Modes}\");\n    }\n}\n```\n\n### Read blob by stream\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"6961a50ef3ad4e43ed9774daffd8457d32cf5e75\") is Commit commit)\n{\n    TreeRoot treeRoot = await commit.GetTreeRootAsync();\n\n    foreach (TreeEntry entry in treeRoot.Children)\n    {\n        // For Blob, the instance type is `TreeBlobEntry`.\n        if (entry is TreeBlobEntry blob)\n        {\n            using Stream stream = await blob.OpenBlobAsync();\n\n            // (You can access the blob...)\n        }\n    }\n}\n```\n\n### Reading a submodule in commit tree.\n\nYou can also identify references to submodules.\nHowever, if you want to reference information in a submodule, you must open a new repository.\nThis is accomplished with `OpenSubModuleAsync()`.\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"6961a50ef3ad4e43ed9774daffd8457d32cf5e75\") is Commit commit)\n{\n    TreeRoot treeRoot = await commit.GetTreeRootAsync();\n\n    foreach (TreeEntry entry in treeRoot.Children)\n    {\n        // For a submodule, the instance type is `TreeSubModuleEntry`.\n        if (entry is TreeSubModuleEntry subModule)\n        {\n            // Open this submodule repository.\n            using var subModuleRepository = await subModule.OpenSubModuleAsync();\n\n            // Retreive the commit hash specified in the original repository.\n            if (await subModuleRepository.GetCommitAsync(\n                subModule.Hash) is Commit subModuleCommit)\n            {\n                // ...\n            }\n        }\n    }\n}\n```\n\n### Traverse a branch through primary commits\n\nA commit in Git can have multiple parent commits.\nThis occurs with merge commits, where there are links to all parent commits.\nThe first parent commit is called the \"primary commit\"\nand is always present except for the first commit in the repository.\n\nUse `GetPrimaryParentCommitAsync()` to retrieve the primary commit,\nand use `GetParentCommitsAsync()` to get links to all parent commits.\n\nAs a general thing about Git,\nit is important to note that the parent-child relationship of commits (caused by branching and merging),\nalways expressed as one direction, from \"child\" to \"parent\".\n\nThis is also true for the high-level interface; there is no interface for referencing a child from its parent.\nTherefore, if you wish to perform such a search, you must construct the link in the reverse direction on your own.\n\nThe following example recursively searches for a parent commit from a child commit.\n\n```csharp\nBranch branch = repository.Branches[\"develop\"];\n\nConsole.WriteLine($\"Name: {branch.Name}\");\nConsole.WriteLine($\"IsRemote: {branch.IsRemote}\");\n\nCommit? current = await branch.GetHeadCommitAsync();\n\n// Continue as long as the parent commit exists.\nwhile (current != null)\n{\n    Console.WriteLine($\"Hash: {current.Hash}\");\n    Console.WriteLine($\"Author: {current.Author}\");\n    Console.WriteLine($\"Committer: {current.Committer}\");\n    Console.WriteLine($\"Subject: {current.Subject}\");\n    Console.WriteLine($\"Body: {current.Body}\");\n\n    // Get primary parent commit.\n    current = await current.GetPrimaryParentCommitAsync();\n}\n```\n\n----\n\n## Samples (Primitive interfaces)\n\nThe high-level interface is implemented internally using these primitive interfaces.\nWe do not have a complete list of all examples, so we recommend referring to the GitReader code if you need information.\n\n* You may want to start with [StructuredRepositoryFacade class](/GitReader.Core/Structures/StructuredRepositoryFacade.cs).\n\n### Read current head commit\n\n```csharp\nusing GitReader;\nusing GitReader.Primitive;\n\nusing PrimitiveRepository repository =\n    await Repository.Factory.OpenPrimitiveAsync(\n        \"/home/kekyo/Projects/YourOwnLocalGitRepo\");\n\nif (await repository.GetCurrentHeadReferenceAsync() is PrimitiveReference head)\n{\n    if (await repository.GetCommitAsync(head) is PrimitiveCommit commit)\n    {\n        Console.WriteLine($\"Hash: {commit.Hash}\");\n        Console.WriteLine($\"Author: {commit.Author}\");\n        Console.WriteLine($\"Committer: {commit.Committer}\");\n        Console.WriteLine($\"Message: {commit.Message}\");\n    }\n}\n```\n\n### Read a commit directly\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\") is PrimitiveCommit commit)\n{\n    Console.WriteLine($\"Hash: {commit.Hash}\");\n    Console.WriteLine($\"Author: {commit.Author}\");\n    Console.WriteLine($\"Committer: {commit.Committer}\");\n    Console.WriteLine($\"Message: {commit.Message}\");\n}\n```\n\n### Read a branch head commit\n\n```csharp\nPrimitiveReference head = await repository.GetBranchHeadReferenceAsync(\"develop\");\n\nif (await repository.GetCommitAsync(head) is PrimitiveCommit commit)\n{\n    Console.WriteLine($\"Hash: {commit.Hash}\");\n    Console.WriteLine($\"Author: {commit.Author}\");\n    Console.WriteLine($\"Committer: {commit.Committer}\");\n    Console.WriteLine($\"Message: {commit.Message}\");\n}\n```\n\n### Enumerate branches\n\n```csharp\nPrimitiveReference[] branches = await repository.GetBranchHeadReferencesAsync();\n\nforeach (PrimitiveReference branch in branches)\n{\n    Console.WriteLine($\"Name: {branch.Name}\");\n    Console.WriteLine($\"Commit: {branch.Commit}\");\n}\n```\n\n### Enumerate remote branches\n\n```csharp\nPrimitiveReference[] branches = await repository.GetRemoteBranchHeadReferencesAsync();\n\nforeach (PrimitiveReference branch in branches)\n{\n    Console.WriteLine($\"Name: {branch.Name}\");\n    Console.WriteLine($\"Commit: {branch.Commit}\");\n}\n```\n\n### Enumerate tags\n\n```csharp\nPrimitiveTagReference[] tagReferences = await repository.GetTagReferencesAsync();\n\nforeach (PrimitiveTagReference tagReference in tagReferences)\n{\n    PrimitiveTag tag = await repository.GetTagAsync(tagReference);\n\n    Console.WriteLine($\"Hash: {tag.Hash}\");\n    Console.WriteLine($\"Type: {tag.Type}\");\n    Console.WriteLine($\"Name: {tag.Name}\");\n    Console.WriteLine($\"Tagger: {tag.Tagger}\");\n    Console.WriteLine($\"Message: {tag.Message}\");\n}\n```\n\n### Read commit tree information\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\") is PrimitiveCommit commit)\n{\n    PrimitiveTree tree = await repository.GetTreeAsync(commit.TreeRoot);\n\n    foreach (Hash childHash in tree.Children)\n    {\n        PrimitiveTreeEntry child = await repository.GetTreeAsync(childHash);\n\n        Console.WriteLine($\"Hash: {child.Hash}\");\n        Console.WriteLine($\"Name: {child.Name}\");\n        Console.WriteLine($\"Modes: {child.Modes}\");\n    }\n}\n```\n\n### Read blob by stream\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\") is PrimitiveCommit commit)\n{\n    PrimitiveTree tree = await repository.GetTreeAsync(commit.TreeRoot);\n\n    foreach (Hash childHash in tree.Children)\n    {\n        PrimitiveTreeEntry child = await repository.GetTreeAsync(childHash);\n        if (child.Modes.HasFlag(PrimitiveModeFlags.File))\n        {\n            using Stream stream = await repository.OpenBlobAsync(child.Hash);\n\n            // (You can access the blob...)\n        }\n    }\n}\n```\n\n### Reading a submodule in commit tree.\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\") is PrimitiveCommit commit)\n{\n    PrimitiveTree tree = await repository.GetTreeAsync(commit.TreeRoot);\n\n    foreach (Hash childHash in tree.Children)\n    {\n        PrimitiveTreeEntry child = await repository.GetTreeAsync(childHash);\n\n        // If this tree entry is a submodule.\n        if (child.SpecialModes == PrimitiveSpecialModes.SubModule)\n        {\n            // The argument must be a \"tree path\".\n            // It is the sequence of all paths from the repository root up to this entry.\n            using var subModuleRepository = await repository.OpenSubModuleAsync(\n                new[] { child });\n\n            if (await repository.GetCommitAsync(\n                child.Hash) is PrimitiveCommit subModuleCommit)\n            {\n                // ...\n            }\n        }\n    }\n}\n```\n\n### Traverse a commit through primary commits\n\n```csharp\nif (await repository.GetCommitAsync(\n    \"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\") is PrimitiveCommit commit)\n{\n    while (true)\n    {\n        Console.WriteLine($\"Hash: {commit.Hash}\");\n        Console.WriteLine($\"Author: {commit.Author}\");\n        Console.WriteLine($\"Committer: {commit.Committer}\");\n        Console.WriteLine($\"Message: {commit.Message}\");\n\n        // Bottom of branch.\n        if (commit.Parents.Length == 0)\n        {\n            break;\n        }\n\n        // Get primary parent.\n        Hash primary = commit.Parents[0];\n        if (await repository.GetCommitAsync(primary) is not PrimitiveCommit parent)\n        {\n            throw new Exception();\n        }\n\n        current = parent;\n    }\n}\n```\n\n----\n\n## Samples (Others)\n\n### SHA1 hash operations\n\n```csharp\nHash hashFromString = \"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\";\nHash hashFromArray = new byte[] { 0x12, 0x05, 0xdc, ... };\n\nvar hashFromStringConstructor =\n    new Hash(\"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\");\nvar hashFromArrayConstructor =\n    new Hash(new byte[] { 0x12, 0x05, 0xdc, ... });\n\nif (Hash.TryParse(\"1205dc34ce48bda28fc543daaf9525a9bb6e6d10\", out Hash hash))\n{\n    // ...\n}\n\nCommit commit = ...;\nHash targetHash = commit;\n```\n\n### Enumerate remote urls\n\n```csharp\nforeach (KeyValuePair\u003cstring, string\u003e entry in repository.RemoteUrls)\n{\n    Console.WriteLine($\"Remote: Name={entry.Key}, Url={entry.Value}\");\n}\n```\n\n\n----\n\n## Contributed (Thanks!)\n\n* Stash implementation: [Julien Richard](https://github.com/jairbubbles)\n\n## License\n\nApache-v2\n\n## History\n\n* 1.10.0:\n  * Added Git worktree detection. (#15)\n* 1.9.0:\n  * Supported multiple same named branches.\n  * Added .NET 9.0 tfm.\n  * Fixed IOR exception when triggered loading large offset table. (#14)\n* 1.8.0:\n  * Added file system abstraction interface called `IFileSystem`.\n    * This interface allows repository access independent of the local file system.\n    * Currently undocumented, but there is a `StandardFileSystem` class that uses `System.IO` as its default implementation, so you may want to refer to this class.\n* 1.7.0:\n  * Rebuilt on .NET 8.0 SDK.\n* 1.6.0:\n  * Added submodule accessor.\n  * Fixed invalid remote url entries at multiple declaration. (#10)\n* 1.5.0:\n  * Included .NET 8.0 RC2 assembly (`net8.0`).\n* 1.4.0:\n  * Improved stability to open metadata files, avoids file sharing violation.\n* 1.3.0:\n  * Fixed internal cached streams locked when disposed the repository. (#9)\n* 1.2.0:\n  * Added `Repository.getCurrentHead()` function on F# interface.\n  * Uses ArrayPool on both netcoreapp and netstandard2.1.\n* 1.1.0:\n  * Fixed causing path not found exception when lack these directories.\n  * Added debugger display attribute on some types.\n* 1.0.0:\n  * Reached 1.0.0 :tada:\n  * Fixed broken decoded stream for deltified stream with derived large-base-stream.\n* 0.13.0:\n  * Improved performance.\n* 0.12.0:\n  * Reduced the time taken to open structured repository when peeled-tag is available from packed-refs.\n  * The Tags interface has been rearranged.\n  * Added raw stream opener interfaces.\n  * Some bug fixed.\n* 0.11.0:\n  * The structured interface no longer reads commit information when it opens.\n    Instead, you must explicitly call `Branch.GetHeadCommitAsync()`,\n    but the open will be processed much faster.  \n  * Improved performance.\n* 0.10.0:\n  * Implemented stash interface.\n  * Improved performance.\n  * Fixed minor corruption on blob data when it arrives zero-sized deltified data.\n* 0.9.0:\n  * Exposed remote urls.\n  * Changed some type names avoid confliction.\n* 0.8.0:\n  * Added tree/blob accessors.\n  * Improved performance.\n* 0.7.0:\n  * Switched primitive interface types with prefix `Primitive`.\n  * Improved performance.\n  * Tested large repositories.\n* 0.6.0:\n  * Improved message handling on high-level interfaces.\n  * Re-implemented delta compression decoder.\n  * Supported both FETCH_HEAD and packed_refs parser.\n  * Improved performance.\n  * Removed index locker.\n  * Fixed contains invalid hash on annotated commit tag.\n  * Improved minor interface features.\n* 0.5.0:\n  * Supported deconstructor by F# active patterns.\n  * Downgraded at least F# version 5.\n* 0.4.0:\n  * Added F# binding.\n  * Fixed lack for head branch name.\n* 0.3.0:\n  * Supported ability for not found detection.\n* 0.2.0:\n  * The shape of the public interfaces are almost fixed.\n  * Improved high-level interfaces.\n  * Splitted core library (Preparation for F# binding)\n* 0.1.0:\n  * Initial release.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fgitreader","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkekyo%2Fgitreader","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkekyo%2Fgitreader/lists"}