From c913f920fa4aff068a3fe51ee9804df22d86821a Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 5 Sep 2025 17:52:01 +1000 Subject: [PATCH 1/5] Improving logging and status display --- .../Branch/NewBranchCommandHandlerTests.cs | 30 ++++--- .../Remote/PullStackCommandHandlerTests.cs | 9 +- .../Remote/PushStackCommandHandlerTests.cs | 15 ++-- .../Stack/NewStackCommandHandlerTests.cs | 21 +++-- .../Stack/UpdateStackCommandHandlerTests.cs | 36 +++++--- .../Helpers/TestDisplayProvider.cs | 7 +- src/Stack/Commands/Branch/NewBranchCommand.cs | 21 ++--- .../Commands/Config/OpenConfigCommand.cs | 2 +- .../Helpers/InputProviderExtensionMethods.cs | 25 ++++-- src/Stack/Commands/Helpers/StackActions.cs | 79 +++++++++-------- src/Stack/Commands/Helpers/StackHelpers.cs | 16 +--- .../PullRequests/CreatePullRequestsCommand.cs | 4 +- src/Stack/Commands/Remote/PullStackCommand.cs | 15 +++- src/Stack/Commands/Remote/PushStackCommand.cs | 16 +++- src/Stack/Commands/Remote/SyncStackCommand.cs | 87 +++++++++++-------- src/Stack/Commands/Stack/NewStackCommand.cs | 6 +- .../Commands/Stack/StackStatusCommand.cs | 20 +++-- .../Commands/Stack/UpdateStackCommand.cs | 9 +- src/Stack/Git/GitClient.cs | 16 ++-- src/Stack/Git/ProcessHelpers.cs | 12 +-- src/Stack/Infrastructure/AnsiConsoleLogger.cs | 6 +- src/Stack/Infrastructure/Commands/Command.cs | 3 +- src/Stack/Infrastructure/CommonOptions.cs | 8 +- .../Infrastructure/ConsoleDisplayProvider.cs | 54 +++++++++++- .../HostApplicationBuilderExtensions.cs | 8 +- 25 files changed, 346 insertions(+), 179 deletions(-) diff --git a/src/Stack.Tests/Commands/Branch/NewBranchCommandHandlerTests.cs b/src/Stack.Tests/Commands/Branch/NewBranchCommandHandlerTests.cs index 21a9e3fa..a45414f8 100644 --- a/src/Stack.Tests/Commands/Branch/NewBranchCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/Branch/NewBranchCommandHandlerTests.cs @@ -26,6 +26,7 @@ public async Task WhenNoInputsProvided_AsksForStackAndBranchAndParentBranch_Crea var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -37,7 +38,7 @@ public async Task WhenNoInputsProvided_AsksForStackAndBranchAndParentBranch_Crea .WithRemoteUri(remoteUri) .WithSourceBranch(sourceBranch)) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider.Text(Questions.BranchName, Arg.Any(), Arg.Any()).Returns(newBranch); @@ -72,6 +73,7 @@ public async Task WhenStackNameProvided_DoesNotAskForStackName_CreatesAndAddsBra var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -83,7 +85,7 @@ public async Task WhenStackNameProvided_DoesNotAskForStackName_CreatesAndAddsBra .WithRemoteUri(remoteUri) .WithSourceBranch(sourceBranch)) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); inputProvider.Text(Questions.BranchName, Arg.Any(), Arg.Any()).Returns(newBranch); inputProvider.Select(Questions.SelectParentBranch, Arg.Any(), Arg.Any()).Returns(anotherBranch); @@ -117,6 +119,7 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName_CreatesAndAddsBr var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -124,7 +127,7 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName_CreatesAndAddsBr .WithSourceBranch(sourceBranch) .WithBranch(branch => branch.WithName(anotherBranch))) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); inputProvider.Text(Questions.BranchName, Arg.Any(), Arg.Any()).Returns(newBranch); inputProvider.Select(Questions.SelectParentBranch, Arg.Any(), Arg.Any()).Returns(anotherBranch); @@ -157,6 +160,7 @@ public async Task WhenStackNameProvided_ButStackDoesNotExist_Throws() var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -168,7 +172,7 @@ public async Task WhenStackNameProvided_ButStackDoesNotExist_Throws() .WithRemoteUri(remoteUri) .WithSourceBranch(sourceBranch)) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(sourceBranch); @@ -194,6 +198,7 @@ public async Task WhenBranchNameProvided_DoesNotAskForBranchName_CreatesAndAddsB var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -205,7 +210,7 @@ public async Task WhenBranchNameProvided_DoesNotAskForBranchName_CreatesAndAddsB .WithRemoteUri(remoteUri) .WithSourceBranch(sourceBranch)) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider.Select(Questions.SelectParentBranch, Arg.Any(), Arg.Any()).Returns(anotherBranch); @@ -238,6 +243,7 @@ public async Task WhenBranchNameProvided_ButBranchAlreadyExistLocally_Throws() var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -248,7 +254,7 @@ public async Task WhenBranchNameProvided_ButBranchAlreadyExistLocally_Throws() .WithRemoteUri(remoteUri) .WithSourceBranch(sourceBranch)) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); gitClient.GetRemoteUri().Returns(remoteUri); @@ -276,6 +282,7 @@ public async Task WhenBranchNameProvided_ButBranchAlreadyExistsInStack_Throws() var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -288,7 +295,7 @@ public async Task WhenBranchNameProvided_ButBranchAlreadyExistsInStack_Throws() .WithRemoteUri(remoteUri) .WithSourceBranch(sourceBranch)) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); gitClient.GetRemoteUri().Returns(remoteUri); @@ -315,6 +322,7 @@ public async Task WhenPushToTheRemoteFails_StillCreatesTheBranchLocallyAndAddsIt var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -326,7 +334,7 @@ public async Task WhenPushToTheRemoteFails_StillCreatesTheBranchLocallyAndAddsIt .WithRemoteUri(remoteUri) .WithSourceBranch(sourceBranch)) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); gitClient.When(gc => gc.PushNewBranch(newBranch)).Do(_ => { throw new Exception("Failed to push branch"); }); @@ -365,6 +373,7 @@ public async Task WhenParentBranchNotProvided_AsksForParentBranch_CreatesNewBran var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -376,7 +385,7 @@ public async Task WhenParentBranchNotProvided_AsksForParentBranch_CreatesNewBran .WithRemoteUri(remoteUri) .WithSourceBranch(sourceBranch)) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider.Text(Questions.BranchName, Arg.Any(), Arg.Any()).Returns(newBranch); @@ -412,6 +421,7 @@ public async Task WhenParentBranchProvided_DoesNotAskForParentBranch_CreatesNewB var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); var gitClient = Substitute.For(); + var displayProvider = new TestDisplayProvider(testOutputHelper); var stackConfig = new TestStackConfigBuilder() .WithStack(stack => stack .WithName("Stack1") @@ -423,7 +433,7 @@ public async Task WhenParentBranchProvided_DoesNotAskForParentBranch_CreatesNewB .WithRemoteUri(remoteUri) .WithSourceBranch(sourceBranch)) .Build(); - var handler = new NewBranchCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewBranchCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider.Text(Questions.BranchName, Arg.Any(), Arg.Any()).Returns(newBranch); diff --git a/src/Stack.Tests/Commands/Remote/PullStackCommandHandlerTests.cs b/src/Stack.Tests/Commands/Remote/PullStackCommandHandlerTests.cs index 60436ddf..d8816851 100644 --- a/src/Stack.Tests/Commands/Remote/PullStackCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/Remote/PullStackCommandHandlerTests.cs @@ -35,9 +35,10 @@ public async Task WhenNoStackNameIsProvided_AsksForStack_PullsChangesForTheCorre .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new PullStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new PullStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -76,9 +77,10 @@ public async Task WhenNameIsProvided_DoesNotAskForName_PullsChangesForTheCorrect .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new PullStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new PullStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -116,9 +118,10 @@ public async Task WhenNameIsProvided_ButStackDoesNotExist_Throws() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new PullStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new PullStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); diff --git a/src/Stack.Tests/Commands/Remote/PushStackCommandHandlerTests.cs b/src/Stack.Tests/Commands/Remote/PushStackCommandHandlerTests.cs index f0da8bef..40184949 100644 --- a/src/Stack.Tests/Commands/Remote/PushStackCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/Remote/PushStackCommandHandlerTests.cs @@ -39,9 +39,10 @@ public async Task WhenChangesExistLocally_TheyArePushedToTheRemote() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new PushStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new PushStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.ChangeBranch(branch1); @@ -81,9 +82,10 @@ public async Task WhenNameIsProvided_DoesNotAskForName_PushesChangesToRemoteForB .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new PushStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new PushStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.ChangeBranch(branch1); @@ -124,9 +126,10 @@ public async Task WhenNameIsProvided_ButStackDoesNotExist_Throws() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new PushStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new PushStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.ChangeBranch(branch1); @@ -166,9 +169,10 @@ public async Task WhenNumberOfBranchesIsGreaterThanMaxBatchSize_ChangesAreSucces .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new PushStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new PushStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.ChangeBranch(branch1); @@ -208,9 +212,10 @@ public async Task WhenUsingForceWithLease_ChangesAreForcePushedToTheRemote() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitHubClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new PushStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new PushStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.ChangeBranch(branch1); diff --git a/src/Stack.Tests/Commands/Stack/NewStackCommandHandlerTests.cs b/src/Stack.Tests/Commands/Stack/NewStackCommandHandlerTests.cs index a8f5d20c..887b9a0a 100644 --- a/src/Stack.Tests/Commands/Stack/NewStackCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/Stack/NewStackCommandHandlerTests.cs @@ -25,8 +25,9 @@ public async Task WithAnExistingBranch_TheStackIsCreatedAndTheCurrentBranchIsCha var stackConfig = new TestStackConfigBuilder().Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); - var handler = new NewStackCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); gitClient.GetLocalBranchesOrderedByMostRecentCommitterDate().Returns([sourceBranch, existingBranch]); gitClient.GetRemoteUri().Returns(remoteUri); @@ -59,8 +60,9 @@ public async Task WithNoBranch_TheStackIsCreatedAndTheCurrentBranchIsNotChanged( var stackConfig = new TestStackConfigBuilder().Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); - var handler = new NewStackCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); gitClient.GetLocalBranchesOrderedByMostRecentCommitterDate().Returns([sourceBranch, existingBranch]); gitClient.GetRemoteUri().Returns(remoteUri); @@ -91,8 +93,9 @@ public async Task WhenStackNameIsProvidedInInputs_TheProviderIsNotAskedForAName_ var stackConfig = new TestStackConfigBuilder().Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); - var handler = new NewStackCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); gitClient.GetLocalBranchesOrderedByMostRecentCommitterDate().Returns([sourceBranch]); gitClient.GetRemoteUri().Returns(remoteUri); @@ -123,8 +126,9 @@ public async Task WhenSourceBranchIsProvidedInInputs_TheProviderIsNotAskedForThe var stackConfig = new TestStackConfigBuilder().Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); - var handler = new NewStackCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); gitClient.GetLocalBranchesOrderedByMostRecentCommitterDate().Returns([sourceBranch]); gitClient.GetRemoteUri().Returns(remoteUri); @@ -156,8 +160,9 @@ public async Task WhenBranchNameIsProvidedInInputs_TheProviderIsNotAskedForTheBr var stackConfig = new TestStackConfigBuilder().Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); - var handler = new NewStackCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); gitClient.GetLocalBranchesOrderedByMostRecentCommitterDate().Returns([sourceBranch]); gitClient.GetRemoteUri().Returns(remoteUri); @@ -191,8 +196,9 @@ public async Task WithANewBranch_TheStackIsCreatedAndTheBranchExistsOnTheRemote( var stackConfig = new TestStackConfigBuilder().Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); - var handler = new NewStackCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); gitClient.GetLocalBranchesOrderedByMostRecentCommitterDate().Returns([sourceBranch]); gitClient.GetRemoteUri().Returns(remoteUri); @@ -225,8 +231,9 @@ public async Task WithANewBranch_AndThePushFails_TheStackIsStillCreatedSuccessfu var stackConfig = new TestStackConfigBuilder().Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); - var handler = new NewStackCommandHandler(inputProvider, logger, gitClient, stackConfig); + var handler = new NewStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig); gitClient.GetLocalBranchesOrderedByMostRecentCommitterDate().Returns([sourceBranch]); gitClient.GetRemoteUri().Returns(remoteUri); diff --git a/src/Stack.Tests/Commands/Stack/UpdateStackCommandHandlerTests.cs b/src/Stack.Tests/Commands/Stack/UpdateStackCommandHandlerTests.cs index 16e7f5d3..70d928ef 100644 --- a/src/Stack.Tests/Commands/Stack/UpdateStackCommandHandlerTests.cs +++ b/src/Stack.Tests/Commands/Stack/UpdateStackCommandHandlerTests.cs @@ -35,9 +35,10 @@ public async Task WhenNameIsProvided_DoesNotAskForName_UpdatesCorrectStack() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -72,9 +73,10 @@ public async Task WhenNameIsProvided_ButStackDoesNotExist_Throws() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -108,9 +110,10 @@ public async Task WhenOnASpecificBranchInTheStack_TheSameBranchIsSetAsCurrentAft .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); // We are on a specific branch in the stack gitClient.GetCurrentBranch().Returns(branch1); @@ -143,9 +146,10 @@ public async Task WhenOnlyASingleStackExists_DoesNotAskForStackName_UpdatesStack .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); gitClient.GetCurrentBranch().Returns(branch1); @@ -180,9 +184,10 @@ public async Task WhenRebaseIsSpecified_StackIsUpdatedUsingRebase() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); gitClient.GetRemoteUri().Returns(remoteUri); @@ -217,9 +222,10 @@ public async Task WhenGitConfigValueIsSetToRebase_StackIsUpdatedUsingRebase() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); gitClient.GetRemoteUri().Returns(remoteUri); @@ -254,9 +260,10 @@ public async Task WhenGitConfigValueIsSetToRebase_ButMergeIsSpecified_StackIsUpd .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); gitClient.GetRemoteUri().Returns(remoteUri); @@ -292,9 +299,10 @@ public async Task WhenGitConfigValueIsSetToMerge_StackIsUpdatedUsingMerge() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); gitClient.GetRemoteUri().Returns(remoteUri); @@ -330,9 +338,10 @@ public async Task WhenGitConfigValueIsSetToMerge_ButRebaseIsSpecified_StackIsUpd .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); gitClient.GetRemoteUri().Returns(remoteUri); @@ -368,9 +377,10 @@ public async Task WhenGitConfigValueDoesNotExist_AndRebaseIsSelected_StackIsUpda .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider.Select(Questions.SelectUpdateStrategy, Arg.Any(), Arg.Any()).Returns(UpdateStrategy.Rebase); @@ -407,9 +417,10 @@ public async Task WhenGitConfigValueDoesNotExist_AndMergeIsSelected_StackIsUpdat .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); inputProvider.Select(Questions.SelectStack, Arg.Any(), Arg.Any()).Returns("Stack1"); inputProvider.Select(Questions.SelectUpdateStrategy, Arg.Any(), Arg.Any()).Returns(UpdateStrategy.Merge); @@ -441,9 +452,10 @@ public async Task WhenBothRebaseAndMergeAreSpecified_AnErrorIsThrown() .Build(); var inputProvider = Substitute.For(); var logger = XUnitLogger.CreateLogger(testOutputHelper); + var displayProvider = new TestDisplayProvider(testOutputHelper); var gitClient = Substitute.For(); var stackActions = Substitute.For(); - var handler = new UpdateStackCommandHandler(inputProvider, logger, gitClient, stackConfig, stackActions); + var handler = new UpdateStackCommandHandler(inputProvider, logger, displayProvider, gitClient, stackConfig, stackActions); gitClient.GetRemoteUri().Returns(remoteUri); diff --git a/src/Stack.Tests/Helpers/TestDisplayProvider.cs b/src/Stack.Tests/Helpers/TestDisplayProvider.cs index 10852b45..3109d113 100644 --- a/src/Stack.Tests/Helpers/TestDisplayProvider.cs +++ b/src/Stack.Tests/Helpers/TestDisplayProvider.cs @@ -6,9 +6,14 @@ namespace Stack.Tests.Helpers; public class TestDisplayProvider(ITestOutputHelper testOutputHelper) : IDisplayProvider { + public async Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default) + { + testOutputHelper.WriteLine($"STATUS: {message}"); + return await action(cancellationToken); + } + public async Task DisplayStatus(string message, Func action, CancellationToken cancellationToken = default) { - await Task.CompletedTask; testOutputHelper.WriteLine($"STATUS: {message}"); await action(cancellationToken); } diff --git a/src/Stack/Commands/Branch/NewBranchCommand.cs b/src/Stack/Commands/Branch/NewBranchCommand.cs index c46be3aa..b8af6098 100644 --- a/src/Stack/Commands/Branch/NewBranchCommand.cs +++ b/src/Stack/Commands/Branch/NewBranchCommand.cs @@ -47,6 +47,7 @@ public record NewBranchCommandInputs(string? StackName, string? BranchName, stri public class NewBranchCommandHandler( IInputProvider inputProvider, ILogger logger, + IDisplayProvider displayProvider, IGitClient gitClient, IStackConfig stackConfig) : CommandHandlerBase @@ -119,12 +120,13 @@ public override async Task Handle(NewBranchCommandInputs inputs, CancellationTok stackConfig.Save(stackData); - logger.BranchCreated(branchName); - try { - logger.PushingBranchToRemote(branchName); - gitClient.PushNewBranch(branchName); + await displayProvider.DisplayStatus($"Pushing branch '{branchName}' to remote repository...", async (ct) => + { + await Task.CompletedTask; + gitClient.PushNewBranch(branchName); + }, cancellationToken); } catch (Exception) { @@ -132,17 +134,16 @@ public override async Task Handle(NewBranchCommandInputs inputs, CancellationTok } gitClient.ChangeBranch(branchName); + + logger.BranchCreated(branchName, stack.Name); } } internal static partial class LoggerExtensionMethods { - [LoggerMessage(Level = LogLevel.Information, Message = "Creating branch {Branch} from {SourceBranch} in stack \"{Stack}\"")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Creating branch {Branch} from {SourceBranch} in stack \"{Stack}\"")] public static partial void CreatingBranch(this ILogger logger, string branch, string sourceBranch, string stack); - [LoggerMessage(Level = LogLevel.Information, Message = "Branch {Branch} created.")] - public static partial void BranchCreated(this ILogger logger, string branch); - - [LoggerMessage(Level = LogLevel.Information, Message = "Pushing branch {Branch} to remote repository")] - public static partial void PushingBranchToRemote(this ILogger logger, string branch); + [LoggerMessage(Level = LogLevel.Information, Message = "Branch {Branch} created in stack \"{Stack}\".")] + public static partial void BranchCreated(this ILogger logger, string branch, string stack); } \ No newline at end of file diff --git a/src/Stack/Commands/Config/OpenConfigCommand.cs b/src/Stack/Commands/Config/OpenConfigCommand.cs index 76ecfcd2..6179a371 100644 --- a/src/Stack/Commands/Config/OpenConfigCommand.cs +++ b/src/Stack/Commands/Config/OpenConfigCommand.cs @@ -46,6 +46,6 @@ protected override async Task Execute(ParseResult parseResult, CancellationToken internal static partial class LoggerExtensionMethods { - [LoggerMessage(Level = LogLevel.Information, Message = "No config file found.")] + [LoggerMessage(Level = LogLevel.Warning, Message = "No config file found.")] public static partial void NoConfigFileFound(this ILogger logger); } diff --git a/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs b/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs index eef30baa..2ea37ab6 100644 --- a/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs +++ b/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs @@ -64,14 +64,25 @@ public static async Task MultiSelect( string currentBranch, CancellationToken cancellationToken) { - var stackNames = stacks.OrderByCurrentStackThenByName(currentBranch).Select(s => s.Name).ToArray(); + if (name is not null) + { + return stacks.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); + } + + if (stacks.Count == 1) + { + return stacks.First(); + } + var stacksContainingCurrentBranch = stacks.Where(s => s.IsCurrentStack(currentBranch)).ToList(); - - var stackSelection = - name ?? - (stacks.Count == 1 ? stacks.First().Name : null) ?? - (stacksContainingCurrentBranch.Count == 1 ? stacksContainingCurrentBranch.First().Name : null) ?? - await inputProvider.Select(Questions.SelectStack, stackNames, cancellationToken); + + if (stacksContainingCurrentBranch.Count == 1) + { + return stacksContainingCurrentBranch.First(); + } + + var stackNames = stacks.OrderByCurrentStackThenByName(currentBranch).Select(s => s.Name).ToArray(); + var stackSelection = await inputProvider.Select(Questions.SelectStack, stackNames, cancellationToken); var stack = stacks.FirstOrDefault(s => s.Name.Equals(stackSelection, StringComparison.OrdinalIgnoreCase)); if (stack is not null) diff --git a/src/Stack/Commands/Helpers/StackActions.cs b/src/Stack/Commands/Helpers/StackActions.cs index 5eee92ba..4a834983 100644 --- a/src/Stack/Commands/Helpers/StackActions.cs +++ b/src/Stack/Commands/Helpers/StackActions.cs @@ -2,6 +2,7 @@ using Stack.Infrastructure; using Stack.Git; using Microsoft.Extensions.Logging; +using Spectre.Console; namespace Stack.Commands.Helpers { @@ -301,36 +302,40 @@ private async Task UpdateBranchLineUsingRebase(StackStatus status, List { - var result = await ConflictResolutionDetector.WaitForConflictResolution( - gitClient, - logger, - ConflictOperationType.Rebase, - TimeSpan.FromSeconds(1), - null, - cancellationToken); + gitClient.ChangeBranch(branch); - switch (result) + try { - case ConflictResolutionResult.Completed: - break; - case ConflictResolutionResult.Aborted: - throw new Exception("Rebase aborted due to conflicts."); - case ConflictResolutionResult.Timeout: - throw new TimeoutException("Timed out waiting for rebase conflict resolution."); - case ConflictResolutionResult.NotStarted: - logger.LogWarning("Expected rebase to be in progress but marker not found. Proceeding cautiously."); - break; + gitClient.RebaseFromLocalSourceBranch(sourceBranchName); } - } + catch (ConflictException) + { + var result = await ConflictResolutionDetector.WaitForConflictResolution( + gitClient, + logger, + ConflictOperationType.Rebase, + TimeSpan.FromSeconds(1), + null, + cancellationToken); + + switch (result) + { + case ConflictResolutionResult.Completed: + break; + case ConflictResolutionResult.Aborted: + throw new Exception("Rebase aborted due to conflicts."); + case ConflictResolutionResult.Timeout: + throw new TimeoutException("Timed out waiting for rebase conflict resolution."); + case ConflictResolutionResult.NotStarted: + logger.LogWarning("Expected rebase to be in progress but marker not found. Proceeding cautiously."); + break; + } + } + + await displayProvider.DisplayMessage($"{Emoji.Known.CheckMark} Rebasing {branch} onto {sourceBranchName}...", ct); + }, cancellationToken); } private async Task RebaseOntoNewParent( @@ -375,42 +380,42 @@ private async Task RebaseOntoNewParent( internal static partial class LoggerExtensionMethods { - [LoggerMessage(Level = LogLevel.Information, Message = "Pulling changes for {Branch} from remote")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Pulling changes for {Branch} from remote")] public static partial void PullingCurrentBranch(this ILogger logger, string branch); - [LoggerMessage(Level = LogLevel.Information, Message = "Pulling changes for {Branch} (worktree: \"{WorktreePath}\") from remote")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Pulling changes for {Branch} (worktree: \"{WorktreePath}\") from remote")] public static partial void PullingWorktreeBranch(this ILogger logger, string branch, string worktreePath); - [LoggerMessage(Level = LogLevel.Information, Message = "Fetching changes for {Branches} from remote")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Fetching changes for {Branches} from remote")] public static partial void FetchingNonCurrentBranches(this ILogger logger, string branches); - [LoggerMessage(Level = LogLevel.Information, Message = "Pushing new branch {Branch} to remote")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Pushing new branch {Branch} to remote")] public static partial void PushingNewBranch(this ILogger logger, string branch); - [LoggerMessage(Level = LogLevel.Information, Message = "Pushing changes for {Branches} to remote")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Pushing changes for {Branches} to remote")] public static partial void PushingBranches(this ILogger logger, string branches); - [LoggerMessage(Level = LogLevel.Information, Message = "Updating stack \"{Stack}\" using merge...")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Updating stack \"{Stack}\" using merge...")] public static partial void UpdatingStackUsingMerge(this ILogger logger, string stack); [LoggerMessage(Level = LogLevel.Trace, Message = "Branch {Branch} no longer exists on the remote repository or the associated pull request is no longer open. Skipping...")] public static partial void TraceSkippingInactiveBranch(this ILogger logger, string branch); - [LoggerMessage(Level = LogLevel.Information, Message = "Merging {SourceBranch} into {Branch}")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Merging {SourceBranch} into {Branch}")] public static partial void MergingBranch(this ILogger logger, string sourceBranch, string branch); - [LoggerMessage(Level = LogLevel.Information, Message = "Updating stack \"{Stack}\" using rebase...")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Updating stack \"{Stack}\" using rebase...")] public static partial void UpdatingStackUsingRebase(this ILogger logger, string stack); - [LoggerMessage(Level = LogLevel.Information, Message = "Rebasing stack \"{Stack}\" for branch line: {SourceBranch} --> {BranchLine}")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Rebasing stack \"{Stack}\" for branch line: {SourceBranch} --> {BranchLine}")] public static partial void RebasingStackForBranchLine(this ILogger logger, string stack, string sourceBranch, string branchLine); [LoggerMessage(Level = LogLevel.Debug, Message = "No active branches found for branch line.")] public static partial void NoActiveBranchesFound(this ILogger logger); - [LoggerMessage(Level = LogLevel.Information, Message = "Rebasing {Branch} onto {SourceBranch}")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Rebasing {Branch} onto {SourceBranch}")] public static partial void RebasingBranchOnto(this ILogger logger, string branch, string sourceBranch); - [LoggerMessage(Level = LogLevel.Information, Message = "Rebasing {Branch} onto new parent {NewParentBranch}")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Rebasing {Branch} onto new parent {NewParentBranch}")] public static partial void RebasingBranchOntoNewParent(this ILogger logger, string branch, string newParentBranch); } diff --git a/src/Stack/Commands/Helpers/StackHelpers.cs b/src/Stack/Commands/Helpers/StackHelpers.cs index 6d1c4348..08b36d9a 100644 --- a/src/Stack/Commands/Helpers/StackHelpers.cs +++ b/src/Stack/Commands/Helpers/StackHelpers.cs @@ -121,19 +121,7 @@ public static List GetStackStatus( .ToArray(); var branchStatuses = gitClient.GetBranchStatuses(allBranchesInStacks); - - if (includePullRequestStatus) - { - displayProvider.DisplayStatus("Checking status of GitHub pull requests...", async (ct) => - { - await Task.CompletedTask; - EvaluateBranchStatusDetails(logger, gitClient, gitHubClient, includePullRequestStatus, stacksToReturnStatusFor, stacksOrderedByCurrentBranch, branchStatuses); - }); - } - else - { - EvaluateBranchStatusDetails(logger, gitClient, gitHubClient, includePullRequestStatus, stacksToReturnStatusFor, stacksOrderedByCurrentBranch, branchStatuses); - } + EvaluateBranchStatusDetails(logger, gitClient, gitHubClient, includePullRequestStatus, stacksToReturnStatusFor, stacksOrderedByCurrentBranch, branchStatuses); return stacksToReturnStatusFor; @@ -640,7 +628,7 @@ internal static partial class LoggerExtensionMethods [LoggerMessage(Level = LogLevel.Information, Message = " {Branch}")] public static partial void BranchToCleanup(this ILogger logger, string branch); - [LoggerMessage(Level = LogLevel.Information, Message = "Deleting local branch {Branch}")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Deleting local branch {Branch}")] public static partial void DeletingLocalBranch(this ILogger logger, string branch); [LoggerMessage(Level = LogLevel.Information, Message = "Updating pull request {PullRequest} with stack details")] diff --git a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs index e07c2833..0ef2d55e 100644 --- a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs +++ b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs @@ -292,7 +292,7 @@ internal static partial class LoggerExtensionMethods [LoggerMessage(Level = LogLevel.Information, Message = "Pull request selected: {HeadBranch} -> {BaseBranch}")] public static partial void PullRequestSelected(this ILogger logger, string headBranch, string baseBranch); - [LoggerMessage(Level = LogLevel.Information, Message = "Creating pull request for branch {HeadBranch} to {BaseBranch}")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Creating pull request for branch {HeadBranch} to {BaseBranch}")] public static partial void CreatingPullRequest(this ILogger logger, string headBranch, string baseBranch); [LoggerMessage(Level = LogLevel.Information, Message = "Pull request \"{PullRequest}\" created for branch {HeadBranch} to {BaseBranch}")] @@ -301,6 +301,6 @@ internal static partial class LoggerExtensionMethods [LoggerMessage(Level = LogLevel.Information, Message = "No new pull requests to create.")] public static partial void NoPullRequestsToCreate(this ILogger logger); - [LoggerMessage(Level = LogLevel.Information, Message = "Found pull request template at \"{TemplatePath}\", this will be used as the default body for each pull request.")] + [LoggerMessage(Level = LogLevel.Debug, Message = "Found pull request template at \"{TemplatePath}\", this will be used as the default body for each pull request.")] public static partial void FoundPullRequestTemplate(this ILogger logger, string templatePath); } \ No newline at end of file diff --git a/src/Stack/Commands/Remote/PullStackCommand.cs b/src/Stack/Commands/Remote/PullStackCommand.cs index 9a2bd71a..73cb6efa 100644 --- a/src/Stack/Commands/Remote/PullStackCommand.cs +++ b/src/Stack/Commands/Remote/PullStackCommand.cs @@ -37,6 +37,7 @@ public record PullStackCommandInputs(string? Stack); public class PullStackCommandHandler( IInputProvider inputProvider, ILogger logger, + IDisplayProvider displayProvider, IGitClient gitClient, IStackConfig stackConfig, IStackActions stackActions) @@ -63,8 +64,20 @@ public override async Task Handle(PullStackCommandInputs inputs, CancellationTok if (stack is null) throw new InvalidOperationException($"Stack '{inputs.Stack}' not found."); - stackActions.PullChanges(stack); + await displayProvider.DisplayStatus($"Pulling changes from remote repository...", async (ct) => + { + await Task.CompletedTask; + stackActions.PullChanges(stack); + }, cancellationToken); gitClient.ChangeBranch(currentBranch); + + logger.PulledStack(stack.Name); } } + +internal static partial class LoggerExtensionMethods +{ + [LoggerMessage(2, LogLevel.Information, "Pulled changes for stack '{Stack}'.")] + public static partial void PulledStack(this ILogger logger, string stack); +} diff --git a/src/Stack/Commands/Remote/PushStackCommand.cs b/src/Stack/Commands/Remote/PushStackCommand.cs index 584d3c2f..621f63dd 100644 --- a/src/Stack/Commands/Remote/PushStackCommand.cs +++ b/src/Stack/Commands/Remote/PushStackCommand.cs @@ -50,6 +50,7 @@ public record PushStackCommandInputs(string? Stack, int MaxBatchSize, bool Force public class PushStackCommandHandler( IInputProvider inputProvider, ILogger logger, + IDisplayProvider displayProvider, IGitClient gitClient, IStackConfig stackConfig, IStackActions stackActions) @@ -76,7 +77,18 @@ public override async Task Handle(PushStackCommandInputs inputs, CancellationTok if (stack is null) throw new InvalidOperationException($"Stack '{inputs.Stack}' not found."); - stackActions.PushChanges(stack, inputs.MaxBatchSize, inputs.ForceWithLease); - return; + await displayProvider.DisplayStatus($"Pushing changes to remote repository...", async (ct) => + { + await Task.CompletedTask; + stackActions.PushChanges(stack, inputs.MaxBatchSize, inputs.ForceWithLease); + }, cancellationToken); + + logger.PushedStack(stack.Name); } } + +internal static partial class LoggerExtensionMethods +{ + [LoggerMessage(1, LogLevel.Information, "Pushed changes for stack '{Stack}'.")] + public static partial void PushedStack(this ILogger logger, string stack); +} diff --git a/src/Stack/Commands/Remote/SyncStackCommand.cs b/src/Stack/Commands/Remote/SyncStackCommand.cs index 55b0dd94..51f77f00 100644 --- a/src/Stack/Commands/Remote/SyncStackCommand.cs +++ b/src/Stack/Commands/Remote/SyncStackCommand.cs @@ -1,4 +1,5 @@ using System.CommandLine; +using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Stack.Commands.Helpers; using Stack.Config; @@ -94,58 +95,74 @@ public override async Task Handle(SyncStackCommandInputs inputs, CancellationTok if (stack is null) throw new InvalidOperationException($"Stack '{inputs.Stack}' not found."); - FetchChanges(); + await displayProvider.DisplayStatus("Fetching changes from remote repository...", async (ct) => + { + await Task.CompletedTask; + gitClient.Fetch(true); + }); - var status = StackHelpers.GetStackStatus( - stack, - currentBranch, - logger, - displayProvider, - gitClient, - gitHubClient, - true); + if (!inputs.Confirm) + { + await displayProvider.DisplayStatus("Checking stack status...", async (ct) => + { + var status = StackHelpers.GetStackStatus( + stack, + currentBranch, + logger, + displayProvider, + gitClient, + gitHubClient, + true); - await StackHelpers.OutputStackStatus(status, displayProvider, cancellationToken); + await StackHelpers.OutputStackStatus(status, displayProvider, cancellationToken); + }, cancellationToken); - await displayProvider.DisplayNewLine(cancellationToken); + await displayProvider.DisplayNewLine(cancellationToken); - if (inputs.Confirm || await inputProvider.Confirm(Questions.ConfirmSyncStack, cancellationToken)) - { - logger.SyncingStackWithRemote(stack.Name); + if (!await inputProvider.Confirm(Questions.ConfirmSyncStack, cancellationToken)) + { + return; + } + } + await displayProvider.DisplayStatus("Pulling changes from remote repository...", async (ct) => + { + await Task.CompletedTask; stackActions.PullChanges(stack); + }, cancellationToken); - var updateStrategy = await StackHelpers.GetUpdateStrategy( - inputs.Merge == true ? UpdateStrategy.Merge : inputs.Rebase == true ? UpdateStrategy.Rebase : null, - gitClient, inputProvider, logger, cancellationToken); - - await stackActions.UpdateStack(stack, updateStrategy, cancellationToken); + var updateStrategy = await StackHelpers.GetUpdateStrategy( + inputs.Merge == true ? UpdateStrategy.Merge : inputs.Rebase == true ? UpdateStrategy.Rebase : null, + gitClient, inputProvider, logger, cancellationToken); - var forceWithLease = updateStrategy == UpdateStrategy.Rebase; + await displayProvider.DisplayStatus("Updating stack...", async (ct) => + { + await stackActions.UpdateStack(stack, updateStrategy, ct); + }, cancellationToken); - if (!inputs.NoPush) - stackActions.PushChanges(stack, inputs.MaxBatchSize, forceWithLease); + var forceWithLease = updateStrategy == UpdateStrategy.Rebase; - if (stack.SourceBranch.Equals(currentBranch, StringComparison.InvariantCultureIgnoreCase) || - stack.AllBranchNames.Contains(currentBranch, StringComparer.OrdinalIgnoreCase)) + if (!inputs.NoPush) + { + await displayProvider.DisplayStatus("Pushing changes to remote repository...", async (ct) => { - gitClient.ChangeBranch(currentBranch); - } + await Task.CompletedTask; + stackActions.PushChanges(stack, inputs.MaxBatchSize, forceWithLease); + }, cancellationToken); } - } - private void FetchChanges() - { - displayProvider.DisplayStatus("Fetching changes from remote repository", async (ct) => + if (stack.SourceBranch.Equals(currentBranch, StringComparison.InvariantCultureIgnoreCase) || + stack.AllBranchNames.Contains(currentBranch, StringComparer.OrdinalIgnoreCase)) { - await Task.CompletedTask; - gitClient.Fetch(true); - }); + gitClient.ChangeBranch(currentBranch); + } + + logger.StackSyncedWithRemote(stack.Name); } } internal static partial class LoggerExtensionMethods { - [LoggerMessage(Level = LogLevel.Information, Message = "Syncing stack \"{Stack}\" with the remote repository")] - public static partial void SyncingStackWithRemote(this ILogger logger, string stack); + [LoggerMessage(Level = LogLevel.Information, Message = "Stack \"{Stack}\" synced with the remote repository")] + public static partial void StackSyncedWithRemote(this ILogger logger, string stack); } \ No newline at end of file diff --git a/src/Stack/Commands/Stack/NewStackCommand.cs b/src/Stack/Commands/Stack/NewStackCommand.cs index c226d65d..31a525db 100644 --- a/src/Stack/Commands/Stack/NewStackCommand.cs +++ b/src/Stack/Commands/Stack/NewStackCommand.cs @@ -75,6 +75,7 @@ public record NewStackCommandInputs(string? Name, string? SourceBranch, string? public class NewStackCommandHandler( IInputProvider inputProvider, ILogger logger, + IDisplayProvider displayProvider, IGitClient gitClient, IStackConfig stackConfig) : CommandHandlerBase @@ -119,8 +120,11 @@ public override async Task Handle(NewStackCommandInputs inputs, CancellationToke try { - logger.PushingBranch(branchName); + await displayProvider.DisplayStatus($"Pushing branch '{branchName}' to remote repository...", async (ct) => + { + await Task.CompletedTask; gitClient.PushNewBranch(branchName); + }, cancellationToken); } catch (Exception) { diff --git a/src/Stack/Commands/Stack/StackStatusCommand.cs b/src/Stack/Commands/Stack/StackStatusCommand.cs index b43be675..301b1d4b 100644 --- a/src/Stack/Commands/Stack/StackStatusCommand.cs +++ b/src/Stack/Commands/Stack/StackStatusCommand.cs @@ -232,14 +232,18 @@ public override async Task Handle(StackStatusCommand stacksToCheckStatusFor.Add(stack); } - var stackStatusResults = StackHelpers.GetStackStatus( - stacksToCheckStatusFor, - currentBranch, - logger, - displayProvider, - gitClient, - gitHubClient, - inputs.Full); + var stackStatusResults = await displayProvider.DisplayStatus("Checking stack status...", async (ct) => + { + await Task.CompletedTask; + return StackHelpers.GetStackStatus( + stacksToCheckStatusFor, + currentBranch, + logger, + displayProvider, + gitClient, + gitHubClient, + inputs.Full); + }, cancellationToken); return new StackStatusCommandResponse(stackStatusResults); } diff --git a/src/Stack/Commands/Stack/UpdateStackCommand.cs b/src/Stack/Commands/Stack/UpdateStackCommand.cs index bc0ba9f6..3a35c2a5 100644 --- a/src/Stack/Commands/Stack/UpdateStackCommand.cs +++ b/src/Stack/Commands/Stack/UpdateStackCommand.cs @@ -47,6 +47,7 @@ public record UpdateStackCommandResponse(); public class UpdateStackCommandHandler( IInputProvider inputProvider, ILogger logger, + IDisplayProvider displayProvider, IGitClient gitClient, IStackConfig stackConfig, IStackActions stackActions) @@ -67,6 +68,7 @@ public override async Task Handle(UpdateStackCommandInputs inputs, CancellationT if (stacksForRemote.Count == 0) { + logger.NoStacksForRepository(); return; } @@ -81,14 +83,15 @@ public override async Task Handle(UpdateStackCommandInputs inputs, CancellationT inputs.Merge == true ? UpdateStrategy.Merge : inputs.Rebase == true ? UpdateStrategy.Rebase : null, gitClient, inputProvider, logger, cancellationToken); - await stackActions.UpdateStack(stack, updateStrategy, cancellationToken); + await displayProvider.DisplayStatus("Updating stack...", async (ct) => + { + await stackActions.UpdateStack(stack, updateStrategy, cancellationToken); + }, cancellationToken); if (stack.SourceBranch.Equals(currentBranch, StringComparison.InvariantCultureIgnoreCase) || stack.AllBranchNames.Contains(currentBranch, StringComparer.OrdinalIgnoreCase)) { gitClient.ChangeBranch(currentBranch); } - - return; } } \ No newline at end of file diff --git a/src/Stack/Git/GitClient.cs b/src/Stack/Git/GitClient.cs index 309d5a20..c99b8d9a 100644 --- a/src/Stack/Git/GitClient.cs +++ b/src/Stack/Git/GitClient.cs @@ -334,14 +334,14 @@ private void ExecuteGitCommand( { var output = ExecuteGitCommandAndReturnOutput(command, captureStandardError, exceptionHandler); - if (output.Length > 0) - { - // We want to write the output of commands that perform - // changes to the Git repository as the output might be important. - // In verbose mode we would have already written the output - // of the command so don't write it again. - logger.DebugCommandOutput(Markup.Escape(output)); - } + // if (output.Length > 0) + // { + // // We want to write the output of commands that perform + // // changes to the Git repository as the output might be important. + // // In verbose mode we would have already written the output + // // of the command so don't write it again. + // logger.DebugCommandOutput(Markup.Escape(output)); + // } } } diff --git a/src/Stack/Git/ProcessHelpers.cs b/src/Stack/Git/ProcessHelpers.cs index a782b46c..1e409d3b 100644 --- a/src/Stack/Git/ProcessHelpers.cs +++ b/src/Stack/Git/ProcessHelpers.cs @@ -16,7 +16,7 @@ public static string ExecuteProcessAndReturnOutput( bool captureStandardError = false, Func? exceptionHandler = null) { - logger.TraceCommand(fileName, command); + logger.ExecutingCommand(fileName, command); var infoBuilder = new StringBuilder(); var errorBuilder = new StringBuilder(); @@ -53,7 +53,7 @@ public static string ExecuteProcessAndReturnOutput( if (result != 0) { - logger.TraceFailedCommand(fileName, command, result, errorBuilder.ToString()); + logger.CommandFailed(fileName, command, result, errorBuilder.ToString()); if (exceptionHandler != null) { @@ -71,7 +71,7 @@ public static string ExecuteProcessAndReturnOutput( if (infoBuilder.Length > 0) { - logger.TraceInfoOutput(Markup.Escape(infoBuilder.ToString())); + logger.CommandOutput(Markup.Escape(infoBuilder.ToString())); } var output = infoBuilder.ToString(); @@ -95,11 +95,11 @@ public class ProcessException(string message, string filePath, string command, i internal static partial class LoggerExtensionMethods { [LoggerMessage(Level = LogLevel.Trace, Message = "{FileName} {Command}")] - public static partial void TraceCommand(this ILogger logger, string fileName, string command); + public static partial void ExecutingCommand(this ILogger logger, string fileName, string command); [LoggerMessage(Level = LogLevel.Trace, Message = "Failed to execute command: {FileName} {Command}. Exit code: {ExitCode}. Error: {Error}.")] - public static partial void TraceFailedCommand(this ILogger logger, string fileName, string command, int exitCode, string error); + public static partial void CommandFailed(this ILogger logger, string fileName, string command, int exitCode, string error); [LoggerMessage(Level = LogLevel.Trace, Message = "{Info}")] - public static partial void TraceInfoOutput(this ILogger logger, string info); + public static partial void CommandOutput(this ILogger logger, string info); } \ No newline at end of file diff --git a/src/Stack/Infrastructure/AnsiConsoleLogger.cs b/src/Stack/Infrastructure/AnsiConsoleLogger.cs index 5c4f625a..5aad6b04 100644 --- a/src/Stack/Infrastructure/AnsiConsoleLogger.cs +++ b/src/Stack/Infrastructure/AnsiConsoleLogger.cs @@ -41,9 +41,13 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except break; } case LogLevel.Debug: + { + console.MarkupLine($"[grey58]{message}[/]"); + break; + } case LogLevel.Trace: { - console.MarkupLine($"[grey]{message}[/]"); + console.MarkupLine($"[grey30]{message}[/]"); break; } } diff --git a/src/Stack/Infrastructure/Commands/Command.cs b/src/Stack/Infrastructure/Commands/Command.cs index e6db507e..801cd5d0 100644 --- a/src/Stack/Infrastructure/Commands/Command.cs +++ b/src/Stack/Infrastructure/Commands/Command.cs @@ -28,6 +28,7 @@ public Command( InputProvider = inputProvider; Add(CommonOptions.WorkingDirectory); + Add(CommonOptions.Debug); Add(CommonOptions.Verbose); SetAction(async (parseResult, cancellationToken) => @@ -71,7 +72,7 @@ internal static partial class LoggerExtensionMethods [LoggerMessage(Level = LogLevel.Error, Message = "An error occurred running command \"{FilePath} {Command}\"")] public static partial void ProcessExceptionCommandDetails(this ILogger logger, string filePath, string command); - [LoggerMessage(Level = LogLevel.Debug, Message = "{Message}")] + [LoggerMessage(Level = LogLevel.Information, Message = "{Message}")] public static partial void ProcessExceptionErrorMessage(this ILogger logger, string message); [LoggerMessage(Level = LogLevel.Error, Message = "{Message}")] diff --git a/src/Stack/Infrastructure/CommonOptions.cs b/src/Stack/Infrastructure/CommonOptions.cs index 7486cf1e..b959fd09 100644 --- a/src/Stack/Infrastructure/CommonOptions.cs +++ b/src/Stack/Infrastructure/CommonOptions.cs @@ -8,7 +8,13 @@ public static class CommonOptions Required = false }; - public static Option Verbose { get; } = new Option("--verbose") + public static Option Debug { get; } = new Option("--debug", "-d") + { + Description = "Show debug output.", + Required = false + }; + + public static Option Verbose { get; } = new Option("--verbose", "-v") { Description = "Show verbose output.", Required = false diff --git a/src/Stack/Infrastructure/ConsoleDisplayProvider.cs b/src/Stack/Infrastructure/ConsoleDisplayProvider.cs index 1f014095..113d95e9 100644 --- a/src/Stack/Infrastructure/ConsoleDisplayProvider.cs +++ b/src/Stack/Infrastructure/ConsoleDisplayProvider.cs @@ -7,6 +7,7 @@ namespace Stack.Infrastructure; public interface IDisplayProvider { Task DisplayStatus(string message, Func action, CancellationToken cancellationToken = default); + Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default); Task DisplayTree(string header, IEnumerable> items, Func? itemFormatter = null, CancellationToken cancellationToken = default) where T : notnull; Task DisplayMessage(string message, CancellationToken cancellationToken = default); Task DisplayHeader(string header, CancellationToken cancellationToken = default); @@ -15,12 +16,63 @@ public interface IDisplayProvider public class ConsoleDisplayProvider(IAnsiConsole console) : IDisplayProvider { + readonly AsyncLocal _currentStatusContext = new(); + public async Task DisplayStatus(string message, Func action, CancellationToken cancellationToken = default) { + if (_currentStatusContext.Value is not null) + { + _currentStatusContext.Value.Status(message); + await action(cancellationToken); + return; + } + await console .Status() .Spinner(Spinner.Known.Dots3) - .StartAsync(message, async (_) => await action(cancellationToken)); + .StartAsync(message, async (context) => + { + _currentStatusContext.Value = context; + try + { + await action(cancellationToken); + } + finally + { + if (ReferenceEquals(_currentStatusContext.Value, context)) + { + _currentStatusContext.Value = null; + } + } + }); + } + + public async Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default) + { + if (_currentStatusContext.Value is not null) + { + _currentStatusContext.Value.Status(message); + return await action(cancellationToken); + } + + return await console + .Status() + .Spinner(Spinner.Known.Dots3) + .StartAsync(message, async (context) => + { + _currentStatusContext.Value = context; + try + { + return await action(cancellationToken); + } + finally + { + if (ReferenceEquals(_currentStatusContext.Value, context)) + { + _currentStatusContext.Value = null; + } + } + }); } public async Task DisplayTree(string header, IEnumerable> items, Func? itemFormatter = null, CancellationToken cancellationToken = default) diff --git a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs index 5d9042c7..05dbba14 100644 --- a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs +++ b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs @@ -22,9 +22,13 @@ public static IHostApplicationBuilder ConfigureServices(this IHostApplicationBui public static IHostApplicationBuilder ConfigureLogging(this IHostApplicationBuilder builder, string[] args) { - builder.Logging.SetMinimumLevel(LogLevel.Debug); + builder.Logging.SetMinimumLevel(LogLevel.Information); - if (args.Contains("--verbose") || args.Contains("-v")) + if (args.Contains(CommonOptions.Debug.Name) || args.Any(a => CommonOptions.Debug.Aliases.Contains(a))) + { + builder.Logging.SetMinimumLevel(LogLevel.Debug); + } + else if (args.Contains(CommonOptions.Verbose.Name) || args.Any(a => CommonOptions.Verbose.Aliases.Contains(a))) { builder.Logging.SetMinimumLevel(LogLevel.Trace); } From c19334f1ea617431bb4209212142fb8287573fbd Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 12 Sep 2025 08:39:56 +1000 Subject: [PATCH 2/5] Experimenting with success messages --- src/Stack/Commands/Helpers/StackActions.cs | 4 +--- src/Stack/Commands/Remote/SyncStackCommand.cs | 6 +++--- src/Stack/Infrastructure/ConsoleDisplayProvider.cs | 10 ++++++++++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/Stack/Commands/Helpers/StackActions.cs b/src/Stack/Commands/Helpers/StackActions.cs index 4a834983..01d4fbe3 100644 --- a/src/Stack/Commands/Helpers/StackActions.cs +++ b/src/Stack/Commands/Helpers/StackActions.cs @@ -302,7 +302,7 @@ private async Task UpdateBranchLineUsingRebase(StackStatus status, List + await displayProvider.DisplayStatusWithSuccess($"Rebasing {branch} onto {sourceBranchName}", async ct => { gitClient.ChangeBranch(branch); @@ -333,8 +333,6 @@ await displayProvider.DisplayStatus($"Rebasing {branch} onto {sourceBranchName}" break; } } - - await displayProvider.DisplayMessage($"{Emoji.Known.CheckMark} Rebasing {branch} onto {sourceBranchName}...", ct); }, cancellationToken); } diff --git a/src/Stack/Commands/Remote/SyncStackCommand.cs b/src/Stack/Commands/Remote/SyncStackCommand.cs index 51f77f00..5c2d2df3 100644 --- a/src/Stack/Commands/Remote/SyncStackCommand.cs +++ b/src/Stack/Commands/Remote/SyncStackCommand.cs @@ -95,7 +95,7 @@ public override async Task Handle(SyncStackCommandInputs inputs, CancellationTok if (stack is null) throw new InvalidOperationException($"Stack '{inputs.Stack}' not found."); - await displayProvider.DisplayStatus("Fetching changes from remote repository...", async (ct) => + await displayProvider.DisplayStatusWithSuccess("Fetching changes from remote repository...", async (ct) => { await Task.CompletedTask; gitClient.Fetch(true); @@ -125,7 +125,7 @@ await displayProvider.DisplayStatus("Checking stack status...", async (ct) => } } - await displayProvider.DisplayStatus("Pulling changes from remote repository...", async (ct) => + await displayProvider.DisplayStatusWithSuccess("Pulling changes from remote repository...", async (ct) => { await Task.CompletedTask; stackActions.PullChanges(stack); @@ -144,7 +144,7 @@ await displayProvider.DisplayStatus("Updating stack...", async (ct) => if (!inputs.NoPush) { - await displayProvider.DisplayStatus("Pushing changes to remote repository...", async (ct) => + await displayProvider.DisplayStatusWithSuccess("Pushing changes to remote repository...", async (ct) => { await Task.CompletedTask; stackActions.PushChanges(stack, inputs.MaxBatchSize, forceWithLease); diff --git a/src/Stack/Infrastructure/ConsoleDisplayProvider.cs b/src/Stack/Infrastructure/ConsoleDisplayProvider.cs index 113d95e9..10a5026e 100644 --- a/src/Stack/Infrastructure/ConsoleDisplayProvider.cs +++ b/src/Stack/Infrastructure/ConsoleDisplayProvider.cs @@ -10,6 +10,16 @@ public interface IDisplayProvider Task DisplayStatus(string message, Func> action, CancellationToken cancellationToken = default); Task DisplayTree(string header, IEnumerable> items, Func? itemFormatter = null, CancellationToken cancellationToken = default) where T : notnull; Task DisplayMessage(string message, CancellationToken cancellationToken = default); + Task DisplaySuccess(string message, CancellationToken cancellationToken = default) + => DisplayMessage($"{Emoji.Known.CheckMark} {message}", cancellationToken); + Task DisplayStatusWithSuccess(string message, Func action, CancellationToken cancellationToken = default) + { + return DisplayStatus(message, async ct => + { + await action(ct); + await DisplaySuccess(message, ct); + }, cancellationToken); + } Task DisplayHeader(string header, CancellationToken cancellationToken = default); Task DisplayNewLine(CancellationToken cancellationToken = default); } From a55eb759af2e6109db392e4e479c564c0034d467 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Fri, 12 Sep 2025 17:26:39 +1000 Subject: [PATCH 3/5] Cleanup --- src/Stack/Git/GitClient.cs | 11 +---------- src/Stack/Infrastructure/CommonOptions.cs | 4 ++-- .../HostApplicationBuilderExtensions.cs | 4 ++-- 3 files changed, 5 insertions(+), 14 deletions(-) diff --git a/src/Stack/Git/GitClient.cs b/src/Stack/Git/GitClient.cs index c99b8d9a..bd5bf701 100644 --- a/src/Stack/Git/GitClient.cs +++ b/src/Stack/Git/GitClient.cs @@ -332,16 +332,7 @@ private void ExecuteGitCommand( bool captureStandardError = false, Func? exceptionHandler = null) { - var output = ExecuteGitCommandAndReturnOutput(command, captureStandardError, exceptionHandler); - - // if (output.Length > 0) - // { - // // We want to write the output of commands that perform - // // changes to the Git repository as the output might be important. - // // In verbose mode we would have already written the output - // // of the command so don't write it again. - // logger.DebugCommandOutput(Markup.Escape(output)); - // } + ExecuteGitCommandAndReturnOutput(command, captureStandardError, exceptionHandler); } } diff --git a/src/Stack/Infrastructure/CommonOptions.cs b/src/Stack/Infrastructure/CommonOptions.cs index b959fd09..59339976 100644 --- a/src/Stack/Infrastructure/CommonOptions.cs +++ b/src/Stack/Infrastructure/CommonOptions.cs @@ -8,13 +8,13 @@ public static class CommonOptions Required = false }; - public static Option Debug { get; } = new Option("--debug", "-d") + public static Option Debug { get; } = new Option("--debug") { Description = "Show debug output.", Required = false }; - public static Option Verbose { get; } = new Option("--verbose", "-v") + public static Option Verbose { get; } = new Option("--verbose") { Description = "Show verbose output.", Required = false diff --git a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs index 05dbba14..b531d25e 100644 --- a/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs +++ b/src/Stack/Infrastructure/HostApplicationBuilderExtensions.cs @@ -24,11 +24,11 @@ public static IHostApplicationBuilder ConfigureLogging(this IHostApplicationBuil { builder.Logging.SetMinimumLevel(LogLevel.Information); - if (args.Contains(CommonOptions.Debug.Name) || args.Any(a => CommonOptions.Debug.Aliases.Contains(a))) + if (args.Contains(CommonOptions.Debug.Name) || args.Any(CommonOptions.Debug.Aliases.Contains)) { builder.Logging.SetMinimumLevel(LogLevel.Debug); } - else if (args.Contains(CommonOptions.Verbose.Name) || args.Any(a => CommonOptions.Verbose.Aliases.Contains(a))) + else if (args.Contains(CommonOptions.Verbose.Name) || args.Any(CommonOptions.Verbose.Aliases.Contains)) { builder.Logging.SetMinimumLevel(LogLevel.Trace); } From d201ffa399f79121e97e270a003373d17343182e Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Wed, 17 Sep 2025 08:29:10 +1000 Subject: [PATCH 4/5] Improve handling of no stacks in repo --- .../Helpers/InputProviderExtensionMethods.cs | 5 +++++ src/Stack/Commands/Stack/StackStatusCommand.cs | 12 ++++++++++++ 2 files changed, 17 insertions(+) diff --git a/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs b/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs index 2ea37ab6..2317b728 100644 --- a/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs +++ b/src/Stack/Commands/Helpers/InputProviderExtensionMethods.cs @@ -64,6 +64,11 @@ public static async Task MultiSelect( string currentBranch, CancellationToken cancellationToken) { + if (stacks.Count == 0) + { + return null; + } + if (name is not null) { return stacks.FirstOrDefault(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)); diff --git a/src/Stack/Commands/Stack/StackStatusCommand.cs b/src/Stack/Commands/Stack/StackStatusCommand.cs index 301b1d4b..b8f4cef0 100644 --- a/src/Stack/Commands/Stack/StackStatusCommand.cs +++ b/src/Stack/Commands/Stack/StackStatusCommand.cs @@ -212,6 +212,13 @@ public override async Task Handle(StackStatusCommand var remoteUri = gitClient.GetRemoteUri(); var stacksForRemote = stackData.Stacks.Where(s => s.RemoteUri.Equals(remoteUri, StringComparison.OrdinalIgnoreCase)).ToList(); + + if (stacksForRemote.Count == 0) + { + logger.NoStacksForRepository(); + return new StackStatusCommandResponse([]); + } + var currentBranch = gitClient.GetCurrentBranch(); var stacksToCheckStatusFor = new List(); @@ -226,6 +233,11 @@ public override async Task Handle(StackStatusCommand if (stack is null) { + if (inputs.Stack is null) + { + throw new InvalidOperationException("Stack not found."); + } + throw new InvalidOperationException($"Stack '{inputs.Stack}' not found."); } From da4f49e2e49bed813ce9d3d95a4fba3a1cd405a7 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Wed, 17 Sep 2025 08:32:39 +1000 Subject: [PATCH 5/5] Add `--debug` to readme --- README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/README.md b/README.md index c46f4740..6a1e02b3 100644 --- a/README.md +++ b/README.md @@ -171,6 +171,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -n, --name The name of the stack. Must be unique within the repository. -s, --source-branch The source branch to use for the new stack. Defaults to the default branch for the repository. @@ -188,6 +189,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. --json Output results as JSON. -?, -h, --help Show help and usage information @@ -203,6 +205,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. --json Output results as JSON. -s, --stack The name of the stack. @@ -221,6 +224,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. -y, --yes Confirm the command without prompting. @@ -237,6 +241,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. -n, --name The new name for the stack. @@ -255,6 +260,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. --rebase Use rebase when updating the stack. Overrides any setting in Git configuration. @@ -272,6 +278,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -b, --branch The name of the branch. -?, -h, --help Show help and usage information @@ -287,6 +294,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. -y, --yes Confirm the command without prompting. @@ -303,6 +311,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. -b, --branch The name of the branch. @@ -320,6 +329,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. -b, --branch The name of the branch. @@ -337,6 +347,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. -b, --branch The name of the branch. @@ -358,6 +369,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. -?, -h, --help Show help and usage information @@ -373,6 +385,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. --max-batch-size The maximum number of branches to process at once. [default: 5] @@ -390,6 +403,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. --max-batch-size The maximum number of branches to process at once. [default: 5] @@ -412,6 +426,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. -?, -h, --help Show help and usage information @@ -427,6 +442,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -s, --stack The name of the stack. -?, -h, --help Show help and usage information @@ -444,6 +460,7 @@ Usage: Options: --working-dir The path to the directory containing the git repository. Defaults to the current directory. + --debug Show debug output. --verbose Show verbose output. -?, -h, --help Show help and usage information ```