Skip to content

Commit

Permalink
Merge pull request #1 from xenial-io/topic/feat-confirm-branch
Browse files Browse the repository at this point in the history
Topic/feat confirm branch
  • Loading branch information
biohazard999 authored Oct 28, 2022
2 parents 89d2eaa + 85265c4 commit ec2df62
Show file tree
Hide file tree
Showing 16 changed files with 605 additions and 5 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Not in a git repository: /var/www/
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The current branch you are working on is not main or master
actually it is develop
Do you want to continue [y/n] (n): n
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
emptyString
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
emptyString
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The current branch you are working on is not main or master
actually it is topic/my-feature-123
Do you want to continue [y/n] (n): y
98 changes: 98 additions & 0 deletions Xenial.RTool.Tests/ReleaseApplicationTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
using System;
using System.Collections.Generic;
using System.IO.Abstractions.TestingHelpers;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

using FakeItEasy;

using Spectre.Console.Testing;

using Xenial.RTool;

namespace Xenial.Tests.RTool;

[UsesVerify]
public sealed class ReleaseApplicationTests
{
private MockFileSystem FileSystem { get; } = new MockFileSystem(
new Dictionary<string, MockFileData>()
);
private TestConsole Console { get; } = new TestConsole();
private IGitCommandRunner CommandRunner { get; } = A.Fake<IGitCommandRunner>();

private ReleaseContext CreateTestContext(string? cd = null)
=> new ReleaseContext(
FileSystem,
Console,
CommandRunner,
cd ?? Environment.CurrentDirectory
);

private (ReleaseApplication app, ReleaseContext ctx) CreateApplication()
=> (new ReleaseApplication(FileSystem, Console, CommandRunner), CreateTestContext());

[Fact]
public async Task CheckForGitMustNotCallNextWhenNotInGitRepo()
{
var next = A.Fake<ReleaseApplicationDelegate>();
var detector = A.Fake<IGitRepositoryDetector>();
A.CallTo(() => detector.DetectGitRepository(A.Dummy<string>())).Returns(false);
var ctx = CreateTestContext("/var/www/");

await ReleaseApplication.CheckForGit(next, ctx, detector);

A.CallTo(next).MustNotHaveHappened();

await Verify(Console.Output);
}

[Fact]
public async Task CheckForGitMustCallNextWhenInGitRepo()
{
var next = A.Fake<ReleaseApplicationDelegate>();
var detector = A.Fake<IGitRepositoryDetector>();
A.CallTo(() => detector.DetectGitRepository(A<string>.Ignored)).Returns(true);

var ctx = CreateTestContext();

await ReleaseApplication.CheckForGit(next, ctx, detector);

A.CallTo(next).MustHaveHappened();
}

[Theory]
[InlineData("main", false, true)]
[InlineData("master", false, true)]
[InlineData("develop", true, false)]
[InlineData("topic/my-feature-123", true, true)]
public async Task ConfirmBranches(string currentBranch, bool mustConfirm, bool confirm)
{
var next = A.Fake<ReleaseApplicationDelegate>();

A.CallTo(() => CommandRunner.ReadCommand("branch --show-current", A<string>.Ignored))
.Returns(Task.FromResult(currentBranch));

if (mustConfirm)
{
Console.Input.PushTextWithEnter(confirm ? "y" : "n");
}

var (app, ctx) = CreateApplication();

await app.ConfirmBranch(next, ctx);

if (confirm)
{
A.CallTo(next).MustHaveHappened();
}
else
{
A.CallTo(next).MustNotHaveHappened();
}

await Verify(Console.Output)
.UseParameters(currentBranch, mustConfirm, confirm);
}
}
12 changes: 9 additions & 3 deletions Xenial.RTool.Tests/Xenial.RTool.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,18 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="FakeItEasy" Version="7.3.1" />
<PackageReference Include="FakeItEasy.Analyzer.CSharp" Version="6.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.3.2" />
<PackageReference Include="Shouldly" Version="4.1.0" />
<PackageReference Include="Spectre.Console.Testing" Version="0.45.0" />
<PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="17.2.3" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
<PackageReference Include="Verify.Xunit" Version="18.0.0" />
<PackageReference Include="xunit" Version="2.4.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.5">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
Expand Down
85 changes: 85 additions & 0 deletions Xenial.RTool/Common/MiddlewareBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System.Collections.Immutable;
using System.Diagnostics;

