-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
e53ba99
commit d5433bf
Showing
8 changed files
with
316 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
184 changes: 184 additions & 0 deletions
184
src/Stack.Tests/Commands/Remote/PullStackCommandHandlerTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
using FluentAssertions; | ||
using NSubstitute; | ||
using Stack.Commands; | ||
using Stack.Config; | ||
using Stack.Git; | ||
using Stack.Tests.Helpers; | ||
using Stack.Infrastructure; | ||
using Stack.Commands.Helpers; | ||
using Xunit.Abstractions; | ||
|
||
namespace Stack.Tests.Commands.Remote; | ||
|
||
public class PullStackCommandHandlerTests(ITestOutputHelper testOutputHelper) | ||
{ | ||
[Fact] | ||
public async Task WhenChangesExistOnTheRemote_TheyArePulledDownToTheLocalBranch() | ||
{ | ||
// Arrange | ||
var sourceBranch = Some.BranchName(); | ||
var branch1 = Some.BranchName(); | ||
var branch2 = Some.BranchName(); | ||
using var repo = new TestGitRepositoryBuilder() | ||
.WithBranch(builder => builder.WithName(sourceBranch).PushToRemote()) | ||
.WithBranch(builder => builder.WithName(branch1).FromSourceBranch(sourceBranch).WithNumberOfEmptyCommits(10).PushToRemote()) | ||
.WithBranch(builder => builder.WithName(branch2).FromSourceBranch(branch1).WithNumberOfEmptyCommits(1).PushToRemote()) | ||
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(sourceBranch, 5, b => b.PushToRemote()) | ||
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(branch1, 3, b => b.PushToRemote()) | ||
.Build(); | ||
|
||
var tipOfRemoteSourceBranch = repo.GetTipOfRemoteBranch(sourceBranch); | ||
var tipOfRemoteBranch1 = repo.GetTipOfRemoteBranch(branch1); | ||
|
||
repo.GetCommitsReachableFromBranch(sourceBranch).Should().NotContain(tipOfRemoteSourceBranch); | ||
repo.GetCommitsReachableFromBranch(branch1).Should().NotContain(tipOfRemoteBranch1); | ||
|
||
var stackConfig = Substitute.For<IStackConfig>(); | ||
var inputProvider = Substitute.For<IInputProvider>(); | ||
var outputProvider = new TestOutputProvider(testOutputHelper); | ||
var gitOperations = new GitOperations(outputProvider, repo.GitOperationSettings); | ||
var handler = new PullStackCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); | ||
|
||
gitOperations.ChangeBranch(branch1); | ||
|
||
var stack1 = new Config.Stack("Stack1", repo.RemoteUri, sourceBranch, [branch1, branch2]); | ||
var stack2 = new Config.Stack("Stack2", repo.RemoteUri, sourceBranch, []); | ||
var stacks = new List<Config.Stack>([stack1, stack2]); | ||
stackConfig.Load().Returns(stacks); | ||
|
||
inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1"); | ||
|
||
// Act | ||
await handler.Handle(new PullStackCommandInputs(null)); | ||
|
||
// Assert | ||
repo.GetCommitsReachableFromBranch(sourceBranch).Should().Contain(tipOfRemoteSourceBranch); | ||
repo.GetCommitsReachableFromBranch(branch1).Should().Contain(tipOfRemoteBranch1); | ||
} | ||
|
||
[Fact] | ||
public async Task WhenNameIsProvided_DoesNotAskForName_PullsChangesFromRemoteForBranchesInStack() | ||
{ | ||
// Arrange | ||
var sourceBranch = Some.BranchName(); | ||
var branch1 = Some.BranchName(); | ||
var branch2 = Some.BranchName(); | ||
using var repo = new TestGitRepositoryBuilder() | ||
.WithBranch(builder => builder.WithName(sourceBranch).PushToRemote()) | ||
.WithBranch(builder => builder.WithName(branch1).FromSourceBranch(sourceBranch).WithNumberOfEmptyCommits(10).PushToRemote()) | ||
.WithBranch(builder => builder.WithName(branch2).FromSourceBranch(branch1).WithNumberOfEmptyCommits(1).PushToRemote()) | ||
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(sourceBranch, 5, b => b.PushToRemote()) | ||
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(branch1, 3, b => b.PushToRemote()) | ||
.Build(); | ||
|
||
var tipOfRemoteSourceBranch = repo.GetTipOfRemoteBranch(sourceBranch); | ||
var tipOfRemoteBranch1 = repo.GetTipOfRemoteBranch(branch1); | ||
|
||
repo.GetCommitsReachableFromBranch(sourceBranch).Should().NotContain(tipOfRemoteSourceBranch); | ||
repo.GetCommitsReachableFromBranch(branch1).Should().NotContain(tipOfRemoteBranch1); | ||
|
||
var stackConfig = Substitute.For<IStackConfig>(); | ||
var inputProvider = Substitute.For<IInputProvider>(); | ||
var outputProvider = new TestOutputProvider(testOutputHelper); | ||
var gitOperations = new GitOperations(outputProvider, repo.GitOperationSettings); | ||
var handler = new PullStackCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); | ||
|
||
gitOperations.ChangeBranch(branch1); | ||
|
||
var stack1 = new Config.Stack("Stack1", repo.RemoteUri, sourceBranch, [branch1, branch2]); | ||
var stack2 = new Config.Stack("Stack2", repo.RemoteUri, sourceBranch, []); | ||
var stacks = new List<Config.Stack>([stack1, stack2]); | ||
stackConfig.Load().Returns(stacks); | ||
|
||
// Act | ||
await handler.Handle(new PullStackCommandInputs("Stack1")); | ||
|
||
// Assert | ||
repo.GetCommitsReachableFromBranch(sourceBranch).Should().Contain(tipOfRemoteSourceBranch); | ||
repo.GetCommitsReachableFromBranch(branch1).Should().Contain(tipOfRemoteBranch1); | ||
inputProvider.DidNotReceive().Select(Questions.SelectStack, Arg.Any<string[]>()); | ||
} | ||
|
||
[Fact] | ||
public async Task WhenNameIsProvided_ButStackDoesNotExist_Throws() | ||
{ | ||
// Arrange | ||
var sourceBranch = Some.BranchName(); | ||
var branch1 = Some.BranchName(); | ||
var branch2 = Some.BranchName(); | ||
using var repo = new TestGitRepositoryBuilder() | ||
.WithBranch(builder => builder.WithName(sourceBranch).PushToRemote()) | ||
.WithBranch(builder => builder.WithName(branch1).FromSourceBranch(sourceBranch).WithNumberOfEmptyCommits(10).PushToRemote()) | ||
.WithBranch(builder => builder.WithName(branch2).FromSourceBranch(branch1).WithNumberOfEmptyCommits(1).PushToRemote()) | ||
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(sourceBranch, 5, b => b.PushToRemote()) | ||
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(branch1, 3, b => b.PushToRemote()) | ||
.Build(); | ||
|
||
var tipOfRemoteSourceBranch = repo.GetTipOfRemoteBranch(sourceBranch); | ||
var tipOfRemoteBranch1 = repo.GetTipOfRemoteBranch(branch1); | ||
|
||
repo.GetCommitsReachableFromBranch(sourceBranch).Should().NotContain(tipOfRemoteSourceBranch); | ||
repo.GetCommitsReachableFromBranch(branch1).Should().NotContain(tipOfRemoteBranch1); | ||
|
||
var stackConfig = Substitute.For<IStackConfig>(); | ||
var inputProvider = Substitute.For<IInputProvider>(); | ||
var outputProvider = new TestOutputProvider(testOutputHelper); | ||
var gitOperations = new GitOperations(outputProvider, repo.GitOperationSettings); | ||
var handler = new PullStackCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); | ||
|
||
gitOperations.ChangeBranch(branch1); | ||
|
||
var stack1 = new Config.Stack("Stack1", repo.RemoteUri, sourceBranch, [branch1, branch2]); | ||
var stack2 = new Config.Stack("Stack2", repo.RemoteUri, sourceBranch, []); | ||
var stacks = new List<Config.Stack>([stack1, stack2]); | ||
stackConfig.Load().Returns(stacks); | ||
|
||
inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1"); | ||
|
||
// Act and assert | ||
var invalidStackName = Some.Name(); | ||
await handler.Invoking(async h => await h.Handle(new PullStackCommandInputs(invalidStackName))) | ||
.Should().ThrowAsync<InvalidOperationException>() | ||
.WithMessage($"Stack '{invalidStackName}' not found."); | ||
} | ||
|
||
[Fact] | ||
public async Task WhenChangesExistOnTheRemote_ForABranchThatIsNotInTheStack_TheyAreNotPulledDownToTheLocalBranch() | ||
{ | ||
// Arrange | ||
var sourceBranch = Some.BranchName(); | ||
var branch1 = Some.BranchName(); | ||
var branch2 = Some.BranchName(); | ||
using var repo = new TestGitRepositoryBuilder() | ||
.WithBranch(builder => builder.WithName(sourceBranch).PushToRemote()) | ||
.WithBranch(builder => builder.WithName(branch1).FromSourceBranch(sourceBranch).WithNumberOfEmptyCommits(10).PushToRemote()) | ||
.WithBranch(builder => builder.WithName(branch2).FromSourceBranch(branch1).WithNumberOfEmptyCommits(1).PushToRemote()) | ||
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(sourceBranch, 5, b => b.PushToRemote()) | ||
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(branch1, 3, b => b.PushToRemote()) | ||
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(branch2, 3, b => b.PushToRemote()) | ||
.Build(); | ||
|
||
var tipOfRemoteBranch2 = repo.GetTipOfRemoteBranch(branch2); | ||
|
||
var stackConfig = Substitute.For<IStackConfig>(); | ||
var inputProvider = Substitute.For<IInputProvider>(); | ||
var outputProvider = new TestOutputProvider(testOutputHelper); | ||
var gitOperations = new GitOperations(outputProvider, repo.GitOperationSettings); | ||
var handler = new PullStackCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig); | ||
|
||
gitOperations.ChangeBranch(branch1); | ||
|
||
var stack1 = new Config.Stack("Stack1", repo.RemoteUri, sourceBranch, [branch1]); | ||
var stack2 = new Config.Stack("Stack2", repo.RemoteUri, sourceBranch, [branch2]); | ||
var stacks = new List<Config.Stack>([stack1, stack2]); | ||
stackConfig.Load().Returns(stacks); | ||
|
||
inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1"); | ||
|
||
// Act | ||
await handler.Handle(new PullStackCommandInputs(null)); | ||
|
||
// Assert | ||
repo.GetCommitsReachableFromBranch(branch2).Should().NotContain(tipOfRemoteBranch2); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
using System.ComponentModel; | ||
using Spectre.Console; | ||
using Spectre.Console.Cli; | ||
using Stack.Commands.Helpers; | ||
using Stack.Config; | ||
using Stack.Git; | ||
using Stack.Infrastructure; | ||
|
||
namespace Stack.Commands; | ||
|
||
public class PullStackCommandSettings : DryRunCommandSettingsBase | ||
{ | ||
[Description("The name of the stack to pull changes from the remote for.")] | ||
[CommandOption("-n|--name")] | ||
public string? Name { get; init; } | ||
} | ||
|
||
public class PullStackCommand : AsyncCommand<PullStackCommandSettings> | ||
{ | ||
public override async Task<int> ExecuteAsync(CommandContext context, PullStackCommandSettings settings) | ||
{ | ||
var console = AnsiConsole.Console; | ||
var outputProvider = new ConsoleOutputProvider(console); | ||
|
||
var handler = new PullStackCommandHandler( | ||
new ConsoleInputProvider(console), | ||
outputProvider, | ||
new GitOperations(outputProvider, settings.GetGitOperationSettings()), | ||
new StackConfig()); | ||
|
||
await handler.Handle(new PullStackCommandInputs(settings.Name)); | ||
|
||
return 0; | ||
} | ||
} | ||
|
||
public record PullStackCommandInputs(string? Name); | ||
public class PullStackCommandHandler( | ||
IInputProvider inputProvider, | ||
IOutputProvider outputProvider, | ||
IGitOperations gitOperations, | ||
IStackConfig stackConfig) | ||
{ | ||
public async Task Handle(PullStackCommandInputs inputs) | ||
{ | ||
await Task.CompletedTask; | ||
var stacks = stackConfig.Load(); | ||
|
||
var remoteUri = gitOperations.GetRemoteUri(); | ||
var stacksForRemote = stacks.Where(s => s.RemoteUri.Equals(remoteUri, StringComparison.OrdinalIgnoreCase)).ToList(); | ||
|
||
if (stacksForRemote.Count == 0) | ||
{ | ||
outputProvider.Information("No stacks found for current repository."); | ||
return; | ||
} | ||
|
||
var currentBranch = gitOperations.GetCurrentBranch(); | ||
|
||
var stack = inputProvider.SelectStack(outputProvider, inputs.Name, stacksForRemote, currentBranch); | ||
|
||
if (stack is null) | ||
throw new InvalidOperationException($"Stack '{inputs.Name}' not found."); | ||
|
||
var branchStatus = gitOperations.GetBranchStatuses([stack.SourceBranch, .. stack.Branches]); | ||
|
||
foreach (var branch in branchStatus.Where(b => b.Value.RemoteBranchExists)) | ||
{ | ||
outputProvider.Information($"Pulling changes for {branch.Value.BranchName.Branch()} from remote"); | ||
gitOperations.ChangeBranch(branch.Value.BranchName); | ||
gitOperations.PullBranch(branch.Value.BranchName); | ||
} | ||
|
||
gitOperations.ChangeBranch(currentBranch); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters