Skip to content

Commit

Permalink
Add support for creating pull requests as drafts (#131)
Browse files Browse the repository at this point in the history
  • Loading branch information
geofflamrock authored Dec 10, 2024
1 parent f7a17bb commit ac77036
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 68 deletions.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ public async Task WhenThereAreMultiplePullRequestsInAStack_OpensAllPullRequests(

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

var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri());
var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri(), false);
gitHubOperations
.GetPullRequest("branch-3")
.Returns(prForBranch3);

var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri());
var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri(), false);
gitHubOperations
.GetPullRequest("branch-5")
.Returns(prForBranch5);
Expand Down Expand Up @@ -85,12 +85,12 @@ public async Task WhenThereAreSomePullRequestsInAStack_OpensAllPullRequests()

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

var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri());
var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri(), false);
gitHubOperations
.GetPullRequest("branch-3")
.Returns(prForBranch3);

var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Closed, Some.HttpsUri());
var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Closed, Some.HttpsUri(), false);

// Act
await handler.Handle(OpenPullRequestsCommandInputs.Empty);
Expand Down Expand Up @@ -123,12 +123,12 @@ public async Task WhenStackNameIsProvided_OpensAllPullRequestsForTheStack()
]);
stackConfig.Load().Returns(stacks);

var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri());
var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri(), false);
gitHubOperations
.GetPullRequest("branch-3")
.Returns(prForBranch3);

var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri());
var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri(), false);
gitHubOperations
.GetPullRequest("branch-5")
.Returns(prForBranch5);
Expand Down Expand Up @@ -163,12 +163,12 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName_OpensAllPullRequ
]);
stackConfig.Load().Returns(stacks);

var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri());
var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri(), false);
gitHubOperations
.GetPullRequest("branch-3")
.Returns(prForBranch3);

var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri());
var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri(), false);
gitHubOperations
.GetPullRequest("branch-5")
.Returns(prForBranch5);
Expand Down Expand Up @@ -204,12 +204,12 @@ public async Task WhenStackNameIsProvided_ButItStackDoesNotExist_Throws()
]);
stackConfig.Load().Returns(stacks);

var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri());
var prForBranch3 = new GitHubPullRequest(1, "PR Title for branch-3", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri(), false);
gitHubOperations
.GetPullRequest("branch-3")
.Returns(prForBranch3);

var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri());
var prForBranch5 = new GitHubPullRequest(2, "PR Title for branch-5", string.Empty, GitHubPullRequestStates.Open, Some.HttpsUri(), false);
gitHubOperations
.GetPullRequest("branch-5")
.Returns(prForBranch5);
Expand Down
14 changes: 7 additions & 7 deletions src/Stack.Tests/Commands/Stack/StackStatusCommandHandlerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public async Task WhenMultipleBranchesExistInAStack_AndOneHasAPullRequests_Retur
.GetStatusOfRemoteBranch("branch-5", "branch-3")
.Returns((1, 0));

var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri());
var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri(), false);

gitHubOperations
.GetPullRequest("branch-3")
Expand Down Expand Up @@ -117,7 +117,7 @@ public async Task WhenStackNameIsProvided_DoesNotAskForStack_ReturnsStatus()
.GetStatusOfRemoteBranch("branch-5", "branch-3")
.Returns((1, 0));

var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri());
var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri(), false);

gitHubOperations
.GetPullRequest("branch-3")
Expand Down Expand Up @@ -187,7 +187,7 @@ public async Task WhenAllStacksAreRequested_ReturnsStatusOfEachStack()
.GetStatusOfRemoteBranch("branch-4", "branch-2")
.Returns((3, 1));

var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri());
var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri(), false);

gitHubOperations
.GetPullRequest("branch-3")
Expand Down Expand Up @@ -263,7 +263,7 @@ public async Task WhenAllStacksAreRequested_WithStacksInMultipleRepositories_Ret
.GetStatusOfRemoteBranch("branch-4", "branch-2")
.Returns((3, 1));

var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri());
var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri(), false);

gitHubOperations
.GetPullRequest("branch-3")
Expand Down Expand Up @@ -360,7 +360,7 @@ public async Task WhenMultipleBranchesExistInAStack_AndOneNoLongerExistsOnTheRem
.GetStatusOfRemoteBranch("branch-5", "branch-1")
.Returns((1, 0));

var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri());
var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri(), false);

