Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds --push option when creating new branches #165

Merged
merged 53 commits into from
Dec 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
215b859
Show local status by default in status command
geofflamrock Dec 20, 2024
8084392
Make showing status much better and quicker
geofflamrock Dec 20, 2024
b30c303
Remove `--minimal` option for now
geofflamrock Dec 20, 2024
1b318b6
Separate git branch status parsing
geofflamrock Dec 20, 2024
24e37be
Revert change to detecting remote branches for other commands
geofflamrock Dec 20, 2024
4410d0b
Cleanup
geofflamrock Dec 20, 2024
369aba4
Adds pull command
geofflamrock Dec 20, 2024
bba932f
Tiny improvement to output when getting status of single stack
geofflamrock Dec 20, 2024
6b573fb
wip pull tests
geofflamrock Dec 20, 2024
1925602
Add more tests
geofflamrock Dec 24, 2024
e0f25ec
Merge branch 'main' into stack-status-local-by-default
geofflamrock Dec 27, 2024
785d5cd
Merge branch 'stack-status-local-by-default' into add-pull-command
geofflamrock Dec 27, 2024
4df13a8
Improve getting tip of remote branch
geofflamrock Dec 27, 2024
e95e5cb
Adds push command
geofflamrock Dec 27, 2024
734fae3
Add formatting
geofflamrock Dec 27, 2024
d167733
Testing out capturing std err
geofflamrock Dec 27, 2024
3254a3a
Make std err optional
geofflamrock Dec 27, 2024
4b00fa2
Merge branch 'main' into stack-status-local-by-default
geofflamrock Dec 27, 2024
294c403
Merge branch 'stack-status-local-by-default' into add-pull-command
geofflamrock Dec 27, 2024
9bcc789
Merge branch 'add-pull-command' into add-push-command
geofflamrock Dec 27, 2024
e5f81ec
Make formatting less noisy
geofflamrock Dec 27, 2024
0d2ad4e
Merge branch 'stack-status-local-by-default' into add-pull-command
geofflamrock Dec 27, 2024
99fea49
Merge branch 'add-pull-command' into add-push-command
geofflamrock Dec 27, 2024
9a1c158
Add tests
geofflamrock Dec 27, 2024
2f543b0
Push new branch if required
geofflamrock Dec 27, 2024
1d953e2
Adds --no-push option to new stack creation
geofflamrock Dec 30, 2024
f8295da
Add --push option to new branch creation
geofflamrock Dec 30, 2024
b1eac2e
Update help
geofflamrock Dec 30, 2024
a0a8d7d
Update help and readme
geofflamrock Dec 30, 2024
1ce8196
Update help
geofflamrock Dec 30, 2024
9c64936
Merge branch 'stack-status-local-by-default' into add-pull-command
geofflamrock Dec 30, 2024
2103554
Merge branch 'add-pull-command' into add-push-command
geofflamrock Dec 30, 2024
a58a7f0
Merge branch 'add-push-command' into add-push-option-to-new-branch
geofflamrock Dec 30, 2024
4d5fcc5
Update readme
geofflamrock Dec 30, 2024
efdeb61
Update readme
geofflamrock Dec 30, 2024
d6246e7
Merge branch 'add-push-command' into add-push-option-to-new-branch
geofflamrock Dec 30, 2024
009017d
Cleanup
geofflamrock Dec 30, 2024
f07c1be
Merge branch 'add-pull-command' into add-push-command
geofflamrock Dec 30, 2024
59c5613
Merge branch 'add-push-command' into add-push-option-to-new-branch
geofflamrock Dec 30, 2024
2079a48
Cleanup
geofflamrock Dec 30, 2024
b503c1a
Merge branch 'add-push-command' into add-push-option-to-new-branch
geofflamrock Dec 30, 2024
a71d84e
Remove force options
geofflamrock Dec 30, 2024
3b9ec24
Merge branch 'add-push-command' into add-push-option-to-new-branch
geofflamrock Dec 30, 2024
6b8b7dc
Cleanup
geofflamrock Dec 30, 2024
8c39898
Merge branch 'stack-status-local-by-default' into add-pull-command
geofflamrock Dec 30, 2024
6a12e0e
Merge branch 'add-pull-command' into add-push-command
geofflamrock Dec 30, 2024
12b922b
Merge branch 'add-push-command' into add-push-option-to-new-branch
geofflamrock Dec 30, 2024
c22f3a0
Merge branch 'main' into add-pull-command
geofflamrock Dec 30, 2024
41c49ce
Merge branch 'add-pull-command' into add-push-command
geofflamrock Dec 30, 2024
b6170e5
Merge branch 'add-push-command' into add-push-option-to-new-branch
geofflamrock Dec 30, 2024
8a0f05c
Merge branch 'main' into add-push-command
geofflamrock Dec 30, 2024
7337fb5
Merge branch 'add-push-command' into add-push-option-to-new-branch
geofflamrock Dec 30, 2024
cb07ff6
Merge branch 'main' into add-push-option-to-new-branch
geofflamrock Dec 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ OPTIONS:
-n, --name The name of the stack. Must be unique
-s, --source-branch The source branch to use for the new branch. Defaults to the default branch for the repository
-b, --branch The name of the branch to create within the stack
--push Push the new branch to the remote repository
```

### `stack list`
Expand Down Expand Up @@ -243,6 +244,7 @@ OPTIONS:
--dry-run Show what would happen without making any changes
-s, --stack The name of the stack to create the branch in
-n, --name The name of the branch to create
--push Push the new branch to the remote repository
```

