Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 124 additions & 0 deletions src/Stack.Tests/Commands/Branch/RemoveBranchCommandHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,4 +289,128 @@ public async Task WhenConfirmProvided_DoesNotAskForConfirmation_RemovesBranchFro
});
inputProvider.DidNotReceive().Confirm(Questions.ConfirmRemoveBranch);
}

[Fact]
public async Task WhenSchemaIsV2_AndChildActionIsMoveChildrenToParent_RemovesBranchAndMovesChildrenToParent()
{
// Arrange
var sourceBranch = Some.BranchName();
var branchToRemove = Some.BranchName();
var childBranch = Some.BranchName();
using var repo = new TestGitRepositoryBuilder()
.WithBranch(sourceBranch)
.WithBranch(branchToRemove)
.WithBranch(childBranch)
.Build();

var stackConfig = new TestStackConfigBuilder()
.WithSchemaVersion(SchemaVersion.V2)
.WithStack(stack => stack
.WithName("Stack1")
.WithRemoteUri(repo.RemoteUri)
.WithSourceBranch(sourceBranch)
.WithBranch(b => b.WithName(branchToRemove).WithChildBranch(b => b.WithName(childBranch))))
.Build();
var inputProvider = Substitute.For<IInputProvider>();
var logger = new TestLogger(testOutputHelper);
var gitClient = new GitClient(logger, repo.GitClientSettings);
var handler = new RemoveBranchCommandHandler(inputProvider, logger, gitClient, stackConfig);

inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1");
inputProvider.Select(Questions.SelectBranch, Arg.Any<string[]>()).Returns(branchToRemove);
inputProvider.Select(Questions.RemoveBranchChildAction, Arg.Any<RemoveBranchChildAction[]>(), Arg.Any<Func<RemoveBranchChildAction, string>>())
.Returns(RemoveBranchChildAction.MoveChildrenToParent);
inputProvider.Confirm(Questions.ConfirmRemoveBranch).Returns(true);

// Act
await handler.Handle(RemoveBranchCommandInputs.Empty);

// Assert
stackConfig.Stacks.Should().BeEquivalentTo(new List<Config.Stack>
{
new("Stack1", repo.RemoteUri, sourceBranch, [new Config.Branch(childBranch, [])])
});
}

[Fact]
public async Task WhenSchemaIsV2_AndChildActionIsRemoveChildren_RemovesBranchAndDeletesChildren()
{
// Arrange
var sourceBranch = Some.BranchName();
var branchToRemove = Some.BranchName();
var childBranch = Some.BranchName();
using var repo = new TestGitRepositoryBuilder()
.WithBranch(sourceBranch)
.WithBranch(branchToRemove)
.WithBranch(childBranch)
.Build();

var stackConfig = new TestStackConfigBuilder()
.WithSchemaVersion(SchemaVersion.V2)
.WithStack(stack => stack
.WithName("Stack1")
.WithRemoteUri(repo.RemoteUri)
.WithSourceBranch(sourceBranch)
.WithBranch(b => b.WithName(branchToRemove).WithChildBranch(b => b.WithName(childBranch))))
.Build();
var inputProvider = Substitute.For<IInputProvider>();
var logger = new TestLogger(testOutputHelper);
var gitClient = new GitClient(logger, repo.GitClientSettings);
var handler = new RemoveBranchCommandHandler(inputProvider, logger, gitClient, stackConfig);

inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1");
inputProvider.Select(Questions.SelectBranch, Arg.Any<string[]>()).Returns(branchToRemove);
inputProvider.Select(Questions.RemoveBranchChildAction, Arg.Any<RemoveBranchChildAction[]>(), Arg.Any<Func<RemoveBranchChildAction, string>>())
.Returns(RemoveBranchChildAction.RemoveChildren);
inputProvider.Confirm(Questions.ConfirmRemoveBranch).Returns(true);

// Act
await handler.Handle(RemoveBranchCommandInputs.Empty);

// Assert
stackConfig.Stacks.Should().BeEquivalentTo(new List<Config.Stack>
{
new("Stack1", repo.RemoteUri, sourceBranch, [])
});
}

[Fact]
public async Task WhenSchemaIsV1_RemovesBranchAndMovesChildrenToParent()
{
// Arrange
var sourceBranch = Some.BranchName();
var branchToRemove = Some.BranchName();
var childBranch = Some.BranchName();
using var repo = new TestGitRepositoryBuilder()
.WithBranch(sourceBranch)
.WithBranch(branchToRemove)
.WithBranch(childBranch)
.Build();

var stackConfig = new TestStackConfigBuilder()
.WithSchemaVersion(SchemaVersion.V1)
.WithStack(stack => stack
.WithName("Stack1")
.WithRemoteUri(repo.RemoteUri)
.WithSourceBranch(sourceBranch)
.WithBranch(b => b.WithName(branchToRemove).WithChildBranch(b => b.WithName(childBranch))))
.Build();
var inputProvider = Substitute.For<IInputProvider>();
var logger = new TestLogger(testOutputHelper);
var gitClient = new GitClient(logger, repo.GitClientSettings);
var handler = new RemoveBranchCommandHandler(inputProvider, logger, gitClient, stackConfig);

inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1");
inputProvider.Select(Questions.SelectBranch, Arg.Any<string[]>()).Returns(branchToRemove);
inputProvider.Confirm(Questions.ConfirmRemoveBranch).Returns(true);

// Act
await handler.Handle(RemoveBranchCommandInputs.Empty);

// Assert
stackConfig.Stacks.Should().BeEquivalentTo(new List<Config.Stack>
{
new("Stack1", repo.RemoteUri, sourceBranch, [new Config.Branch(childBranch, [])])
});
}
}
4 changes: 2 additions & 2 deletions src/Stack.Tests/Helpers/TestStackConfigBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public TestStackConfigBuilder WithStack(Action<TestStackBuilder> stackBuilder)
return this;
}