gitHubOperations
.GetPullRequest("branch-5")
Expand Down Expand Up @@ -421,7 +421,7 @@ public async Task WhenMultipleBranchesExistInAStack_AndOneNoLongerExistsOnTheRem
.GetStatusOfRemoteBranch("branch-5", "branch-1")
.Returns((1, 0));

var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri());
var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri(), false);

gitHubOperations
.GetPullRequest("branch-5")
Expand Down Expand Up @@ -484,7 +484,7 @@ public async Task WhenOnlyOneStackExists_DoesNotAskForStackName_ReturnsStatus()
.GetStatusOfRemoteBranch("branch-5", "branch-3")
.Returns((1, 0));

var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri());
var pr = new GitHubPullRequest(1, "PR title", "PR body", GitHubPullRequestStates.Open, Some.HttpsUri(), false);

gitHubOperations
.GetPullRequest("branch-3")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public async Task WhenABranchInTheStackExistsOnTheRemote_ButThePullRequestIsMerg
var branchesThatExistInRemote = new List<string>(["branch-1", "branch-2", "branch-3"]);

gitOperations.DoesRemoteBranchExist(Arg.Is<string>(b => branchesThatExistInRemote.Contains(b))).Returns(true);
gitHubOperations.GetPullRequest("branch-2").Returns(new GitHubPullRequest(1, Some.Name(), Some.Name(), GitHubPullRequestStates.Merged, Some.HttpsUri()));
gitHubOperations.GetPullRequest("branch-2").Returns(new GitHubPullRequest(1, Some.Name(), Some.Name(), GitHubPullRequestStates.Merged, Some.HttpsUri(), false));

// Act
await handler.Handle(new UpdateStackCommandInputs(null, false));
Expand Down
1 change: 1 addition & 0 deletions src/Stack/Commands/Helpers/Questions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ public static class Questions
public static string PullRequestTitle(string sourceBranch, string targetBranch) => $"Title for pull request from {sourceBranch.Branch()} -> {targetBranch.Branch()}:";
public const string PullRequestStackDescription = "Stack description for pull request:";
public const string OpenPullRequests = "Open the pull requests in the browser?";
public const string CreatePullRequestsAsDrafts = "Create pull requests as drafts?";
}
33 changes: 27 additions & 6 deletions src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ public class CreatePullRequestsCommandSettings : DryRunCommandSettingsBase
[Description("The name of the stack to create pull requests for.")]
[CommandOption("-n|--name")]
public string? Name { get; init; }

[Description("Create pull requests as drafts.")]
[CommandOption("--draft")]
public bool? Draft { get; init; }
}

public class CreatePullRequestsCommand : AsyncCommand<CreatePullRequestsCommandSettings>
Expand All @@ -30,15 +34,15 @@ public override async Task<int> ExecuteAsync(CommandContext context, CreatePullR
new GitHubOperations(console, settings.GetGitHubOperationSettings()),
new StackConfig());

await handler.Handle(new CreatePullRequestsCommandInputs(settings.Name));
await handler.Handle(new CreatePullRequestsCommandInputs(settings.Name, settings.Draft));

return 0;
}
}

public record CreatePullRequestsCommandInputs(string? StackName)
public record CreatePullRequestsCommandInputs(string? StackName, bool? Draft)
{
public static CreatePullRequestsCommandInputs Empty => new((string?)null);
public static CreatePullRequestsCommandInputs Empty => new(null, null);
}