### `stack branch add`
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
using FluentAssertions;
using NSubstitute;
using Spectre.Console;
using Stack.Commands;
using Stack.Commands.Helpers;
using Stack.Config;
using Stack.Git;
using Stack.Infrastructure;
using Stack.Tests.Helpers;

namespace Stack.Tests.Commands.Stack;
namespace Stack.Tests.Commands.Branch;

public class AddBranchCommandHandlerTests
{
Expand Down
62 changes: 54 additions & 8 deletions src/Stack.Tests/Commands/Branch/NewBranchCommandHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Stack.Tests.Commands.Branch;
public class NewBranchCommandHandlerTests
{
[Fact]
public async Task WhenNoInputsProvided_AsksForStackAndBranchAndConfirms_CreatesAndAddsBranchToStackAndSwitchesToBranch()
public async Task WhenNoInputsProvided_AsksForStackAndBranchAndConfirms_CreatesAndAddsBranchToStack_DoesNotPushToRemote_AndSwitchesToBranch()
{
// Arrange
var sourceBranch = Some.BranchName();
Expand Down Expand Up @@ -53,6 +53,7 @@ public async Task WhenNoInputsProvided_AsksForStackAndBranchAndConfirms_CreatesA
new("Stack2", repo.RemoteUri, sourceBranch, [])
});
gitOperations.GetCurrentBranch().Should().Be(newBranch);
repo.GetBranches().Should().Contain(b => b.FriendlyName == newBranch && !b.IsTracking);
}

[Fact]
Expand Down Expand Up @@ -130,7 +131,7 @@ public async Task WhenStackNameProvided_DoesNotAskForStackName_CreatesAndAddsBra
inputProvider.Text(Questions.BranchName, Arg.Any<string>()).Returns(newBranch);

// Act
await handler.Handle(new NewBranchCommandInputs("Stack1", null, false));
await handler.Handle(new NewBranchCommandInputs("Stack1", null, false, false));

// Assert
inputProvider.DidNotReceive().Select(Questions.SelectStack, Arg.Any<string[]>());
Expand Down Expand Up @@ -171,7 +172,7 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName_CreatesAndAddsBr
inputProvider.Text(Questions.BranchName, Arg.Any<string>()).Returns(newBranch);

// Act
await handler.Handle(new NewBranchCommandInputs(null, null, false));
await handler.Handle(new NewBranchCommandInputs(null, null, false, false));

// Assert
inputProvider.DidNotReceive().Select(Questions.SelectStack, Arg.Any<string[]>());
Expand Down Expand Up @@ -208,7 +209,7 @@ public async Task WhenStackNameProvided_ButStackDoesNotExist_Throws()

