Skip to content

frblondin/GitObjectDb

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

GitObjectDb simplifies the configuration management versioning by backing it in Git.

Name Badge
GitObjectDb NuGet Badge
GitObjectDb.SystemTextJson NuGet Badge
GitObjectDb.YamlDotNet NuGet Badge
GitObjectDb.Api.OData NuGet Badge
GitObjectDb.Api.GraphQL NuGet Badge
GitObjectDb.Api.ProtoBuf NuGet Badge
GitObjectDb.Api.ProtoBuf.Model NuGet Badge

Build Status

Overview

GitObjectDb is designed to simplify the configuration management versioning. It does so by removing the need for hand-coding the commands needed to interact with Git.

The Git repository is used as a pure database as the files containing the serialized copy of the objects are never fetched in the filesystem. GitObjectDb only uses the blob storage provided by Git.

Here's a simple example:

  1. Define your own repository data model:
    [GitFolder("Applications")]
    public record Application : Node
    {
        public string Name { get; init; }
    
        public string Description { get; init; }
    }
    [GitFolder("Pages")]
    public record Table : Node
    {
        public string Name { get; init; }
    
        public string Description { get; init; }
    
        [StoreAsSeparateFile(Extension = "txt")]
        public string? RichContent { get; init; }
    }
  2. Manipulate objects as follows:
    var existingApplication = connection.Lookup<Application>("main", "applications", new UniqueId(id));
    var newTable = new Table { ... };
    connection
        .Update("main", c => c.CreateOrUpdate(newTable, parent: existingApplication))
    	.Commit(new("Added new table.", author, committer));

Features

Structured & unstructured data storage

var node = new SomeNode
{
    SomeProperty = "Value stored as json",
    RichContent = "Value stored as raw text in separate Git blob, next to primary one",
}:

... gets stored in Git as follows:

  • zerzrzrz.json
{
  "$type": "Sample.SomeNode",
  "id": "zerzrzrz",
  "someProperty": "Value stored as json"
}
  • zerzrzrz.RichContent.txt
Value stored many dynamic resources in separate Git blob, next to primary one

You can also store resources as separate files:

new Resource(node, "Some/Folder", "File.txt", new Resource.Data("Value stored in a separate file in <node path>/Resources/Some/Folder/File.txt"));

Branching

connection
    .Update("main", c => c.CreateOrUpdate(table with { Description = newDescription }))
    .Commit(new("Some message", signature, signature));
connection.Checkout("newBranch", "main~1");
connection
    .Update("main", c => c.CreateOrUpdate(table with { Name = newName }))
    .Commit(new("Another message", signature, signature));

Comparing commits

var comparison = connection.Compare("main~5", "main");
var nodeChanges = comparison.Modified.OfType<Change.NodeChange>();

Node references

Node references allows linking existing nodes in a repository:

public record Order : Node
{
    public Client Client { get; set; }
    // ...
}
public record Client : Node
{
    // ...
}
// Nodes get loaded with their references (using a shared )
var cache = new Dictionary<DataPath, ITreeItem>();
var order = connection.GetNodes<Order>("main", referenceCache: cache).First();
Console.WriteLine(order.Client.Id);

Merge, Rebase, Cherry-pick

// main:      A---B    A---B
//             \    ->  \   \
// newBranch:   C        C---x

connection
    .Update("main", c => c.CreateOrUpdate(table with { Description = newDescription }))
    .Commit(new("B", signature, signature));
connection.Repository.Branches.Add("newBranch", "main~1");
connection
    .Update("newBranch", c => c.CreateOrUpdate(table with { Name = newName }))
    .Commit(new("C", signature, signature));

sut.Merge(upstreamCommittish: "main");

Node versioning management

Imagine a scenario where you define in your code a first type:

[GitFolder(FolderName = "Items", UseNodeFolders = false)]
[IsDeprecatedNodeType(typeof(SomeNodeV2))]
private record SomeNodeV1 : Node
{
    public int Flags { get; set; }
}

[GitFolder(FolderName = "Items", UseNodeFolders = false)]
private record SomeNodeV2 : Node
{
    public BindingFlags TypedFlags { get; set; }
}

You then want to introduce a new change so that the Flags property contains more meaningful information, relying on enums:

[GitFolder(FolderName = "Items", UseNodeFolders = false)]
private record SomeNodeV2 : Node
{
    public BindingFlags TypedFlags { get; set; }
}

All you need to do is to #1 add the [IsDeprecatedNodeType(typeof(SomeNodeV2))] attribute. This will instruct the deserializer to convert nodes to new version, using a converter. #2 converter needs to be provided in the model. You can use AutoMapper or other tools at your convenience.

[GitFolder(FolderName = "Items", UseNodeFolders = false)]
[IsDeprecatedNodeType(typeof(SomeNodeV2))]
private record SomeNodeV1 : Node
{
    // ...
}
var model = new ConventionBaseModelBuilder()
    .RegisterType<SomeNodeV1>()
    .RegisterType<SomeNodeV2>()
    .AddDeprecatedNodeUpdater(UpdateDeprecatedNode)
    .Build();
Node UpdateDeprecatedNode(Node old, Type targetType)
{
    var nodeV1 = (SomeNodeV1)old;
    return new SomeNodeV2
    {
        Id = old.Id,
        TypedFlags = (BindingFlags)nodeV1.Flags,
    };
}

Documentation

See documentation.

Prerequisites

  • .NET Standard 2.0 or 2.1

Online resources

Quick contributing guide

  • Fork and clone locally
  • Create a topic specific branch. Add some nice feature. Do not forget the tests ;-)
  • Send a Pull Request to spread the fun!

License

The MIT license (Refer to the LICENSE file).