public TestStackConfigBuilder WithSchemaVersion(SchemaVersion schemaVersion)
public TestStackConfigBuilder WithSchemaVersion(SchemaVersion version)
{
this.schemaVersion = schemaVersion;
schemaVersion = version;
return this;
}

Expand Down
22 changes: 21 additions & 1 deletion src/Stack/Commands/Branch/RemoveBranchCommand.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.ComponentModel;
using Humanizer;
using Spectre.Console.Cli;
using Stack.Commands.Helpers;
using Stack.Config;
Expand Down Expand Up @@ -71,13 +72,32 @@ public override async Task Handle(RemoveBranchCommandInputs inputs)
throw new InvalidOperationException($"Branch '{branchName}' not found in stack '{stack.Name}'.");
}

var action = RemoveBranchChildAction.MoveChildrenToParent;

if (stackData.SchemaVersion == SchemaVersion.V2)
{
action = inputProvider.Select(
Questions.RemoveBranchChildAction,
[RemoveBranchChildAction.MoveChildrenToParent, RemoveBranchChildAction.RemoveChildren],
(action) => action.Humanize());
}

if (inputs.Confirm || inputProvider.Confirm(Questions.ConfirmRemoveBranch))
{
stack.RemoveBranch(branchName);
stack.RemoveBranch(branchName, action);
stackConfig.Save(stackData);

logger.Information($"Branch {branchName.Branch()} removed from stack {stack.Name.Stack()}");
}
}
}

public enum RemoveBranchChildAction
{
[Description("Move children branches to parent branch")]
MoveChildrenToParent,

[Description("Remove children branches")]
RemoveChildren
}

1 change: 1 addition & 0 deletions src/Stack/Commands/Helpers/Questions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public static class Questions
public const string ConfirmDeleteStack = "Are you sure you want to delete this stack?";
public const string ConfirmDeleteBranches = "Are you sure you want to delete these local branches?";
public const string ConfirmRemoveBranch = "Are you sure you want to remove this branch from the stack?";
public const string RemoveBranchChildAction = "What do you want to do with the children of this branch?";
public const string AddOrCreateBranch = "Add or create a branch:";
public const string SelectPullRequestsToCreate = "Select branches to create pull requests for:";
public const string ConfirmCreatePullRequests = "Are you sure you want to create pull requests for branches in this stack?";
Expand Down
26 changes: 16 additions & 10 deletions src/Stack/Config/Stack.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System.Text.Json.Serialization;
using Humanizer;
using Stack.Commands;

namespace Stack.Config;

Expand Down Expand Up @@ -62,7 +63,7 @@ public string GetDefaultBranchName()
return $"{Name.Kebaberize()}-{fullBranchNames.Count + 1}";
}

public void RemoveBranch(string branchName)
public void RemoveBranch(string branchName, RemoveBranchChildAction action)
{
if (Branches.Count == 0)
{
Expand All @@ -73,35 +74,40 @@ public void RemoveBranch(string branchName)
{
if (branch.Name.Equals(branchName, StringComparison.OrdinalIgnoreCase))
{
var childrenToMove = branch.Children;
Branches.Remove(branch);
branch.Children.AddRange(childrenToMove);

if (action == RemoveBranchChildAction.MoveChildrenToParent)
{
Branches.AddRange(branch.Children);
}
return;
}

if (RemoveBranch(branch, branchName))
if (RemoveBranch(branch, branchName, action))
{
return;
}
}
}

static bool RemoveBranch(Branch branch, string branchName)
static bool RemoveBranch(Branch branch, string branchName, RemoveBranchChildAction action)
{
var childBranch = branch.Children.FirstOrDefault(c => c.Name.Equals(branchName, StringComparison.OrdinalIgnoreCase));
if (childBranch != null)
{
// Get all the children of the branch to be removed,
// and add them to the parent of the branch being removed.
var childrenToMove = childBranch.Children;
branch.Children.Remove(childBranch);
branch.Children.AddRange(childrenToMove);
if (action == RemoveBranchChildAction.MoveChildrenToParent)
{
// Get all the children of the branch to be removed,
// and add them to the parent of the branch being removed.
branch.Children.AddRange(childBranch.Children);
}
return true;
}

foreach (var child in branch.Children)
{
if (RemoveBranch(child, branchName))
if (RemoveBranch(child, branchName, action))
{
return true;
}
Expand Down