Lightweight Git local repository traversal library.
Lightweight Git local repository traversal library.
Target | Pakcage |
---|---|
Any | |
F# binding |
Have you ever wanted to access information about your local Git repository in .NET? Explore and tag branches, get commit dates and contributor information, and read commit directory structures and files.
GitReader is written only managed code Git local repository traversal library for a wide range of .NET environments. It is lightweight, has a concise, easy-to-use interface, does not depend any other libraries, and does not contain native libraries, making it suitable for any environment.
Example:
using GitReader;
using GitReader.Structures;
// Open repository (With high-level interface)
using var repository =
await Repository.Factory.OpenStructureAsync(
"/home/kekyo/Projects/YourOwnLocalGitRepo");
// Found current head.
if (repository.Head is { } head)
{
Console.WriteLine($"Name: {head.Name}");
// Get head commit.
var commit = await head.GetHeadCommitAsync();
Console.WriteLine($"Hash: {commit.Hash}");
Console.WriteLine($"Author: {commit.Author}");
Console.WriteLine($"Committer: {commit.Committer}");
Console.WriteLine($"Subject: {commit.Subject}");
Console.WriteLine($"Body: {commit.Body}");
}
It has the following features:
This library was designed from the ground up to replace libgit2sharp
, on which RelaxVersioner depended.
It primarily fits the purpose of easily extracting commit information from a Git repository.
F# 5.0 or upper, it contains F# friendly signature definition.
(Asynchronous operations with Async
type, elimination of null values with Option
, etc.)
Note: All target framework variations are tested only newest it.
Install GitReader from NuGet.
GitReader has high-level interfaces and primitive interfaces.
Comprehensive sample code can be found in the samples directory. The following things are minimal code fragments.
The high-level interface is easily referenced by automatically reading much of the information tied to a commit.
using GitReader;
using GitReader.Structures;
using StructuredRepository repository =
await Repository.Factory.OpenStructureAsync(
"/home/kekyo/Projects/YourOwnLocalGitRepo");
// Found current head
if (repository.Head is Branch head)
{
Console.WriteLine($"Name: {head.Name}");
// Get the commit that this HEAD points to:
Commit commit = await head.GetHeadCommitAsync();
Console.WriteLine($"Hash: {commit.Hash}");
Console.WriteLine($"Author: {commit.Author}");
Console.WriteLine($"Committer: {commit.Committer}");
Console.WriteLine($"Subject: {commit.Subject}");
Console.WriteLine($"Body: {commit.Body}");
}
if (await repository.GetCommitAsync(
"1205dc34ce48bda28fc543daaf9525a9bb6e6d10") is Commit commit)
{
Console.WriteLine($"Hash: {commit.Hash}");
Console.WriteLine($"Author: {commit.Author}");
Console.WriteLine($"Committer: {commit.Committer}");
Console.WriteLine($"Subject: {commit.Subject}");
Console.WriteLine($"Body: {commit.Body}");
}
Branch branch = repository.Branches["develop"];
Console.WriteLine($"Name: {branch.Name}");
Console.WriteLine($"IsRemote: {branch.IsRemote}");
Commit commit = await branch.GetHeadCommitAsync();
Console.WriteLine($"Hash: {commit.Hash}");
Console.WriteLine($"Author: {commit.Author}");
Console.WriteLine($"Committer: {commit.Committer}");
Console.WriteLine($"Subject: {commit.Subject}");
Console.WriteLine($"Body: {commit.Body}");
Tag tag = repository.Tags["1.2.3"];
Console.WriteLine($"Name: {tag.Name}");
Console.WriteLine($"Type: {tag.Type}");
Console.WriteLine($"ObjectHash: {tag.ObjectHash}");
// If present the annotation?
if (tag.HasAnnotation)
{
// Get tag annotation.
Annotation annotation = await tag.GetAnnotationAsync();
Console.WriteLine($"Tagger: {annotation.Tagger}");
Console.WriteLine($"Message: {annotation.Message}");
}
// If tag is a commit tag?
if (tag.Type == ObjectTypes.Commit)
{
// Get the commit indicated by the tag.
Commit commit = await tag.GetCommitAsync();
// ...
}
if (await repository.GetCommitAsync(
"1205dc34ce48bda28fc543daaf9525a9bb6e6d10") is Commit commit)
{
// The ReadOnlyArray<T> class is used to protect the inner array.
// Usage is the same as for general collections such as List<T>.
ReadOnlyArray<Branch> branches = commit.Branches;
ReadOnlyArray<Tag> tags = commit.Tags;
// ...
}
foreach (Branch branch in repository.Branches.Values)
{
Console.WriteLine($"Name: {branch.Name}");
Console.WriteLine($"IsRemote: {branch.IsRemote}");
Commit commit = await branch.GetHeadCommitAsync();
Console.WriteLine($"Hash: {commit.Hash}");
Console.WriteLine($"Author: {commit.Author}");
Console.WriteLine($"Committer: {commit.Committer}");
Console.WriteLine($"Subject: {commit.Subject}");
Console.WriteLine($"Body: {commit.Body}");
}
foreach (Tag tag in repository.Tags.Values)
{
Console.WriteLine($"Name: {tag.Name}");
Console.WriteLine($"Type: {tag.Type}");
Console.WriteLine($"ObjectHash: {tag.ObjectHash}");
}
foreach (Stash stash in repository.Stashes)
{
Console.WriteLine($"Commit: {stash.Commit.Hash}");
Console.WriteLine($"Committer: {stash.Committer}");
Console.WriteLine($"Message: {stash.Message}");
}
if (await repository.GetCommitAsync(
"6961a50ef3ad4e43ed9774daffd8457d32cf5e75") is Command commit)
{
Commit[] parents = await commit.GetParentCommitsAsync();
foreach (Commit parent in parents)
{
Console.WriteLine($"Hash: {parent.Hash}");
Console.WriteLine($"Author: {parent.Author}");
Console.WriteLine($"Committer: {parent.Committer}");
Console.WriteLine($"Subject: {parent.Subject}");
Console.WriteLine($"Body: {parent.Body}");
}
}
Tree information is the tree structure of directories and files that are placed when a commit is checked out. The code shown here does not actually 'check out', but reads these structures as information.
if (await repository.GetCommitAsync(
"6961a50ef3ad4e43ed9774daffd8457d32cf5e75") is Command commit)
{
TreeRoot treeRoot = await commit.GetTreeRootAsync();
foreach (TreeEntry entry in treeRoot.Children)
{
Console.WriteLine($"Hash: {entry.Hash}");
Console.WriteLine($"Name: {entry.Name}");
Console.WriteLine($"Modes: {entry.Modes}");
}
}
if (await repository.GetCommitAsync(
"6961a50ef3ad4e43ed9774daffd8457d32cf5e75") is Command commit)
{
TreeRoot treeRoot = await commit.GetTreeRootAsync();
foreach (TreeEntry entry in treeRoot.Children)
{
// For Blob, the instance type is `TreeBlobEntry`.
if (entry is TreeBlobEntry blob)
{
using Stream stream = await blob.OpenBlobAsync();
// (You can access the blob...)
}
}
}
A commit in Git can have multiple parent commits. This occurs with merge commits, where there are links to all parent commits. The first parent commit is called the "primary commit" and is always present except for the first commit in the repository.
Use GetPrimaryParentCommitAsync()
to retrieve the primary commit,
and use GetParentCommitsAsync()
to get links to all parent commits.
As a general thing about Git, it is important to note that the parent-child relationship of commits (caused by branching and merging), always expressed as one direction, from "child" to "parent".
This is also true for the high-level interface; there is no interface for referencing a child from its parent. Therefore, if you wish to perform such a search, you must construct the link in the reverse direction on your own.
The following example recursively searches for a parent commit from a child commit.
Branch branch = repository.Branches["develop"];
Console.WriteLine($"Name: {branch.Name}");
Console.WriteLine($"IsRemote: {branch.IsRemote}");
Commit? current = await branch.GetHeadCommitAsync();
// Continue as long as the parent commit exists.
while (current != null)
{
Console.WriteLine($"Hash: {current.Hash}");
Console.WriteLine($"Author: {current.Author}");
Console.WriteLine($"Committer: {current.Committer}");
Console.WriteLine($"Subject: {current.Subject}");
Console.WriteLine($"Body: {current.Body}");
// Get primary parent commit.
current = await current.GetPrimaryParentCommitAsync();
}
The high-level interface is implemented internally using these primitive interfaces. We do not have a complete list of all examples, so we recommend referring to the GitReader code if you need information.
using GitReader;
using GitReader.Primitive;
using PrimitiveRepository repository =
await Repository.Factory.OpenPrimitiveAsync(
"/home/kekyo/Projects/YourOwnLocalGitRepo");
if (await repository.GetCurrentHeadReferenceAsync() is PrimitiveReference head)
{
if (await repository.GetCommitAsync(head) is PrimitiveCommit commit)
{
Console.WriteLine($"Hash: {commit.Hash}");
Console.WriteLine($"Author: {commit.Author}");
Console.WriteLine($"Committer: {commit.Committer}");
Console.WriteLine($"Message: {commit.Message}");
}
}
if (await repository.GetCommitAsync(
"1205dc34ce48bda28fc543daaf9525a9bb6e6d10") is PrimitiveCommit commit)
{
Console.WriteLine($"Hash: {commit.Hash}");
Console.WriteLine($"Author: {commit.Author}");
Console.WriteLine($"Committer: {commit.Committer}");
Console.WriteLine($"Message: {commit.Message}");
}
PrimitiveReference head = await repository.GetBranchHeadReferenceAsync("develop");
if (await repository.GetCommitAsync(head) is PrimitiveCommit commit)
{
Console.WriteLine($"Hash: {commit.Hash}");
Console.WriteLine($"Author: {commit.Author}");
Console.WriteLine($"Committer: {commit.Committer}");
Console.WriteLine($"Message: {commit.Message}");
}
PrimitiveReference[] branches = await repository.GetBranchHeadReferencesAsync();
foreach (PrimitiveReference branch in branches)
{
Console.WriteLine($"Name: {branch.Name}");
Console.WriteLine($"Commit: {branch.Commit}");
}
PrimitiveReference[] branches = await repository.GetRemoteBranchHeadReferencesAsync();
foreach (PrimitiveReference branch in branches)
{
Console.WriteLine($"Name: {branch.Name}");
Console.WriteLine($"Commit: {branch.Commit}");
}
PrimitiveTagReference[] tagReferences = await repository.GetTagReferencesAsync();
foreach (PrimitiveTagReference tagReference in tagReferences)
{
PrimitiveTag tag = await repository.GetTagAsync(tagReference);
Console.WriteLine($"Hash: {tag.Hash}");
Console.WriteLine($"Type: {tag.Type}");
Console.WriteLine($"Name: {tag.Name}");
Console.WriteLine($"Tagger: {tag.Tagger}");
Console.WriteLine($"Message: {tag.Message}");
}
if (await repository.GetCommitAsync(
"1205dc34ce48bda28fc543daaf9525a9bb6e6d10") is PrimitiveCommit commit)
{
PrimitiveTree tree = await repository.GetTreeAsync(commit.TreeRoot);
foreach (Hash childHash in tree.Children)
{
PrimitiveTreeEntry child = await repository.GetTreeAsync(childHash);
Console.WriteLine($"Hash: {child.Hash}");
Console.WriteLine($"Name: {child.Name}");
Console.WriteLine($"Modes: {child.Modes}");
}
}
if (await repository.GetCommitAsync(
"1205dc34ce48bda28fc543daaf9525a9bb6e6d10") is PrimitiveCommit commit)
{
PrimitiveTree tree = await repository.GetTreeAsync(commit.TreeRoot);
foreach (Hash childHash in tree.Children)
{
PrimitiveTreeEntry child = await repository.GetTreeAsync(childHash);
if (child.Modes.HasFlag(PrimitiveModeFlags.File))
{
using Stream stream = await repository.OpenBlobAsync(child.Hash);
// (You can access the blob...)
}
}
}
if (await repository.GetCommitAsync(
"1205dc34ce48bda28fc543daaf9525a9bb6e6d10") is PrimitiveCommit commit)
{
while (true)
{
Console.WriteLine($"Hash: {commit.Hash}");
Console.WriteLine($"Author: {commit.Author}");
Console.WriteLine($"Committer: {commit.Committer}");
Console.WriteLine($"Message: {commit.Message}");
// Bottom of branch.
if (commit.Parents.Length == 0)
{
break;
}
// Get primary parent.
Hash primary = commit.Parents[0];
if (await repository.GetCommitAsync(primary) is not PrimitiveCommit parent)
{
throw new Exception();
}
current = parent;
}
}
Hash hashFromString = "1205dc34ce48bda28fc543daaf9525a9bb6e6d10";
Hash hashFromArray = new byte[] { 0x12, 0x05, 0xdc, ... };
var hashFromStringConstructor =
new Hash("1205dc34ce48bda28fc543daaf9525a9bb6e6d10");
var hashFromArrayConstructor =
new Hash(new byte[] { 0x12, 0x05, 0xdc, ... });
if (Hash.TryParse("1205dc34ce48bda28fc543daaf9525a9bb6e6d10", out Hash hash))
{
// ...
}
Commit commit = ...;
Hash targetHash = commit;
foreach (KeyValuePair<string, string> entry in repository.RemoteUrls)
{
Console.WriteLine($"Remote: Name={entry.Key}, Url={entry.Value}");
}
Apache-v2
Branch.GetHeadCommitAsync()
,
but the open will be processed much faster.Primitive
.