From cbfcf47bb54090f847138b7cc35a90712901bb5c Mon Sep 17 00:00:00 2001 From: Steven Kuhn Date: Wed, 4 Dec 2019 19:29:49 -0600 Subject: [PATCH] Repo fixes, remove rule application, and update documentation (#54) This allows checkout of a branch when no commits exists, create a tracked branch, and remove a rule application from a branch. This also update the documentation to have a better quickstart sample. This fixes #51, #52, #53. --- GitVersion.yml | 2 +- README.md | 124 ++++++------ docs/coverpage.md | 2 +- docs/index.html | 4 +- docs/installation.md | 6 +- docs/introduction.md | 60 ++++++ docs/usage.md | 177 ++++++++++++----- .../IInRuleGitRepository.cs | 67 ++----- .../IInRuleGitRepositoryExtensions.cs | 186 +++++++++++++++++ .../InRuleGitRepository.cs | 187 +++++++++--------- .../Fixtures/GitRepositoryFixture.cs | 32 ++- .../InRuleGitRepositoryTests/CheckoutTests.cs | 49 ++++- .../DocumentationTests.cs | 150 ++++++++++++++ .../GetRuleApplicationsTests.cs | 9 + 14 files changed, 798 insertions(+), 257 deletions(-) create mode 100644 src/Sknet.InRuleGitStorage/IInRuleGitRepositoryExtensions.cs create mode 100644 test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/DocumentationTests.cs diff --git a/GitVersion.yml b/GitVersion.yml index 5d6ab39..4f7d716 100644 --- a/GitVersion.yml +++ b/GitVersion.yml @@ -1,5 +1,5 @@ mode: ContinuousDelivery -next-version: 0.1.0 +next-version: 0.2.0 branches: {} ignore: sha: [] diff --git a/README.md b/README.md index 7fc840e..bdb65d9 100644 --- a/README.md +++ b/README.md @@ -1,80 +1,88 @@ -# Git Storage for InRule +InRuleGitStorage +==== -This project adds support for storing and managing InRule rule applications in a custom Git repository. +[![Nuget](https://img.shields.io/nuget/vpre/Sknet.InRuleGitStorage)](https://www.nuget.org/packages/Sknet.InRuleGitStorage) -## Getting Started +This project allows you to store and manage your [InRule](https://www.inrule.com/)® business rules in a Git repository as an alternative to the built-in support of the file system and irCatalog. -### Installing +# Features + +- Initialize a new InRule git repository +- Open an existing InRule git repository +- Clone, pull from, and push to a remote InRule git repository +- Create, remove, and checkout a branch +- Commit (serialize a `RuleApplicationDef` into a git commit) +- Merge branches (_merge conflict support is a work in progress_) +- Get rule application (deserialize the current branch into a `RuleApplicationDef`) +- Get a list of rule applications + +# Quickstart + +## Installing ```powershell Install-Package Sknet.InRuleGitStorage -IncludePrerelease ``` -## Usage - -### Create a new repository -```csharp -InRuleGitRepository.Init(@"C:\path\to\your\repo"); +```batch +dotnet add package Sknet.InRuleGitStorage --version 0.2.0 ``` -### Create your first commit -```csharp -using (var repo = InRuleGitRepository.Open(@"C:\path\to\your\repo")) -{ - var ruleAppDef = new RuleApplicationDef("MyRuleApp"); +## Basic example - var identity = new Identity("Alex Doe", "alex.doe@example.org"); - var signature = new Signature(identity, DateTimeOffset.Now); - repo.Commit(ruleAppDef, "My first commit", signature, signature); -} -``` - -### Get a rule application from the current branch ```csharp -using (var repo = InRuleGitRepository.Open(@"C:\path\to\your\repo")) +// Create a new repository in a local directory +InRuleGitRepository.Init("/path/to/local/repo"); + +// Get a new instance of your local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/local/repo")) { - var ruleAppDef = repo.GetRuleApplication("MyRuleApp"); + // Create a new rule application and commit it to the "master" branch + var ruleApp = new RuleApplicationDef("QuickstartSample"); + repo.Commit(ruleApp, "Add quickstart sample rule application"); + + // Get the rule application from the Git repository + ruleApp = repo.GetRuleApplication("QuickstartSample"); } ``` -## Examples - -### Create multiple commits across different branches +## Remote repository example ```csharp -var repoPath = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), - "MyInRuleRepository"); - -var identity = new Identity("John Doe", "john.doe@example.org"); +// Clone the public samples repository to a local directory +InRuleGitRepository.Clone( + sourceUrl: "https://github.com/stevenkuhn/InRuleGitStorage-Samples.git", + destinationPath: "/path/to/local/repo"); -// Create a repository in the current user's _My Documents_ folder -InRuleGitRepository.Init(repoPath); -using (var repo = InRuleGitRepository.Open(repoPath)) +// Get a new instance of your local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/local/repo")) { - // Update the rule application and create commits in `master`. - var ruleAppDef = new RuleApplicationDef("MyRuleApp"); - var entityDef = ruleAppDef.Entities.Add(new EntityDef("MyEntity")); - - repo.Commit(ruleAppDef, "My first commit", - new Signature(identity, DateTimeOffset.Now), - new Signature(identity, DateTimeOffset.Now)); - - entityDef.Fields.Add(new FieldDef("MyField1", DataType.String)); - - repo.Commit(ruleAppDef, "My second commit", - new Signature(identity, DateTimeOffset.Now), - new Signature(identity, DateTimeOffset.Now)); - - // Create a new branch called `my-new-branch` and set it as the current branch. - repo.CreateBranch("my-new-branch"); - repo.Checkout("my-new-branch"); - entityDef.Fields.Add(new FieldDef("MyField2", DataType.String)); - entityDef.Fields.Add(new FieldDef("MyField3", DataType.Integer)); - - // Update the rule application and commit the change to `my-new-branch`. - repo.Commit(ruleAppDef, "My third commit", - new Signature(identity, DateTimeOffset.Now), - new Signature(identity, DateTimeOffset.Now)); + // Create a local branch that is tracked to the remote "v0.2.0" branch + repo.CreateTrackedBranch("v0.2.0", "origin"); + + // Switch the current branch to the newly created tracked branch + repo.Checkout("v0.2.0"); + + // Create a local branch from the "v0.2.0" branch + repo.CreateBranch("invoice-date-field"); + + // Switch the current branch to the newly created local branch + repo.Checkout("invoice-date-field"); + + // Get the InvoiceSample rule application from the repository, add an invoice date + // field, and commit that change to the current branch + var ruleApp = repo.GetRuleApplication("InvoiceSample"); + ruleApp.Entities["Invoice"].Fields.Add(new FieldDef("Date", DataType.DateTime)); + repo.Commit(ruleApp, "Add invoice date field"); + + // Switch back to the previous branch that does not have the field change + repo.Checkout("v0.2.0"); + + // Merge the invoice date field change into the current branch + repo.Merge("invoice-date-field"); + + // Delete the original branch containing the invoice date field change since the + // change now exists in the "v0.2.0" branch + repo.RemoveBranch("invoice-date-field"); } ``` \ No newline at end of file diff --git a/docs/coverpage.md b/docs/coverpage.md index 166754b..2d5c622 100644 --- a/docs/coverpage.md +++ b/docs/coverpage.md @@ -1,6 +1,6 @@ ![logo](assets/img/logo.svg ':size=200') -# InRuleGitStorage0.1.0 +# InRuleGitStorage0.2.0 >Store and manage your InRule® business rules in a Git repository. diff --git a/docs/index.html b/docs/index.html index 4a11c56..3f88b7b 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2,9 +2,9 @@ - Git Storage for InRule + Sknet.InRuleGitStorage - + diff --git a/docs/installation.md b/docs/installation.md index e8dfee3..8ba10c8 100644 --- a/docs/installation.md +++ b/docs/installation.md @@ -5,17 +5,17 @@ InRuleGitStorage is a NuGet package that can be installed using a package instal ## Package Manager ```powershell -PM> Install-Package Sknet.InRuleGitStorage -Version 0.1.0 +PM> Install-Package Sknet.InRuleGitStorage -Version 0.2.0 ``` ## .NET CLI ```batch -dotnet add package Sknet.InRuleGitStorage --version 0.1.0 +dotnet add package Sknet.InRuleGitStorage --version 0.2.0 ``` ## Package Reference ```xml - + ``` \ No newline at end of file diff --git a/docs/introduction.md b/docs/introduction.md index 46a8c6a..6d5c871 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -15,3 +15,63 @@ This project allows you to store and manage your [InRule](https://www.inrule.com - Get rule application (deserialize the current branch into a `RuleApplicationDef`) - Get a list of rule applications +## Quickstart + +### Basic example + +```csharp +// Create a new repository in a local directory +InRuleGitRepository.Init("/path/to/local/repo"); + +// Get a new instance of your local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/local/repo")) +{ + // Create a new rule application and commit it to the "master" branch + var ruleApp = new RuleApplicationDef("QuickstartSample"); + repo.Commit(ruleApp, "Add quickstart sample rule application"); + + // Get the rule application from the Git repository + ruleApp = repo.GetRuleApplication("QuickstartSample"); +} +``` + +### Remote repository example + +```csharp +// Clone the public samples repository to a local directory +InRuleGitRepository.Clone( + sourceUrl: "https://github.com/stevenkuhn/InRuleGitStorage-Samples.git", + destinationPath: "/path/to/local/repo"); + +// Get a new instance of your local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/local/repo")) +{ + // Create a local branch that is tracked to the remote "v0.2.0" branch + repo.CreateBranch("v0.2.0", "origin"); + + // Switch the current branch to the newly created tracked branch + repo.Checkout("v0.2.0"); + + // Create a local branch from the "v0.2.0" branch + repo.CreateBranch("invoice-date-field"); + + // Switch the current branch to the newly created local branch + repo.Checkout("invoice-date-field"); + + // Get the InvoiceSample rule application from the repository, add an invoice date + // field, and commit that change to the current branch + var ruleApp = repo.GetRuleApplication("InvoiceSample"); + ruleApp.Entities["Invoice"].Fields.Add(new FieldDef("Date", DataType.DateTime)); + repo.Commit(ruleApp, "Add invoice date field"); + + // Switch back to the previous branch that does not have the field change + repo.Checkout("v0.2.0"); + + // Merge the invoice date field change into the current branch + repo.Merge("invoice-date-field"); + + // Delete the original branch containing the invoice date field change since the + // change now exists in the "v0.2.0" branch + repo.RemoveBranch("invoice-date-field"); +} +``` \ No newline at end of file diff --git a/docs/usage.md b/docs/usage.md index 615af18..69a35ce 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -1,25 +1,6 @@ # Usage -## Checkout - ->Switches the current branch to the specified branch name. - -Example: - -```csharp -// Initialize a new repository -using (var repo = InRuleRepository.Init("/path/to/repo")) -{ - // Create a new branch - repo.CreateBranch("mybranch"); - - // Switch from the current "master" branch to "mybranch"; - // future commits will be made this this new branch - repo.Checkout("mybranch"); -} -``` - -## Clone +## Clone  static method >Clones a remote InRule git repository to a new local repository. @@ -31,7 +12,7 @@ InRuleGitRepository.Clone("https://github.com/owner/repo.git", "/path/to/local/r // Clone with username/password authentication InRuleGitRepository.Clone("https://github.com/owner/repo.git", "/path/to/local/repo", - new Sknet.InRuleGitStorage.CloneOptions + new CloneOptions { CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials { @@ -41,6 +22,61 @@ InRuleGitRepository.Clone("https://github.com/owner/repo.git", "/path/to/local/r }); ``` +## Init  static method + +>Initializes a new InRule git repository at the specified path. + +Example: + +```csharp +// Initialize a new repository to the specified path +InRuleGitRepository.Init("/path/to/local/repo"); +``` + +## IsValid  static method + +>Checks if the specified path is a valid InRule Git repository. + +Example: + +```csharp +// Returns true if the path is valid Git repository; false otherwise +var isValidRepo = InRuleGitRepository.IsValid("/path/to/local/repo"); +``` + +## Open  static method + +>Initializes a new instance of InRuleGitRepository for an existing repository at the specified path. + +Example: + +```csharp +// Get a new instance of the local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/local/repo")) +{ + // Perform any repo operations here +} +``` + +## Checkout + +>Switches the current branch to the specified branch name. + +Example: + +```csharp +// Initialize a new repository +using (var repo = InRuleGitRepository.Init("/path/to/repo")) +{ + // Create a new branch + repo.CreateBranch("mybranch"); + + // Switch from the current "master" branch to "mybranch"; + // future commits will be made this this new branch + repo.Checkout("mybranch"); +} +``` + ## Commit >Stores the content of the specified Rule Application in the current branch as a new commit. @@ -49,7 +85,7 @@ Example: ```csharp // Initialize a new repository -using (var repo = InRuleRepository.Init("/path/to/repo")) +using (var repo = InRuleGitRepository.Init("/path/to/repo")) { // Create a new rule application var ruleApplication = new RuleApplicationDef("MyRuleApplication"); @@ -67,7 +103,7 @@ Example: ```csharp // Initialize a new repository -using (var repo = InRuleRepository.Init("/path/to/repo")) +using (var repo = InRuleGitRepository.Init("/path/to/repo")) { // Create a new branch; however the current checked out branch // is still "master" unless a call to Checkout("mybranch") is made @@ -75,49 +111,85 @@ using (var repo = InRuleRepository.Init("/path/to/repo")) } ``` -## Fetch - ->Fetches all of the latest changes from a remote InRule git repository. +>Creates a new tracked branch from the remote branch of the same name. Example: ```csharp +// Clone the remote repository to a local directory +InRuleGitRepository.Clone("https://github.com/owner/repo.git", "/path/to/local/repo"); + +// Get a new instance of the local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/local/repo")) +{ + // Create a new tracked branch based on the remote branch; however the current + // checked out branch remains the same unless a call to Checkout("mybranch") + // is made + repo.CreateBranch("mybranch", "origin"); +} ``` -## GetRuleApplication +## Fetch ->Gets a rule application from the current branch. +>Fetches all of the latest changes from a remote InRule git repository. Example: ```csharp -``` - -## GetRuleApplications +// Clone the remote repository to a local directory +InRuleGitRepository.Clone("https://github.com/owner/repo.git", "/path/to/local/repo"); ->Gets a collection of references to available rule application from the current branch. +// Get a new instance of the local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/local/repo")) +{ + // Fetch all the latest changes from the "origin" remote + repo.Fetch(); -Example: + // Add another remote and fetch all the latest changes from that remote + repo.Remotes.Add("another-remote", "https://github.com/owner/another-repo.git"); + repo.Fetch("another-remote"); -```csharp + // Fetch all the latest changes from "origin" using authentication + repo.Fetch(new FetchOptions + { + CredentialsProvider = (url, usernameFromUrl, types) => new UsernamePasswordCredentials + { + Username = "github_username", + Password = "github_accesstoken" + } + }) +} ``` -## Init +## GetRuleApplication ->Initializes a new InRule git repository at the specified path. +>Gets a rule application from the current branch. Example: ```csharp +// Get a new instance of the local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/repo")) +{ + // Get a RuleApplicationDef instance from the current branch in the repository + var ruleApplication = repo.GetRuleApplication("MyRuleApplication"); +} ``` -## IsValid +## GetRuleApplications ->Checks if the specified path is a valid InRule git repository. +>Gets a collection of references to available rule application from the current branch. Example: ```csharp +// Get a new instance of the local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/repo")) +{ + // Get collection of rule application information and latest commit info for each + // rule application for the current branch in the repository + var ruleApplications = repo.GetRuleApplications(); +} ``` ## Merge @@ -129,15 +201,6 @@ Example: ```csharp ``` -## Open - ->Initializes a new instance of InRuleGitRepository for an existing repository at the specified path. - -Example: - -```csharp -``` - ## Pull >Fetches all of the changes from a remote InRule git repository and merge into the current branch. @@ -163,4 +226,24 @@ Example: Example: ```csharp +// Get a new instance of the local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/repo")) +{ + // Remove the branch; it must not be the current checked out branch + repo.RemoveBranch("mybranch"); +} +``` + +## RemoveRuleApplication + +>Creates a commit that removes a rule application from the current branch + +Example: +```csharp +// Get a new instance of the local InRule Git repository +using (var repo = InRuleGitRepository.Open("/path/to/repo")) +{ + // Create a commit that removes MyRuleApplication from the current branch + repo.RemoveRuleApplication("MyRuleApplication", "Remove remove application"); +} ``` \ No newline at end of file diff --git a/src/Sknet.InRuleGitStorage/IInRuleGitRepository.cs b/src/Sknet.InRuleGitStorage/IInRuleGitRepository.cs index ebeb3c9..38e1cc3 100644 --- a/src/Sknet.InRuleGitStorage/IInRuleGitRepository.cs +++ b/src/Sknet.InRuleGitStorage/IInRuleGitRepository.cs @@ -22,15 +22,6 @@ public interface IInRuleGitRepository : IDisposable /// The branch name to switch to. void Checkout(string branchName); - /// - /// Store the content of the specified rule application in the current - /// branch as a new commit. - /// - /// The rule application to store in the repository. - /// The description of why a change was made to the repository. - /// The generated commit containing the specified rule application and any existing rule applications. - Commit Commit(RuleApplicationDef ruleApplication, string message); - /// /// Store the content of the specified rule application in the current /// branch as a new commit. @@ -55,10 +46,12 @@ public interface IInRuleGitRepository : IDisposable Branch CreateBranch(string branchName); /// - /// Fetch all of the latest changes from a remote InRule git repository. + /// Create a new tracked branch from the remote branch of the same name. /// - /// The parameters that control the fetch behavior. - void Fetch(FetchOptions options); + /// The branch name for the new branch. + /// The name for the remote repository. + /// The created branch. + Branch CreateBranch(string branchName, string remote); /// /// Fetch all of the latest changes from a remote InRule git repository. @@ -81,15 +74,6 @@ public interface IInRuleGitRepository : IDisposable /// A collection of rule application references (not the actual rule applications) from the current branch. RuleApplicationGitInfo[] GetRuleApplications(); - /// - /// Perform a merge of the current branch and the specified branch, and - /// create a commit if there are no conflicts. - /// - /// The branch name to merge with the current branch. - /// The parameters that control the merge behavior. - /// The result of a merge of two trees and any conflicts. - MergeTreeResult Merge(string branchName, MergeOptions options); - /// /// Perform a merge of the current branch and the specified branch, and /// create a commit if there are no conflicts. @@ -100,32 +84,6 @@ public interface IInRuleGitRepository : IDisposable /// The result of a merge of two trees and any conflicts. MergeTreeResult Merge(string branchName, Signature merger, MergeOptions options); - /// - /// Fetch all of the changes from a remote InRule git repository and - /// merge into the current branch. - /// - /// The parameters that control the fetch and merge behavior. - /// The result of a merge of two trees and any conflicts. - MergeTreeResult Pull(PullOptions options); - - /// - /// Fetch all of the changes from a remote InRule git repository and - /// merge into the current branch. - /// - /// The signature to use for the merge. - /// The parameters that control the fetch and merge behavior. - /// The result of a merge of two trees and any conflicts. - MergeTreeResult Pull(Signature merger, PullOptions options); - - /// - /// Fetch all of the changes from a remote InRule git repository and - /// merge into the current branch. - /// - /// The name or URI for the remote repository. - /// The parameters that control the fetch and merge behavior. - /// The result of a merge of two trees and any conflicts. - MergeTreeResult Pull(string remote, PullOptions options); - /// /// Fetch all of the changes from a remote InRule git repository and /// merge into the current branch. @@ -136,12 +94,6 @@ public interface IInRuleGitRepository : IDisposable /// The result of a merge of two trees and any conflicts. MergeTreeResult Pull(string remote, Signature merger, PullOptions options); - /// - /// Push the current branch to a remote InRule git repository. - /// - /// The parameters that control the push behavior. - void Push(PushOptions options); - /// /// Push the current branch to a remote InRule git repository. /// @@ -159,5 +111,14 @@ public interface IInRuleGitRepository : IDisposable /// /// The branch name to remove. void RemoveBranch(string branchName); + + /// + /// Create a commit that removes the specified rule application from the current branch. + /// + /// The case-insensitive rule application name. + /// The description of why a change was made to the repository. + /// The signature of who made the change. + /// The signature of who added the change to the repository. + Commit RemoveRuleApplication(string ruleApplicationName, string message, Signature author, Signature committer); } } diff --git a/src/Sknet.InRuleGitStorage/IInRuleGitRepositoryExtensions.cs b/src/Sknet.InRuleGitStorage/IInRuleGitRepositoryExtensions.cs new file mode 100644 index 0000000..c55ab28 --- /dev/null +++ b/src/Sknet.InRuleGitStorage/IInRuleGitRepositoryExtensions.cs @@ -0,0 +1,186 @@ +using InRule.Repository; +using LibGit2Sharp; +using System; +using System.Runtime.CompilerServices; + +namespace Sknet.InRuleGitStorage +{ + /// + /// Represents extensions for the primary interface for storing and managing + /// InRule rule applications in a git repository. + /// + public static class IInRuleGitRepositoryExtensions + { + /// + /// Store the content of the specified rule application in the current + /// branch as a new commit. + /// + /// The Git repository instance. + /// The rule application to store in the repository. + /// The description of why a change was made to the repository. + /// The generated commit containing the specified rule application and any existing rule applications. + public static Commit Commit(this IInRuleGitRepository repository, RuleApplicationDef ruleApplication, string message) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + var signature = repository.Config.BuildSignature(DateTimeOffset.Now); + + return repository.Commit(ruleApplication, message, signature, signature); + } + + /// + /// Fetch all of the latest changes from a remote InRule git repository. + /// + /// The Git repository instance. + public static void Fetch(this IInRuleGitRepository repository) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + repository.Fetch(new FetchOptions()); + } + + /// + /// Fetch all of the latest changes from a remote InRule git repository. + /// + /// The Git repository instance. + /// The parameters that control the fetch behavior. + public static void Fetch(this IInRuleGitRepository repository, FetchOptions options) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + repository.Fetch("origin", options); + } + + /// + /// Perform a merge of the current branch and the specified branch, and + /// create a commit if there are no conflicts. + /// + /// The Git repository instance. + /// The branch name to merge with the current branch. + /// The result of a merge of two trees and any conflicts. + public static MergeTreeResult Merge(this IInRuleGitRepository repository, string branchName) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + return repository.Merge(branchName, new MergeOptions()); + } + + /// + /// Perform a merge of the current branch and the specified branch, and + /// create a commit if there are no conflicts. + /// + /// The Git repository instance. + /// The branch name to merge with the current branch. + /// The parameters that control the merge behavior. + /// The result of a merge of two trees and any conflicts. + public static MergeTreeResult Merge(this IInRuleGitRepository repository, string branchName, MergeOptions options) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + var signature = repository.Config.BuildSignature(DateTimeOffset.Now); + + return repository.Merge(branchName, signature, options); + } + + /// + /// Fetch all of the changes from a remote InRule git repository and + /// merge into the current branch. + /// + /// The Git repository instance. + /// The result of a merge of two trees and any conflicts. + public static MergeTreeResult Pull(this IInRuleGitRepository repository) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + return repository.Pull(new PullOptions()); + } + + /// + /// Fetch all of the changes from a remote InRule git repository and + /// merge into the current branch. + /// + /// The Git repository instance. + /// The parameters that control the fetch and merge behavior. + /// The result of a merge of two trees and any conflicts. + public static MergeTreeResult Pull(this IInRuleGitRepository repository, PullOptions options) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + var signature = repository.Config.BuildSignature(DateTimeOffset.Now); + + return repository.Pull(signature, options); + } + + /// + /// Fetch all of the changes from a remote InRule git repository and + /// merge into the current branch. + /// + /// The Git repository instance. + /// The signature to use for the merge. + /// The parameters that control the fetch and merge behavior. + /// The result of a merge of two trees and any conflicts. + public static MergeTreeResult Pull(this IInRuleGitRepository repository, Signature merger, PullOptions options) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + return repository.Pull("origin", merger, options); + } + + /// + /// Fetch all of the changes from a remote InRule git repository and + /// merge into the current branch. + /// + /// The Git repository instance. + /// The name or URI for the remote repository. + /// The parameters that control the fetch and merge behavior. + /// The result of a merge of two trees and any conflicts. + public static MergeTreeResult Pull(this IInRuleGitRepository repository, string remote, PullOptions options) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + var signature = repository.Config.BuildSignature(DateTimeOffset.Now); + + return repository.Pull(remote, signature, options); + } + + /// + /// Push the current branch to a remote InRule git repository. + /// + /// The Git repository instance. + public static void Push(this IInRuleGitRepository repository) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + repository.Push("origin", new PushOptions()); + } + + /// + /// Push the current branch to a remote InRule git repository. + /// + /// The Git repository instance. + /// The parameters that control the push behavior. + public static void Push(this IInRuleGitRepository repository, PushOptions options) + { + if (repository == null) throw new ArgumentNullException(nameof(repository)); + + repository.Push("origin", options); + } + + /// + /// Create a commit that removes the specified rule application from the current branch. + /// + /// The Git repository instance. + /// The case-insensitive rule application name. + /// The description of why a change was made to the repository. + public static Commit RemoveRuleApplication(this IInRuleGitRepository repository, string ruleApplicationName, string message) + { + if (ruleApplicationName == null) throw new ArgumentNullException(nameof(ruleApplicationName)); + if (string.IsNullOrWhiteSpace(ruleApplicationName)) throw new ArgumentException("Specified rule application name cannot be null or whitespace.", nameof(ruleApplicationName)); + if (message == null) throw new ArgumentNullException(nameof(message)); + + var signature = repository.Config.BuildSignature(DateTimeOffset.Now); + + return repository.RemoveRuleApplication(ruleApplicationName, message, signature, signature); + } + } +} diff --git a/src/Sknet.InRuleGitStorage/InRuleGitRepository.cs b/src/Sknet.InRuleGitStorage/InRuleGitRepository.cs index a3fe46c..c288db5 100644 --- a/src/Sknet.InRuleGitStorage/InRuleGitRepository.cs +++ b/src/Sknet.InRuleGitStorage/InRuleGitRepository.cs @@ -37,6 +37,12 @@ public void Checkout(string branchName) if (branchName == null) throw new ArgumentNullException(nameof(branchName)); if (string.IsNullOrWhiteSpace(branchName)) throw new ArgumentException("Specified branch name cannot be null or whitespace.", nameof(branchName)); + if (!_repository.Refs.Any()) + { + _repository.Refs.UpdateTarget("HEAD", $"refs/heads/{branchName}"); + return; + } + var targetRef = _repository.Refs[$"refs/heads/{branchName}"]; if (targetRef == null) @@ -47,20 +53,6 @@ public void Checkout(string branchName) _repository.Refs.UpdateTarget(_repository.Refs.Head, targetRef); } - /// - /// Store the content of the specified rule application in the current - /// branch as a new commit. - /// - /// The rule application to store in the repository. - /// The description of why a change was made to the repository. - /// The generated commit containing the specified rule application and any existing rule applications. - public Commit Commit(RuleApplicationDef ruleApplication, string message) - { - var signature = Config.BuildSignature(DateTimeOffset.Now); - - return Commit(ruleApplication, message, signature, signature); - } - /// /// Store the content of the specified rule application in the current /// branch as a new commit. @@ -124,34 +116,58 @@ public Branch CreateBranch(string branchName) if (branchName == null) throw new ArgumentNullException(nameof(branchName)); if (string.IsNullOrWhiteSpace(branchName)) throw new ArgumentException("Specified branch name cannot be null or whitespace.", nameof(branchName)); - var targetRef = _repository.Refs[$"refs/heads/{branchName}"]; + var branch = _repository.Branches[branchName]; - if (targetRef != null) + if (branch != null) { throw new ArgumentException("Specified branch already exists; cannot create a branch.", nameof(branchName)); } - //_repository.Refs.UpdateTarget(_repository.Refs.Head, $"refs/heads/develop"); // TODO: Add test to create branch when no commits exist return _repository.CreateBranch(branchName); } /// - /// Performs application-defined tasks associated with freeing, releasing, - /// or resetting unmanaged resources. + /// Create a new tracked branch from the remote branch of the same name. /// - public void Dispose() + /// The branch name for the new branch. + /// The name for the remote repository. + /// The created branch. + public Branch CreateBranch(string branchName, string remote) { - _repository.Dispose(); + if (branchName == null) throw new ArgumentNullException(nameof(branchName)); + if (string.IsNullOrWhiteSpace(branchName)) throw new ArgumentException("Specified branch name cannot be null or whitespace.", nameof(branchName)); + if (remote == null) throw new ArgumentNullException(nameof(remote)); + if (string.IsNullOrWhiteSpace(remote)) throw new ArgumentException("Specified remote cannot be null or whitespace.", nameof(remote)); + + var branch = _repository.Branches[branchName]; + + if (branch != null) + { + throw new ArgumentException("Specified branch already exists; cannot create a branch.", nameof(branchName)); + } + + var remoteBranch = _repository.Branches[$"{remote}/{branchName}"]; + + if (!remoteBranch.IsRemote) + { + throw new NotImplementedException(); + } + + branch = _repository.CreateBranch(branchName, remoteBranch.Tip); + + _repository.Branches.Update(branch, b => b.TrackedBranch = remoteBranch.CanonicalName); + + return branch; } /// - /// Fetch all of the latest changes from a remote InRule git repository. + /// Performs application-defined tasks associated with freeing, releasing, + /// or resetting unmanaged resources. /// - /// The parameters that control the fetch behavior. - public void Fetch(FetchOptions options) + public void Dispose() { - Fetch("origin", options); + _repository.Dispose(); } /// @@ -297,20 +313,6 @@ public IEnumerable GetRuleApplicationSummaries() return summaries; }*/ - /// - /// Perform a merge of the current branch and the specified branch, and - /// create a commit if there are no conflicts. - /// - /// The branch name to merge with the current branch. - /// The parameters that control the merge behavior. - /// The result of a merge of two trees and any conflicts. - public MergeTreeResult Merge(string branchName, MergeOptions options) - { - var signature = Config.BuildSignature(DateTimeOffset.Now); - - return Merge(branchName, signature, options); - } - /// /// Perform a merge of the current branch and the specified branch, and /// create a commit if there are no conflicts. @@ -355,8 +357,8 @@ private MergeTreeResult MergeFromReference(Reference reference, Signature merger ours: baseCommit, theirs: headCommit, options: new MergeTreeOptions - { - + { + }); // TODO: fix message when pulling from remote @@ -373,45 +375,6 @@ private MergeTreeResult MergeFromReference(Reference reference, Signature merger return mergeTreeResult; } - /// - /// Fetch all of the changes from a remote InRule git repository and - /// merge into the current branch. - /// - /// The parameters that control the fetch and merge behavior. - /// The result of a merge of two trees and any conflicts. - public MergeTreeResult Pull(PullOptions options) - { - var signature = Config.BuildSignature(DateTimeOffset.Now); - - return Pull(signature, options); - } - - /// - /// Fetch all of the changes from a remote InRule git repository and - /// merge into the current branch. - /// - /// The signature to use for the merge. - /// The parameters that control the fetch and merge behavior. - /// The result of a merge of two trees and any conflicts. - public MergeTreeResult Pull(Signature merger, PullOptions options) - { - return Pull("origin", merger, options); - } - - /// - /// Fetch all of the changes from a remote InRule git repository and - /// merge into the current branch. - /// - /// The name or URI for the remote repository. - /// The parameters that control the fetch and merge behavior. - /// The result of a merge of two trees and any conflicts. - public MergeTreeResult Pull(string remote, PullOptions options) - { - var signature = Config.BuildSignature(DateTimeOffset.Now); - - return Pull(remote, signature, options); - } - /// /// Fetch all of the changes from a remote InRule git repository and /// merge into the current branch. @@ -442,15 +405,6 @@ public MergeTreeResult Pull(string remote, Signature merger, PullOptions options return MergeFromReference(reference, merger, mergeOptions); } - /// - /// Push the current branch to a remote InRule git repository. - /// - /// The parameters that control the push behavior. - public void Push(PushOptions options) - { - Push("origin", options); - } - /// /// Push the current branch to a remote InRule git repository. /// @@ -509,6 +463,61 @@ public void RemoveBranch(string branchName) _repository.Branches.Remove(branchName); } + /// + /// Create a commit that removes the specified rule application from the current branch. + /// + /// The case-insensitive rule application name. + /// The description of why a change was made to the repository. + /// The signature of who made the change. + /// The signature of who added the change to the repository. + public Commit RemoveRuleApplication(string ruleApplicationName, string message, Signature author, Signature committer) + { + if (ruleApplicationName == null) throw new ArgumentNullException(nameof(ruleApplicationName)); + if (string.IsNullOrWhiteSpace(ruleApplicationName)) throw new ArgumentException("Specified rule application name cannot be null or whitespace.", nameof(ruleApplicationName)); + if (message == null) throw new ArgumentNullException(nameof(message)); + if (author == null) throw new ArgumentNullException(nameof(author)); + if (committer == null) throw new ArgumentNullException(nameof(committer)); + + var headTarget = _repository.Refs.Head.ResolveToDirectReference(); + + if (headTarget == null) + { + return null; + } + + var commit = _repository.Lookup(headTarget.TargetIdentifier); + + var ruleAppTreeEntry = commit.Tree[ruleApplicationName]; + + if (ruleAppTreeEntry?.Target == null || !(ruleAppTreeEntry.Target is Tree)) + { + return null; + } + + var parents = new[] { _repository.Lookup(_repository.Refs.Head.TargetIdentifier) }; + + var allRuleAppsTreeDefinition = new TreeDefinition(); + + var parentTree = parents[0].Tree; + + foreach (var treeEntry in parentTree) + { + if (string.Equals(treeEntry.Name, ruleAppTreeEntry.Name, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + allRuleAppsTreeDefinition.Add(treeEntry.Name, treeEntry); + } + + var allRuleAppsTree = _repository.ObjectDatabase.CreateTree(allRuleAppsTreeDefinition); + commit = _repository.ObjectDatabase.CreateCommit(author, committer, message, allRuleAppsTree, parents, true, null); + + _repository.Refs.UpdateTarget(_repository.Refs.Head.TargetIdentifier, commit.Sha); + + return commit; + } + /// /// Clone a remote InRule git repository to a new local repository. /// diff --git a/test/Sknet.InRuleGitStorage.Tests/Fixtures/GitRepositoryFixture.cs b/test/Sknet.InRuleGitStorage.Tests/Fixtures/GitRepositoryFixture.cs index b432e87..9d692b4 100644 --- a/test/Sknet.InRuleGitStorage.Tests/Fixtures/GitRepositoryFixture.cs +++ b/test/Sknet.InRuleGitStorage.Tests/Fixtures/GitRepositoryFixture.cs @@ -4,6 +4,36 @@ namespace Sknet.InRuleGitStorage.Tests.Fixtures { + public class TemporaryDirectoryFixture : IDisposable + { + public string DirectoryPath { get; } + + public TemporaryDirectoryFixture() + { + DirectoryPath = Path.Combine(Environment.CurrentDirectory, "Data", $"repo-{Guid.NewGuid().ToString("N")}"); + Directory.CreateDirectory(DirectoryPath); + } + + public void Dispose() + { + DeleteDirectoryPath(); + } + + private void DeleteDirectoryPath() + { + if (!string.IsNullOrWhiteSpace(DirectoryPath) && Directory.Exists(DirectoryPath)) + { + var directoryInfo = new DirectoryInfo(DirectoryPath); + foreach (var file in directoryInfo.GetFiles("*", SearchOption.AllDirectories)) + { + file.Attributes &= ~FileAttributes.ReadOnly; + } + + Directory.Delete(DirectoryPath, true); + } + } + } + public class GitRepositoryFixture : IDisposable { private readonly string _repositoryPath; @@ -11,7 +41,7 @@ public class GitRepositoryFixture : IDisposable public GitRepositoryFixture() : this(true) { - + } public GitRepositoryFixture(bool isBare) diff --git a/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/CheckoutTests.cs b/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/CheckoutTests.cs index 1702cf0..6ea3dd7 100644 --- a/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/CheckoutTests.cs +++ b/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/CheckoutTests.cs @@ -47,7 +47,7 @@ public void WithWhiteSpaceBranchName_ShouldThrowException(string path) Assert.Throws(() => repository.Checkout(path)); } - [Fact] + /*[Fact] public void WithNonExistingBranch_ShouldThrowException() { // Arrange @@ -55,7 +55,7 @@ public void WithNonExistingBranch_ShouldThrowException() // Act/Assert Assert.Throws(() => repository.Checkout("unknown-branch")); - } + }*/ [Fact] public void WithNonMasterBranch_ShouldUpdateHead() @@ -97,5 +97,50 @@ public void WithSameBranch_ShouldUpdateHead() // Assert Assert.Equal("develop", _fixture.Repository.Head.FriendlyName); } + + /*[Fact] + public void Playground() + { + using (var localFixture = new GitRepositoryFixture()) + using (var remoteFixture = new GitRepositoryFixture()) + { + var localRepository = new InRuleGitRepository(localFixture.Repository); + var remoteRepository = new InRuleGitRepository(remoteFixture.Repository); + + //localRepository.Config.Set("user.name", "Neil Armstrong"); + //localRepository.Config.Set("user.name", "Neil Armstrong"); + + localRepository.Remotes.Add("origin", remoteFixture.Repository.Info.Path); + + var ruleAppDef = new RuleApplicationDef("MyRuleApplication"); + remoteRepository.Commit(ruleAppDef, "My git to remote repo"); + + remoteRepository.CreateBranch("branchName"); + ruleAppDef = new RuleApplicationDef("AnotherRuleApplication"); + remoteRepository.Commit(ruleAppDef, "Another my git to remote repo"); + + localRepository.Fetch(new FetchOptions()); + + localRepository.CreateTrackedBranch("branchName", "origin"); + localRepository.Checkout("branchName"); + + var ruleAppDef2 = localRepository.GetRuleApplication("AnotherRuleApplication"); + } + } + + [Fact] + public void Playground2() + { + using (var localFixture = new GitRepositoryFixture()) + { + var localRepository = new InRuleGitRepository(localFixture.Repository); + + //localRepository.CreateBranch("myBranchName"); + localRepository.Checkout("myBranchName"); + + var ruleAppDef = new RuleApplicationDef("MyRuleApplication"); + localRepository.Commit(ruleAppDef, "Another my git to remote repo"); + } + }*/ } } diff --git a/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/DocumentationTests.cs b/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/DocumentationTests.cs new file mode 100644 index 0000000..1dfb322 --- /dev/null +++ b/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/DocumentationTests.cs @@ -0,0 +1,150 @@ +using InRule.Repository; +using Sknet.InRuleGitStorage.Tests.Fixtures; +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Xunit; + +namespace Sknet.InRuleGitStorage.Tests.InRuleGitRepositoryTests +{ + public class DocumentationTests : IDisposable + { + private readonly TemporaryDirectoryFixture _fixture; + + public DocumentationTests() + { + _fixture = new TemporaryDirectoryFixture(); + } + + public void Dispose() + { + _fixture.Dispose(); + } + + [Fact] + public void Introduction_BasicExample_Test() + { + // Create a new repository in a local directory + InRuleGitRepository.Init(_fixture.DirectoryPath); + + // Get a new instance of your local InRule Git repository + using (var repo = InRuleGitRepository.Open(_fixture.DirectoryPath)) + { + repo.Config.Set("user.name", "Starlord"); + repo.Config.Set("user.email", "starlord@gotg.com"); + + // Create a new rule application and commit it to the "master" branch + var ruleApp = new RuleApplicationDef("QuickstartSample"); + repo.Commit(ruleApp, "Add quickstart sample rule application"); + + // Get the rule application from the Git repository + var result = repo.GetRuleApplication("QuickstartSample"); + + // Assert + Assert.Equal(ruleApp.Guid, result.Guid); + } + } + + [Fact] + public void Introduction_RemoteRepositoryExample_Test() + { + // Arrage + using (var remoteFixture = new GitRepositoryFixture()) + { + using (var repo = InRuleGitRepository.Open(remoteFixture.Repository.Info.Path))// var repo = new InRuleGitRepository(remoteFixture.Repository)) + { + repo.Config.Set("user.name", "Starlord"); + repo.Config.Set("user.email", "starlord@gotg.com"); + + var ruleApp = new RuleApplicationDef("BlankRuleApp"); + repo.Commit(ruleApp, "Add blank rule application"); + + repo.CreateBranch("v0.2.0"); + repo.Checkout("v0.2.0"); + + var assembly = Assembly.GetExecutingAssembly(); + var resourceName = $"Sknet.InRuleGitStorage.Tests.RuleApps.InvoiceSample.ruleappx"; + var stream = assembly.GetManifestResourceStream(resourceName); + ruleApp = RuleApplicationDef.Load(stream); + repo.Commit(ruleApp, "Add invoice sample rule application"); + + resourceName = $"Sknet.InRuleGitStorage.Tests.RuleApps.Chicago Food Tax Generator.ruleappx"; + stream = assembly.GetManifestResourceStream(resourceName); + ruleApp = RuleApplicationDef.Load(stream); + repo.Commit(ruleApp, "Add Chicago Food Tax Generator rule application"); + + repo.Checkout("master"); + } + + // === Test === + + InRuleGitRepository.Clone( + sourceUrl: remoteFixture.Repository.Info.Path, + destinationPath: _fixture.DirectoryPath); + + // Get a new instance of your local InRule Git repository + using (var repo = InRuleGitRepository.Open(_fixture.DirectoryPath)) + { + repo.Config.Set("user.name", "Starlord"); + repo.Config.Set("user.email", "starlord@gotg.com"); + + // Create a local branch that is tracked to the remote "v0.2.0" branch + repo.CreateBranch("v0.2.0", "origin"); + + // Switch the current branch to the newly created tracked branch + repo.Checkout("v0.2.0"); + + // Create a local branch from the "v0.2.0" branch + repo.CreateBranch("invoice-date-field"); + + // Switch the current branch to the newly created local branch + repo.Checkout("invoice-date-field"); + + // Get the InvoiceSample rule application from the repository, add an invoice date + // field, and commit that change to the current branch + var ruleApp = repo.GetRuleApplication("InvoiceSample"); + ruleApp.Entities["Invoice"].Fields.Add(new FieldDef("Date", DataType.DateTime)); + repo.Commit(ruleApp, "Add invoice date field"); + + // Switch back to the previous branch that does not have the field change + repo.Checkout("v0.2.0"); + + // Merge the invoice date field change into the current branch + repo.Merge("invoice-date-field"); + + // Delete the original branch containing the invoice date field change since the + // change now exists in the "v0.2.0" branch + repo.RemoveBranch("invoice-date-field"); + + repo.Push(); + } + + // === ==== === + using (var repo = InRuleGitRepository.Open(remoteFixture.Repository.Info.Path)) + { + repo.Config.Set("user.name", "Starlord"); + repo.Config.Set("user.email", "starlord@gotg.com"); + + repo.Checkout("v0.2.0"); + var ruleApp = repo.GetRuleApplication("InvoiceSample"); + repo.Checkout("master"); + + var field = ruleApp.Entities["Invoice"].Fields["Date"]; + + Assert.NotNull(field); + Assert.Equal(DataType.DateTime, field.DataType); + + repo.Checkout("v0.2.0"); + repo.RemoveRuleApplication("InvoiceSample", "Remove rule application"); + ruleApp = repo.GetRuleApplication("InvoiceSample"); + var ruleApps = repo.GetRuleApplications(); + repo.Checkout("master"); + + Assert.Null(ruleApp); + Assert.Equal(2, ruleApps.Length); + } + } + } + } +} diff --git a/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/GetRuleApplicationsTests.cs b/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/GetRuleApplicationsTests.cs index 9ff422e..12a8c99 100644 --- a/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/GetRuleApplicationsTests.cs +++ b/test/Sknet.InRuleGitStorage.Tests/InRuleGitRepositoryTests/GetRuleApplicationsTests.cs @@ -1,10 +1,19 @@ using System; using System.Collections.Generic; using System.Text; +using Xunit; namespace Sknet.InRuleGitStorage.Tests.InRuleGitRepositoryTests { public class GetRuleApplicationsTests { + /*[Fact] + public void PlaygroundTests() + { + using (var repo = InRuleGitRepository.Open("C:/Users/email/Desktop/InRuleGitDbClone")) + { + var ruleApps = repo.GetRuleApplications(); + } + }*/ } }