namespace Xenial.RTool.Common;

public record MiddlewareBuilder<TDelegate, TBuilder>
: MiddlewareBuilder<TDelegate, Unit, TBuilder>
where TBuilder : MiddlewareBuilder<TDelegate, TBuilder>
where TDelegate : Delegate
{
public MiddlewareBuilder(TDelegate initialDelegate) : base(Unit.Value, initialDelegate)
{

}
}

public record MiddlewareBuilder<TDelegate, TContext, TBuilder>
where TBuilder : MiddlewareBuilder<TDelegate, TContext, TBuilder>
where TDelegate : System.Delegate
{
private static readonly object locker = new();

private ImmutableArray<Func<TDelegate, TDelegate>> middlewares
= ImmutableArray.Create<Func<TDelegate, TDelegate>>();

public TContext Context { get; init; }

private TDelegate InitialDelegate { get; }

public Func<TDelegate, TDelegate, TDelegate>? Delegate { get; init; }

public MiddlewareBuilder(TContext context, TDelegate initialDelegate)
{
Context = context ?? throw new ArgumentNullException(nameof(context));
InitialDelegate = initialDelegate ?? throw new ArgumentNullException(nameof(initialDelegate));
}

[DebuggerStepThrough]
public TBuilder Use(Func<TDelegate, TDelegate> middleware)
{
lock (locker)
{
middlewares = middlewares.Add(middleware);
}

return (TBuilder)this;
}

[DebuggerStepThrough]
public TBuilder UseMiddleware<TMiddleware>(Func<TMiddleware> createMiddleware)
{
var methodInfo = typeof(TMiddleware).GetMethod("InvokeAsync");
if (methodInfo is null)
{
throw new InvalidOperationException($"{typeof(TMiddleware)} must have an method called 'InvokeAsync' that matches the '{typeof(TMiddleware).FullName}' signiture.");
}

return Use(next =>
{
var target = createMiddleware();
var info = methodInfo.CreateDelegate<TDelegate>(target);
if (Delegate is null)
{
_ = info.DynamicInvoke(Context);
return next;
}
return Delegate(info, next);
});
}

[DebuggerStepThrough]
public TDelegate Build()
{
TDelegate app = InitialDelegate;

foreach (var middleware in middlewares.Reverse())
{
app = middleware(app);
}

return app;
}
}
93 changes: 93 additions & 0 deletions Xenial.RTool/Common/Unit.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
namespace Xenial.RTool.Common;

