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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,7 @@ OPTIONS:
--rebase Use rebase when updating the stack. Overrides any setting in Git configuration
--merge Use merge when updating the stack. Overrides any setting in Git configuration
--yes Confirm the sync without prompting
--no-push Don't push changes to the remote repository
```

### GitHub commands <!-- omit from toc -->
Expand Down
76 changes: 63 additions & 13 deletions src/Stack.Tests/Commands/Remote/SyncStackCommandHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task WhenChangesExistOnTheSourceBranchOnTheRemote_PullsChanges_Upda
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

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

// Assert
repo.GetCommitsReachableFromRemoteBranch(branch1).Should().Contain(tipOfRemoteSourceBranch);
Expand Down Expand Up @@ -100,7 +100,7 @@ public async Task WhenNameIsProvided_DoesNotAskForName_SyncsCorrectStack()
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

// Act
await handler.Handle(new SyncStackCommandInputs("Stack1", 5, false, false, false));
await handler.Handle(new SyncStackCommandInputs("Stack1", 5, false, false, false, false));

// Assert
repo.GetCommitsReachableFromRemoteBranch(branch1).Should().Contain(tipOfRemoteSourceBranch);
Expand Down Expand Up @@ -147,7 +147,7 @@ public async Task WhenNameIsProvided_ButStackDoesNotExist_Throws()

// Act and assert
var invalidStackName = Some.Name();
await handler.Invoking(async h => await h.Handle(new SyncStackCommandInputs(invalidStackName, 5, false, false, false)))
await handler.Invoking(async h => await h.Handle(new SyncStackCommandInputs(invalidStackName, 5, false, false, false, false)))
.Should().ThrowAsync<InvalidOperationException>()
.WithMessage($"Stack '{invalidStackName}' not found.");
}
Expand Down Expand Up @@ -194,7 +194,7 @@ public async Task WhenOnASpecificBranchInTheStack_TheSameBranchIsSetAsCurrentAft
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

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

// Assert
repo.GetCommitsReachableFromRemoteBranch(branch1).Should().Contain(tipOfRemoteSourceBranch);
Expand Down Expand Up @@ -238,7 +238,7 @@ public async Task WhenOnlyASingleStackExists_DoesNotAskForStackName_SyncsStack()
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

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

// Assert
repo.GetCommitsReachableFromRemoteBranch(branch1).Should().Contain(tipOfRemoteSourceBranch);
Expand Down Expand Up @@ -290,7 +290,7 @@ public async Task WhenUsingRebase_ChangesExistOnTheSourceBranchOnTheRemote_Pulls
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

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

// Assert
var tipOfBranch1 = repo.GetTipOfBranch(branch1);
Expand Down Expand Up @@ -342,7 +342,7 @@ public async Task WhenUsingMerge_ChangesExistOnTheSourceBranchOnTheRemote_PullsC
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

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

// Assert
var tipOfBranch1 = repo.GetTipOfBranch(branch1);
Expand Down Expand Up @@ -395,7 +395,7 @@ public async Task WhenNotSpecifyingRebaseOrMerge_AndUpdateSettingIsRebase_DoesSy
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

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

// Assert
var tipOfBranch1 = repo.GetTipOfBranch(branch1);
Expand Down Expand Up @@ -448,7 +448,7 @@ public async Task WhenNotSpecifyingRebaseOrMerge_AndUpdateSettingIsMerge_DoesSyn
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

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

// Assert
var tipOfBranch1 = repo.GetTipOfBranch(branch1);
Expand Down Expand Up @@ -501,7 +501,7 @@ public async Task WhenGitConfigValueIsSetToMerge_ButRebaseIsSpecified_DoesSyncUs
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

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

// Assert
var tipOfBranch1 = repo.GetTipOfBranch(branch1);
Expand Down Expand Up @@ -554,7 +554,7 @@ public async Task WhenGitConfigValueIsSetToRebase_ButMergeIsSpecified_DoesSyncUs
inputProvider.Confirm(Questions.ConfirmSyncStack).Returns(true);

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

// Assert
var tipOfBranch1 = repo.GetTipOfBranch(branch1);
Expand All @@ -577,7 +577,7 @@ public async Task WhenBothRebaseAndMergeAreSpecified_AnErrorIsThrown()

// Act and assert
await handler
.Invoking(h => h.Handle(new SyncStackCommandInputs(null, 5, true, true, false)))
.Invoking(h => h.Handle(new SyncStackCommandInputs(null, 5, true, true, false, false)))
.Should().ThrowAsync<InvalidOperationException>()
.WithMessage("Cannot specify both rebase and merge.");
}
Expand Down Expand Up @@ -622,11 +622,61 @@ public async Task WhenConfirmOptionIsProvided_DoesNotAskForConfirmation()
inputProvider.Select(Questions.SelectStack, Arg.Any<string[]>()).Returns("Stack1");

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

// Assert
repo.GetCommitsReachableFromRemoteBranch(branch1).Should().Contain(tipOfRemoteSourceBranch);
repo.GetCommitsReachableFromRemoteBranch(branch2).Should().Contain(tipOfRemoteSourceBranch);
inputProvider.DidNotReceive().Confirm(Questions.ConfirmSyncStack);
}

[Fact]
public async Task WhenNoPushOptionIsProvided_DoesNotPushChangesToRemote()
{
// Arrange
var sourceBranch = Some.BranchName();
var branch1 = Some.BranchName();
var branch2 = Some.BranchName();
using var repo = new TestGitRepositoryBuilder()
.WithBranch(builder => builder.WithName(sourceBranch).PushToRemote())
.WithBranch(builder => builder.WithName(branch1).FromSourceBranch(sourceBranch).WithNumberOfEmptyCommits(10).PushToRemote())
.WithBranch(builder => builder.WithName(branch2).FromSourceBranch(branch1).WithNumberOfEmptyCommits(1).PushToRemote())
.WithNumberOfEmptyCommitsOnRemoteTrackingBranchOf(sourceBranch, 5, b => b.PushToRemote())
.Build();

var tipOfRemoteSourceBranch = repo.GetTipOfRemoteBranch(sourceBranch);
repo.GetCommitsReachableFromBranch(sourceBranch).Should().NotContain(tipOfRemoteSourceBranch);

var stackConfig = new TestStackConfigBuilder()
.WithStack(stack => stack
.WithName("Stack1")
.WithRemoteUri(repo.RemoteUri)
.WithSourceBranch(sourceBranch)
.WithBranch(stackBranch => stackBranch.WithName(branch1))
.WithBranch(stackBranch => stackBranch.WithName(branch2)))
.WithStack(stack => stack
.WithName("Stack2")
.WithRemoteUri(repo.RemoteUri)
.WithSourceBranch(sourceBranch))
.Build();
var inputProvider = Substitute.For<IInputProvider>();
var logger = new TestLogger(testOutputHelper);
var gitClient = new GitClient(logger, repo.GitClientSettings);
var gitHubClient = Substitute.For<IGitHubClient>();
var handler = new SyncStackCommandHandler(inputProvider, logger, gitClient, gitHubClient, stackConfig);

gitClient.ChangeBranch(branch1);

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

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

// Assert
repo.GetCommitsReachableFromBranch(branch1).Should().Contain(tipOfRemoteSourceBranch);
repo.GetCommitsReachableFromBranch(branch2).Should().Contain(tipOfRemoteSourceBranch);
repo.GetCommitsReachableFromRemoteBranch(branch1).Should().NotContain(tipOfRemoteSourceBranch);
repo.GetCommitsReachableFromRemoteBranch(branch2).Should().NotContain(tipOfRemoteSourceBranch);
}
}
17 changes: 13 additions & 4 deletions src/Stack/Commands/Remote/SyncStackCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,19 @@ namespace Stack.Commands;

public class SyncStackCommand : Command
{
readonly Option<bool> NoPush = new("--no-push")
{
Description = "Don't push changes to the remote repository"
};

public SyncStackCommand() : base("sync", "Sync a stack with the remote repository. Shortcut for `git fetch --prune`, `stack pull`, `stack update` and `stack push`.")
{
Add(CommonOptions.Stack);
Add(CommonOptions.MaxBatchSize);
Add(CommonOptions.Rebase);
Add(CommonOptions.Merge);
Add(CommonOptions.Confirm);
Add(NoPush);
}

protected override async Task Execute(ParseResult parseResult, CancellationToken cancellationToken)
Expand All @@ -31,7 +37,8 @@ await handler.Handle(new SyncStackCommandInputs(
parseResult.GetValue(CommonOptions.MaxBatchSize),
parseResult.GetValue(CommonOptions.Rebase),
parseResult.GetValue(CommonOptions.Merge),
parseResult.GetValue(CommonOptions.Confirm)));
parseResult.GetValue(CommonOptions.Confirm),
parseResult.GetValue(NoPush)));
}
}

Expand All @@ -40,9 +47,10 @@ public record SyncStackCommandInputs(
int MaxBatchSize,
bool? Rebase,
bool? Merge,
bool Confirm)
bool Confirm,
bool NoPush)
{
public static SyncStackCommandInputs Empty => new(null, 5, null, null, false);
public static SyncStackCommandInputs Empty => new(null, 5, null, null, false, false);
}

public class SyncStackCommandHandler(
Expand Down Expand Up @@ -108,7 +116,8 @@ public override async Task Handle(SyncStackCommandInputs inputs)

var forceWithLease = inputs.Rebase == true || StackHelpers.GetUpdateStrategyConfigValue(gitClient) == UpdateStrategy.Rebase;

StackHelpers.PushChanges(stack, inputs.MaxBatchSize, forceWithLease, gitClient, logger);
if (!inputs.NoPush)
StackHelpers.PushChanges(stack, inputs.MaxBatchSize, forceWithLease, gitClient, logger);

if (stack.SourceBranch.Equals(currentBranch, StringComparison.InvariantCultureIgnoreCase) ||
stack.AllBranchNames.Contains(currentBranch, StringComparer.OrdinalIgnoreCase))
Expand Down