// Act and assert
var invalidStackName = Some.Name();
await handler.Invoking(async h => await h.Handle(new NewBranchCommandInputs(invalidStackName, null, false)))
await handler.Invoking(async h => await h.Handle(new NewBranchCommandInputs(invalidStackName, null, false, false)))
.Should()
.ThrowAsync<InvalidOperationException>()
.WithMessage($"Stack '{invalidStackName}' not found.");
Expand Down Expand Up @@ -245,7 +246,7 @@ public async Task WhenBranchNameProvided_DoesNotAskForBranchName_CreatesAndAddsB
inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1");

// Act
await handler.Handle(new NewBranchCommandInputs(null, newBranch, false));
await handler.Handle(new NewBranchCommandInputs(null, newBranch, false, false));

// Assert
stacks.Should().BeEquivalentTo(new List<Config.Stack>
Expand Down Expand Up @@ -284,7 +285,7 @@ public async Task WhenBranchNameProvided_ButBranchAlreadyExistLocally_Throws()

// Act and assert
var invalidBranchName = Some.Name();
await handler.Invoking(async h => await h.Handle(new NewBranchCommandInputs(null, anotherBranch, false)))
await handler.Invoking(async h => await h.Handle(new NewBranchCommandInputs(null, anotherBranch, false, false)))
.Should()
.ThrowAsync<InvalidOperationException>()
.WithMessage($"Branch '{anotherBranch}' already exists locally.");
Expand Down Expand Up @@ -318,7 +319,7 @@ public async Task WhenBranchNameProvided_ButBranchAlreadyExistsInStack_Throws()
inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1");

// Act and assert
await handler.Invoking(async h => await h.Handle(new NewBranchCommandInputs(null, newBranch, false)))
await handler.Invoking(async h => await h.Handle(new NewBranchCommandInputs(null, newBranch, false, false)))
.Should()
.ThrowAsync<InvalidOperationException>()
.WithMessage($"Branch '{newBranch}' already exists in stack 'Stack1'.");
Expand Down Expand Up @@ -353,7 +354,7 @@ public async Task WhenAllInputsProvided_DoesNotAskForAnything_CreatesAndAddsBran
.Do(ci => stacks = ci.ArgAt<List<Config.Stack>>(0));

// Act
await handler.Handle(new NewBranchCommandInputs("Stack1", newBranch, true));
await handler.Handle(new NewBranchCommandInputs("Stack1", newBranch, true, false));

// Assert
stacks.Should().BeEquivalentTo(new List<Config.Stack>
Expand Down Expand Up @@ -407,4 +408,49 @@ public async Task WhenStackHasANameWithMultipleWords_SuggestsAGoodDefaultNewBran
});
gitOperations.GetCurrentBranch().Should().Be(newBranch);
}

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

var stackConfig = Substitute.For<IStackConfig>();
var inputProvider = Substitute.For<IInputProvider>();
var outputProvider = Substitute.For<IOutputProvider>();
var gitOperations = new GitOperations(outputProvider, repo.GitOperationSettings);
var handler = new NewBranchCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig);

var stacks = new List<Config.Stack>(
[
new("Stack1", repo.RemoteUri, sourceBranch, [anotherBranch]),
new("Stack2", repo.RemoteUri, sourceBranch, [])
]);
stackConfig.Load().Returns(stacks);
stackConfig
.WhenForAnyArgs(s => s.Save(Arg.Any<List<Config.Stack>>()))
.Do(ci => stacks = ci.ArgAt<List<Config.Stack>>(0));

inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1");
inputProvider.Text(Questions.BranchName, Arg.Any<string>()).Returns(newBranch);
inputProvider.Confirm(Questions.ConfirmSwitchToBranch).Returns(false);

// Act
await handler.Handle(new NewBranchCommandInputs(null, null, false, true));