/// <summary>
/// Represents a void type, since <see cref="System.Void"/> is not a valid return type in C#.
/// </summary>
public readonly struct Unit : IEquatable<Unit>, IComparable<Unit>, IComparable
{
private static readonly Unit _value = new();

/// <summary>
/// Default and only value of the <see cref="Unit"/> type.
/// </summary>
public static ref readonly Unit Value => ref _value;

/// <summary>
/// Task from a <see cref="Unit"/> type.
/// </summary>
public static Task<Unit> Task { get; } = System.Threading.Tasks.Task.FromResult(_value);

/// <summary>
/// Compares the current object with another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// A value that indicates the relative order of the objects being compared.
/// The return value has the following meanings:
/// - Less than zero: This object is less than the <paramref name="other" /> parameter.
/// - Zero: This object is equal to <paramref name="other" />.
/// - Greater than zero: This object is greater than <paramref name="other" />.
/// </returns>
public int CompareTo(Unit other) => 0;

/// <summary>
/// Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object.
/// </summary>
/// <param name="obj">An object to compare with this instance.</param>
/// <returns>
/// A value that indicates the relative order of the objects being compared.
/// The return value has these meanings:
/// - Less than zero: This instance precedes <paramref name="obj" /> in the sort order.
/// - Zero: This instance occurs in the same position in the sort order as <paramref name="obj" />.
/// - Greater than zero: This instance follows <paramref name="obj" /> in the sort order.
/// </returns>
int IComparable.CompareTo(object? obj) => 0;

/// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>
/// A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.
/// </returns>
public override int GetHashCode() => 0;

/// <summary>
/// Determines whether the current object is equal to another object of the same type.
/// </summary>
/// <param name="other">An object to compare with this object.</param>
/// <returns>
/// <c>true</c> if the current object is equal to the <paramref name="other" /> parameter; otherwise, <c>false</c>.
/// </returns>
public bool Equals(Unit other) => true;

/// <summary>
/// Determines whether the specified <see cref="System.Object" /> is equal to this instance.
/// </summary>
/// <param name="obj">The object to compare with the current instance.</param>
/// <returns>
/// <c>true</c> if the specified <see cref="System.Object" /> is equal to this instance; otherwise, <c>false</c>.
/// </returns>
public override bool Equals(object? obj) => obj is Unit;

/// <summary>
/// Determines whether the <paramref name="first"/> object is equal to the <paramref name="second"/> object.
/// </summary>
/// <param name="first">The first object.</param>
/// <param name="second">The second object.</param>
/// <c>true</c> if the <paramref name="first"/> object is equal to the <paramref name="second" /> object; otherwise, <c>false</c>.
public static bool operator ==(Unit first, Unit second) => true;

/// <summary>
/// Determines whether the <paramref name="first"/> object is not equal to the <paramref name="second"/> object.
/// </summary>
/// <param name="first">The first object.</param>
/// <param name="second">The second object.</param>
/// <c>true</c> if the <paramref name="first"/> object is not equal to the <paramref name="second" /> object; otherwise, <c>false</c>.
public static bool operator !=(Unit first, Unit second) => false;

/// <summary>
/// Returns a <see cref="System.String" /> that represents this instance.
/// </summary>
/// <returns>A <see cref="System.String" /> that represents this instance.</returns>
public override string ToString() => "()";
}
14 changes: 14 additions & 0 deletions Xenial.RTool/GitCommandRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using static SimpleExec.Command;

namespace Xenial.RTool; public sealed class GitCommandRunner : IGitCommandRunner
{
public Task RunCommand(string command, string? cd = null)
=> RunAsync("git", command, workingDirectory: cd ?? Environment.CurrentDirectory);

public async Task<string> ReadCommand(string command, string? cd = null)
{
var (stdOut, _) = (await ReadAsync("git", command, workingDirectory: cd ?? Environment.CurrentDirectory));
var result = stdOut.Trim();
return result;
}
}
2 changes: 1 addition & 1 deletion Xenial.RTool/GitRepositoryDetector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Xenial.RTool;

public sealed record GitRepositoryDetector(IFileSystem FileSystem)
public sealed record GitRepositoryDetector(IFileSystem FileSystem) : IGitRepositoryDetector
{
public bool DetectGitRepository(string? cd = null)
{
Expand Down
7 changes: 7 additions & 0 deletions Xenial.RTool/IGitCommandRunner.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Xenial.RTool;

public interface IGitCommandRunner
{
Task<string> ReadCommand(string command, string? cd = null);
Task RunCommand(string command, string? cd = null);
}
6 changes: 6 additions & 0 deletions Xenial.RTool/IGitRepositoryDetector.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace Xenial.RTool;

public interface IGitRepositoryDetector
{
bool DetectGitRepository(string? cd = null);
}
10 changes: 9 additions & 1 deletion Xenial.RTool/Program.cs
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
Console.WriteLine("");
using System.IO.Abstractions;

using Spectre.Console;

using Xenial.RTool;

var app = new ReleaseApplication(new FileSystem(), AnsiConsole.Console, new GitCommandRunner());

await app.Release();
11 changes: 11 additions & 0 deletions Xenial.RTool/Properties/launchSettings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"profiles": {
"Xenial.RTool": {
"commandName": "Project"
},
"Xenial.RTool No Git": {
"commandName": "Project",
"workingDirectory": "C:\\Users\\mgrun\\Downloads"
}
}
}
Loading

0 comments on commit ec2df62

Please sign in to comment.