public record CreatePullRequestsCommandResponse();
Expand Down Expand Up @@ -117,7 +121,18 @@ public async Task<CreatePullRequestsCommandResponse> Handle(CreatePullRequestsCo

if (inputProvider.Confirm(Questions.ConfirmCreatePullRequests))
{
CreatePullRequests(outputProvider, gitOperations, gitHubOperations, status, pullRequestCreateActions);
var draft = inputs.Draft ?? false;

if (inputs.Draft is null)
{
draft = inputProvider.Confirm(Questions.CreatePullRequestsAsDrafts, false);
}
else if (draft)
{
outputProvider.Information("Creating pull requests as drafts.");
}

CreatePullRequests(outputProvider, gitOperations, gitHubOperations, status, pullRequestCreateActions, draft);

var pullRequestsInStack = status.Branches.Values
.Where(branch => branch.HasPullRequest)
Expand Down Expand Up @@ -204,7 +219,13 @@ private static void UpdatePullRequestStackDescriptions(IInputProvider inputProvi
}
}

private static void CreatePullRequests(IOutputProvider outputProvider, IGitOperations gitOperations, IGitHubOperations gitHubOperations, StackStatus status, List<GitHubPullRequestCreateAction> pullRequestCreateActions)
private static void CreatePullRequests(
IOutputProvider outputProvider,
IGitOperations gitOperations,
IGitHubOperations gitHubOperations,
StackStatus status,
List<GitHubPullRequestCreateAction> pullRequestCreateActions,
bool draft)
{
var pullRequestTemplatePath = Path.Join(gitOperations.GetRootOfRepository(), ".github", "pull_request_template.md");
var body = string.Empty;
Expand All @@ -220,7 +241,7 @@ private static void CreatePullRequests(IOutputProvider outputProvider, IGitOpera
{
var branchDetail = status.Branches[action.HeadBranch];
outputProvider.Information($"Creating pull request for branch {action.HeadBranch.Branch()} to {action.BaseBranch.Branch()}");
var pullRequest = gitHubOperations.CreatePullRequest(action.HeadBranch, action.BaseBranch, action.Title!, body);
var pullRequest = gitHubOperations.CreatePullRequest(action.HeadBranch, action.BaseBranch, action.Title!, body, draft);

if (pullRequest is not null)
{
Expand Down
20 changes: 15 additions & 5 deletions src/Stack/Git/GitHubOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,15 @@ public static class GitHubPullRequestStates
public const string Merged = "MERGED";
}

public record GitHubPullRequest(int Number, string Title, string Body, string State, Uri Url);
public record GitHubPullRequest(int Number, string Title, string Body, string State, Uri Url, bool IsDraft);

public static class GitHubPullRequestExtensionMethods
{
public static Color GetPullRequestColor(this GitHubPullRequest pullRequest)
{
if (pullRequest.IsDraft)
return Color.Grey;

return pullRequest.State switch
{
GitHubPullRequestStates.Open => Color.Green,
Expand All @@ -40,7 +43,7 @@ public static string GetPullRequestDisplay(this GitHubPullRequest pullRequest)
public interface IGitHubOperations
{
GitHubPullRequest? GetPullRequest(string branch);
GitHubPullRequest? CreatePullRequest(string headBranch, string baseBranch, string title, string body);
GitHubPullRequest? CreatePullRequest(string headBranch, string baseBranch, string title, string body, bool draft);
void EditPullRequest(int number, string body);
void OpenPullRequest(GitHubPullRequest pullRequest);
}
Expand All @@ -49,16 +52,23 @@ public class GitHubOperations(IAnsiConsole console, GitHubOperationSettings sett
{
public GitHubPullRequest? GetPullRequest(string branch)
{
var output = ExecuteGitHubCommandAndReturnOutput($"pr list --json title,number,body,state,url --head {branch} --state all");
var output = ExecuteGitHubCommandAndReturnOutput($"pr list --json title,number,body,state,url,isDraft --head {branch} --state all");
var pullRequests = System.Text.Json.JsonSerializer.Deserialize<List<GitHubPullRequest>>(output,
new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web))!;

return pullRequests.FirstOrDefault();
}

public GitHubPullRequest? CreatePullRequest(string headBranch, string baseBranch, string title, string body)
public GitHubPullRequest? CreatePullRequest(string headBranch, string baseBranch, string title, string body, bool draft)
{
ExecuteGitHubCommand($"pr create --title \"{title}\" --body \"{body}\" --base {baseBranch} --head {headBranch}");
var command = $"pr create --title \"{title}\" --body \"{body}\" --base {baseBranch} --head {headBranch}";

if (draft)
{
command += " --draft";
}

ExecuteGitHubCommand(command);

if (settings.DryRun)
{
Expand Down
12 changes: 10 additions & 2 deletions src/Stack/Infrastructure/ConsoleInputProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public interface IInputProvider
string Select(string prompt, string[] choices);
T Select<T>(string prompt, T[] choices, Func<T, string>? converter = null) where T : notnull;
T SelectGrouped<T>(string prompt, ChoiceGroup<T>[] choices, Func<T, string>? converter = null) where T : notnull;
bool Confirm(string prompt);
bool Confirm(string prompt, bool defaultValue = true);
}

public class ConsoleInputProvider(IAnsiConsole console) : IInputProvider
Expand Down Expand Up @@ -65,7 +65,15 @@ public T SelectGrouped<T>(string prompt, ChoiceGroup<T>[] groups, Func<T, string
return console.Prompt(select);
}

public bool Confirm(string prompt) => console.Prompt(new ConfirmationPrompt(prompt));
public bool Confirm(string prompt, bool defaultValue = true)
{
var confirmationPrompt = new ConfirmationPrompt(prompt)
{
DefaultValue = defaultValue
};

return console.Prompt(confirmationPrompt);
}
}


0 comments on commit ac77036

Please sign in to comment.