// Assert
stacks.Should().BeEquivalentTo(new List<Config.Stack>
{
new("Stack1", repo.RemoteUri, sourceBranch, [anotherBranch, newBranch]),
new("Stack2", repo.RemoteUri, sourceBranch, [])
});
gitOperations.GetCurrentBranch().Should().NotBe(newBranch);
repo.GetBranches().Should().Contain(b => b.FriendlyName == newBranch && b.IsTracking);
}
}
54 changes: 49 additions & 5 deletions src/Stack.Tests/Commands/Stack/NewStackCommandHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ namespace Stack.Tests.Commands.Stack;
public class NewStackCommandHandlerTests
{
[Fact]
public async Task WithANewBranch_AndSwitchingToTheBranch_TheStackIsCreatedAndTheCurrentBranchIsChanged()
public async Task WithANewBranch_AndSwitchingToTheBranch_TheStackIsCreated_DoesNotPushToRemote_AndTheCurrentBranchIsChanged()
{
// Arrange
var sourceBranch = Some.BranchName();
Expand Down Expand Up @@ -52,6 +52,7 @@ public async Task WithANewBranch_AndSwitchingToTheBranch_TheStackIsCreatedAndThe
});

gitOperations.GetCurrentBranch().Should().Be(newBranch);
repo.GetBranches().Should().Contain(b => b.FriendlyName == newBranch && !b.IsTracking);
}

[Fact]
Expand Down Expand Up @@ -259,7 +260,7 @@ public async Task WhenStackNameIsProvidedInInputs_TheProviderIsNotAskedForAName_
inputProvider.Select(Questions.SelectSourceBranch, Arg.Any<string[]>()).Returns(sourceBranch);
inputProvider.Confirm(Questions.ConfirmAddOrCreateBranch).Returns(false);

var inputs = new NewStackCommandInputs("Stack1", null, null);
var inputs = new NewStackCommandInputs("Stack1", null, null, false);

// Act
var response = await handler.Handle(inputs);
Expand Down Expand Up @@ -299,7 +300,7 @@ public async Task WhenSourceBranchIsProvidedInInputs_TheProviderIsNotAskedForThe
inputProvider.Text(Questions.StackName).Returns("Stack1");
inputProvider.Confirm(Questions.ConfirmAddOrCreateBranch).Returns(false);

var inputs = new NewStackCommandInputs(null, sourceBranch, null);
var inputs = new NewStackCommandInputs(null, sourceBranch, null, false);

// Act
var response = await handler.Handle(inputs);
Expand Down Expand Up @@ -341,7 +342,7 @@ public async Task WhenBranchNameIsProvidedInInputs_TheProviderIsNotAskedForTheBr
inputProvider.Select(Questions.SelectSourceBranch, Arg.Any<string[]>()).Returns(sourceBranch);
// Note there shouldn't be any more inputs required at all

var inputs = new NewStackCommandInputs(null, null, newBranch);
var inputs = new NewStackCommandInputs(null, null, newBranch, false);

// Act
var response = await handler.Handle(inputs);
Expand Down Expand Up @@ -382,7 +383,7 @@ public async Task WhenAllInputsAreProvided_TheProviderIsNotAskedForAnything_AndT
.WhenForAnyArgs(s => s.Save(Arg.Any<List<Config.Stack>>()))
.Do(ci => stacks = ci.ArgAt<List<Config.Stack>>(0));

var inputs = new NewStackCommandInputs("Stack1", sourceBranch, newBranch);
var inputs = new NewStackCommandInputs("Stack1", sourceBranch, newBranch, true);

// Act
var response = await handler.Handle(inputs);
Expand Down Expand Up @@ -439,4 +440,47 @@ public async Task WhenAStackHasANameWithMultipleWords_SuggestsAGoodDefaultNewBra

gitOperations.GetCurrentBranch().Should().Be(newBranch);
}

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

var stackConfig = Substitute.For<IStackConfig>();
var inputProvider = Substitute.For<IInputProvider>();
var outputProvider = Substitute.For<IOutputProvider>();
var gitOperations = new GitOperations(outputProvider, repo.GitOperationSettings);
var gitHubOperations = Substitute.For<IGitHubOperations>();
var handler = new NewStackCommandHandler(inputProvider, outputProvider, gitOperations, stackConfig);

var stacks = new List<Config.Stack>();
stackConfig.Load().Returns(stacks);
stackConfig
.WhenForAnyArgs(s => s.Save(Arg.Any<List<Config.Stack>>()))
.Do(ci => stacks = ci.ArgAt<List<Config.Stack>>(0));

inputProvider.Text(Questions.StackName).Returns("Stack1");
inputProvider.Select(Questions.SelectSourceBranch, Arg.Any<string[]>()).Returns(sourceBranch);
inputProvider.Confirm(Questions.ConfirmAddOrCreateBranch).Returns(true);
inputProvider.Select(Questions.AddOrCreateBranch, Arg.Any<BranchAction[]>(), Arg.Any<Func<BranchAction, string>>()).Returns(BranchAction.Create);
inputProvider.Text(Questions.BranchName, Arg.Any<string>()).Returns(newBranch);
inputProvider.Confirm(Questions.ConfirmSwitchToBranch).Returns(false);

// Act
var response = await handler.Handle(new NewStackCommandInputs(null, null, null, true));

// Assert
response.Should().BeEquivalentTo(new NewStackCommandResponse("Stack1", sourceBranch, BranchAction.Create, newBranch));
stacks.Should().BeEquivalentTo(new List<Config.Stack>
{
new("Stack1", repo.RemoteUri, sourceBranch, [newBranch])
});

repo.GetBranches().Should().Contain(b => b.FriendlyName == newBranch && b.IsTracking);
}
}
5 changes: 5 additions & 0 deletions src/Stack.Tests/Helpers/TestGitRepositoryBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,11 @@ public LibGit2Sharp.Commit GetTipOfRemoteBranch(string branchName)
return [.. LocalRepository.Branches[remoteBranchName].Commits];
}

public List<LibGit2Sharp.Branch> GetBranches()
{
return [.. LocalRepository.Branches];
}

public void Dispose()
{
GC.SuppressFinalize(this);
Expand Down
23 changes: 18 additions & 5 deletions src/Stack/Commands/Branch/NewBranchCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ public class NewBranchCommandSettings : DryRunCommandSettingsBase
[Description("Force creating the branch without prompting.")]
[CommandOption("-f|--force")]
public bool Force { get; init; }

[Description("Push the new branch to the remote repository.")]
[CommandOption("--push")]
public bool Push { get; init; }
}

public class NewBranchCommand : AsyncCommand<NewBranchCommandSettings>
Expand All @@ -38,15 +42,15 @@ public override async Task<int> ExecuteAsync(CommandContext context, NewBranchCo
new GitOperations(outputProvider, settings.GetGitOperationSettings()),
new StackConfig());

await handler.Handle(new NewBranchCommandInputs(settings.Stack, settings.Name, settings.Force));
await handler.Handle(new NewBranchCommandInputs(settings.Stack, settings.Name, settings.Force, settings.Push));

return 0;
}
}

public record NewBranchCommandInputs(string? StackName, string? BranchName, bool Force)
public record NewBranchCommandInputs(string? StackName, string? BranchName, bool Force, bool Push)
{
public static NewBranchCommandInputs Empty => new(null, null, false);
public static NewBranchCommandInputs Empty => new(null, null, false, false);
}

public record NewBranchCommandResponse();
Expand Down Expand Up @@ -99,13 +103,22 @@ public async Task<NewBranchCommandResponse> Handle(NewBranchCommandInputs inputs
outputProvider.Information($"Creating branch {branchName.Branch()} from {sourceBranch.Branch()} in stack {stack.Name.Stack()}");

gitOperations.CreateNewBranch(branchName, sourceBranch);
gitOperations.PushNewBranch(branchName);

stack.Branches.Add(branchName);

stackConfig.Save(stacks);

outputProvider.Information($"Branch created");
if (inputs.Push)
{
gitOperations.PushNewBranch(branchName);
}

outputProvider.Information($"Branch {branchName.Branch()} created.");

if (!inputs.Push)
{
outputProvider.Information($"Use {$"stack push --name \"{stack.Name}\"".Example()} to push the branch to the remote repository.");
}

if (inputs.Force || inputProvider.Confirm(Questions.ConfirmSwitchToBranch))
{
Expand Down
Loading
Loading