From ae78d2a124605e39fae5180ad24d298f7a73b334 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Mon, 11 Nov 2024 16:30:02 +1100 Subject: [PATCH 1/6] wip creating GitHub PRs for a stack --- .../PullRequests/CreatePullRequestsCommand.cs | 71 +++++++++++++++++++ src/Stack/Program.cs | 12 +++- 2 files changed, 80 insertions(+), 3 deletions(-) create mode 100644 src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs diff --git a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs new file mode 100644 index 0000000..303a0b0 --- /dev/null +++ b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs @@ -0,0 +1,71 @@ +using System.ComponentModel; +using Spectre.Console; +using Spectre.Console.Cli; +using Stack.Config; +using Stack.Git; + +namespace Stack.Commands; + +internal class CreatePullRequestsCommandSettings : DryRunCommandSettingsBase +{ + [Description("The name of the stack to update.")] + [CommandOption("-n|--name")] + public string? Name { get; init; } +} + +internal class CreatePullRequestsCommand : AsyncCommand +{ + public override async Task ExecuteAsync(CommandContext context, CreatePullRequestsCommandSettings settings) + { + await Task.CompletedTask; + + var stacks = StackConfig.Load(); + + var remoteUri = GitOperations.GetRemoteUri(settings.GetGitOperationSettings()); + + var stacksForRemote = stacks.Where(s => s.RemoteUri.Equals(remoteUri, StringComparison.OrdinalIgnoreCase)).ToList(); + + if (stacksForRemote.Count == 0) + { + AnsiConsole.WriteLine("No stacks found for current repository."); + return 0; + } + + var currentBranch = GitOperations.GetCurrentBranch(settings.GetGitOperationSettings()); + var stackSelection = settings.Name ?? AnsiConsole.Prompt(Prompts.Stack(stacksForRemote, currentBranch)); + var stack = stacksForRemote.First(s => s.Name.Equals(stackSelection, StringComparison.OrdinalIgnoreCase)); + + AnsiConsole.MarkupLine($"Stack: {stack.Name}"); + + if (AnsiConsole.Prompt(new ConfirmationPrompt("Are you sure you want to create pull requests for branches in this stack?"))) + { + var sourceBranch = stack.SourceBranch; + + foreach (var branch in stack.Branches) + { + if (GitOperations.DoesRemoteBranchExist(branch, settings.GetGitOperationSettings())) + { + var existingPullRequest = GitHubOperations.GetPullRequest(branch, settings.GetGitHubOperationSettings()); + + if (existingPullRequest is not null) + { + AnsiConsole.MarkupLine($"Pull request already exists for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]. Skipping..."); + continue; + } + + var prTitle = AnsiConsole.Prompt(new TextPrompt($"Pull request title for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]:")); + + AnsiConsole.MarkupLine($"Creating pull request for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]"); + sourceBranch = branch; + } + else + { + // Remote branch no longer exists, skip over + AnsiConsole.MarkupLine($"[red]Branch '{branch}' no longer exists on the remote repository. Skipping...[/]"); + } + } + } + + return 0; + } +} diff --git a/src/Stack/Program.cs b/src/Stack/Program.cs index 4509025..5bb91b1 100644 --- a/src/Stack/Program.cs +++ b/src/Stack/Program.cs @@ -18,9 +18,6 @@ configure.AddCommand("delete").WithDescription("Deletes a stack."); configure.AddCommand("update").WithDescription("Updates the branches in a stack."); - // Config commands - configure.AddCommand("config").WithDescription("Opens the configuration file in the default editor."); - // Branch commands configure.AddBranch("branch", branch => { @@ -30,6 +27,15 @@ branch.AddCommand("add").WithDescription("Adds an existing branch in a stack."); }); + // Pull request commands + configure.AddBranch("pr", pr => + { + pr.SetDescription("Manages pull requests for a stack."); + pr.AddCommand("create").WithDescription("Creates pull requests for a stack."); + }); + + // Config commands + configure.AddCommand("config").WithDescription("Opens the configuration file in the default editor."); }); await app.RunAsync(args); From 75943995c3d8c85f8e211f0aca794dcb008777fe Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Mon, 11 Nov 2024 16:55:12 +1100 Subject: [PATCH 2/6] More changes --- .../Helpers/DryRunCommandSettingsBase.cs | 2 ++ .../PullRequests/CreatePullRequestsCommand.cs | 14 ++++---- .../Commands/Stack/StackStatusCommand.cs | 11 +----- src/Stack/Git/GitHubOperations.cs | 35 ++++++++++++++++--- 4 files changed, 42 insertions(+), 20 deletions(-) diff --git a/src/Stack/Commands/Helpers/DryRunCommandSettingsBase.cs b/src/Stack/Commands/Helpers/DryRunCommandSettingsBase.cs index 9610c26..e839eac 100644 --- a/src/Stack/Commands/Helpers/DryRunCommandSettingsBase.cs +++ b/src/Stack/Commands/Helpers/DryRunCommandSettingsBase.cs @@ -13,4 +13,6 @@ internal class DryRunCommandSettingsBase : CommandSettingsBase public bool DryRun { get; init; } public override GitOperationSettings GetGitOperationSettings() => new(DryRun, Verbose); + + public override GitHubOperationSettings GetGitHubOperationSettings() => new(DryRun, Verbose); } diff --git a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs index 303a0b0..6d04c7f 100644 --- a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs +++ b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs @@ -47,15 +47,17 @@ public override async Task ExecuteAsync(CommandContext context, CreatePullR { var existingPullRequest = GitHubOperations.GetPullRequest(branch, settings.GetGitHubOperationSettings()); - if (existingPullRequest is not null) + if (existingPullRequest is not null && existingPullRequest.State != GitHubPullRequestStates.Closed) { - AnsiConsole.MarkupLine($"Pull request already exists for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]. Skipping..."); - continue; + AnsiConsole.MarkupLine($"Pull request [{existingPullRequest.GetPullRequestColor()} link={existingPullRequest.Url}]#{existingPullRequest.Number}: {existingPullRequest.Title}[/] already exists for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]. Skipping..."); + } + else + { + var prTitle = AnsiConsole.Prompt(new TextPrompt($"Pull request title for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]:")); + AnsiConsole.MarkupLine($"Creating pull request for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]"); + GitHubOperations.CreatePullRequest(branch, sourceBranch, prTitle, "test", settings.GetGitHubOperationSettings()); } - var prTitle = AnsiConsole.Prompt(new TextPrompt($"Pull request title for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]:")); - - AnsiConsole.MarkupLine($"Creating pull request for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]"); sourceBranch = branch; } else diff --git a/src/Stack/Commands/Stack/StackStatusCommand.cs b/src/Stack/Commands/Stack/StackStatusCommand.cs index 636c64f..857d931 100644 --- a/src/Stack/Commands/Stack/StackStatusCommand.cs +++ b/src/Stack/Commands/Stack/StackStatusCommand.cs @@ -163,16 +163,7 @@ string BuildBranchName(string branch, string? parentBranch, bool isSourceBranchF if (status.PullRequests.TryGetValue(branch, out var pr)) { - var prStatusColor = Color.Green; - if (pr.State == GitHubPullRequestStates.Merged) - { - prStatusColor = Color.Purple; - } - else if (pr.State == GitHubPullRequestStates.Closed) - { - prStatusColor = Color.Red; - } - branchNameBuilder.Append($" [{prStatusColor} link={pr.Url}]#{pr.Number}: {pr.Title}[/]"); + branchNameBuilder.Append($" [{pr.GetPullRequestColor()} link={pr.Url}]#{pr.Number}: {pr.Title}[/]"); } return branchNameBuilder.ToString(); diff --git a/src/Stack/Git/GitHubOperations.cs b/src/Stack/Git/GitHubOperations.cs index 55e0879..3d592fc 100644 --- a/src/Stack/Git/GitHubOperations.cs +++ b/src/Stack/Git/GitHubOperations.cs @@ -11,16 +11,29 @@ internal record GitHubOperationSettings(bool DryRun, bool Verbose) internal static class GitHubPullRequestStates { - public static string Open = "OPEN"; - public static string Closed = "CLOSED"; - public static string Merged = "MERGED"; + public const string Open = "OPEN"; + public const string Closed = "CLOSED"; + public const string Merged = "MERGED"; } internal record GitHubPullRequest(int Number, string Title, string State, Uri Url); -internal static class GitHubOperations +internal static class GitHubPullRequestExtensionMethods { + public static Color GetPullRequestColor(this GitHubPullRequest pullRequest) + { + return pullRequest.State switch + { + GitHubPullRequestStates.Open => Color.Green, + GitHubPullRequestStates.Closed => Color.Red, + GitHubPullRequestStates.Merged => Color.Purple, + _ => Color.Default + }; + } +} +internal static class GitHubOperations +{ public static GitHubPullRequest? GetPullRequest(string branch, GitHubOperationSettings settings) { var output = ExecuteGitHubCommandAndReturnOutput($"pr list --json title,number,state,url --head {branch} --state all", settings); @@ -30,6 +43,20 @@ internal static class GitHubOperations return pullRequests.FirstOrDefault(); } + public static GitHubPullRequest? CreatePullRequest(string headBranch, string baseBranch, string title, string body, GitHubOperationSettings settings) + { + ExecuteGitHubCommand($"pr create --title {title} --body {body} --base {baseBranch} --head {headBranch}", settings); + + if (settings.DryRun) + { + return null; + } + // var pullRequest = System.Text.Json.JsonSerializer.Deserialize(output, + // new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web))!; + + return null; + } + private static string ExecuteGitHubCommandAndReturnOutput(string command, GitHubOperationSettings settings) { if (settings.Verbose) From 365b9d28326e5d3918ec2d4fe7779927d66b7192 Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Mon, 11 Nov 2024 16:57:22 +1100 Subject: [PATCH 3/6] Fix PR creation command --- src/Stack/Git/GitHubOperations.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stack/Git/GitHubOperations.cs b/src/Stack/Git/GitHubOperations.cs index 3d592fc..645e8ae 100644 --- a/src/Stack/Git/GitHubOperations.cs +++ b/src/Stack/Git/GitHubOperations.cs @@ -45,7 +45,7 @@ internal static class GitHubOperations public static GitHubPullRequest? CreatePullRequest(string headBranch, string baseBranch, string title, string body, GitHubOperationSettings settings) { - ExecuteGitHubCommand($"pr create --title {title} --body {body} --base {baseBranch} --head {headBranch}", settings); + ExecuteGitHubCommand($"pr create --title \"{title}\" --body \"{body}\" --base {baseBranch} --head {headBranch}", settings); if (settings.DryRun) { From 2bdae6fff08784054048602601d7cabbee32636c Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Mon, 11 Nov 2024 17:08:01 +1100 Subject: [PATCH 4/6] Check if PR exists first --- .../PullRequests/CreatePullRequestsCommand.cs | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs index 6d04c7f..6829689 100644 --- a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs +++ b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs @@ -43,27 +43,27 @@ public override async Task ExecuteAsync(CommandContext context, CreatePullR foreach (var branch in stack.Branches) { - if (GitOperations.DoesRemoteBranchExist(branch, settings.GetGitOperationSettings())) - { - var existingPullRequest = GitHubOperations.GetPullRequest(branch, settings.GetGitHubOperationSettings()); + var existingPullRequest = GitHubOperations.GetPullRequest(branch, settings.GetGitHubOperationSettings()); - if (existingPullRequest is not null && existingPullRequest.State != GitHubPullRequestStates.Closed) - { - AnsiConsole.MarkupLine($"Pull request [{existingPullRequest.GetPullRequestColor()} link={existingPullRequest.Url}]#{existingPullRequest.Number}: {existingPullRequest.Title}[/] already exists for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]. Skipping..."); - } - else + if (existingPullRequest is not null && existingPullRequest.State != GitHubPullRequestStates.Closed) + { + AnsiConsole.MarkupLine($"Pull request [{existingPullRequest.GetPullRequestColor()} link={existingPullRequest.Url}]#{existingPullRequest.Number}: {existingPullRequest.Title}[/] already exists for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]. Skipping..."); + } + else + { + if (GitOperations.DoesRemoteBranchExist(branch, settings.GetGitOperationSettings())) { var prTitle = AnsiConsole.Prompt(new TextPrompt($"Pull request title for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]:")); AnsiConsole.MarkupLine($"Creating pull request for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]"); GitHubOperations.CreatePullRequest(branch, sourceBranch, prTitle, "test", settings.GetGitHubOperationSettings()); - } - sourceBranch = branch; - } - else - { - // Remote branch no longer exists, skip over - AnsiConsole.MarkupLine($"[red]Branch '{branch}' no longer exists on the remote repository. Skipping...[/]"); + sourceBranch = branch; + } + else + { + // Remote branch no longer exists, skip over + AnsiConsole.MarkupLine($"[red]Branch '{branch}' no longer exists on the remote repository. Skipping...[/]"); + } } } } From 43fa41a53e147a54fa835ee6d60f4a51307e612b Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Mon, 11 Nov 2024 20:03:36 +1100 Subject: [PATCH 5/6] Show PR details --- .../Commands/PullRequests/CreatePullRequestsCommand.cs | 7 ++++++- src/Stack/Git/GitHubOperations.cs | 8 +++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs index 6829689..0a4661f 100644 --- a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs +++ b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs @@ -55,7 +55,12 @@ public override async Task ExecuteAsync(CommandContext context, CreatePullR { var prTitle = AnsiConsole.Prompt(new TextPrompt($"Pull request title for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]:")); AnsiConsole.MarkupLine($"Creating pull request for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]"); - GitHubOperations.CreatePullRequest(branch, sourceBranch, prTitle, "test", settings.GetGitHubOperationSettings()); + var pullRequest = GitHubOperations.CreatePullRequest(branch, sourceBranch, prTitle, "test", settings.GetGitHubOperationSettings()); + + if (pullRequest is not null) + { + AnsiConsole.MarkupLine($"Pull request [{pullRequest.GetPullRequestColor()} link={pullRequest.Url}]#{pullRequest.Number}: {pullRequest.Title}[/] created for branch [blue]{branch}[/] to [blue]{sourceBranch}[/]"); + } sourceBranch = branch; } diff --git a/src/Stack/Git/GitHubOperations.cs b/src/Stack/Git/GitHubOperations.cs index 645e8ae..164b1bd 100644 --- a/src/Stack/Git/GitHubOperations.cs +++ b/src/Stack/Git/GitHubOperations.cs @@ -34,9 +34,9 @@ public static Color GetPullRequestColor(this GitHubPullRequest pullRequest) internal static class GitHubOperations { - public static GitHubPullRequest? GetPullRequest(string branch, GitHubOperationSettings settings) + public static GitHubPullRequest? GetPullRequest(string headBranch, GitHubOperationSettings settings) { - var output = ExecuteGitHubCommandAndReturnOutput($"pr list --json title,number,state,url --head {branch} --state all", settings); + var output = ExecuteGitHubCommandAndReturnOutput($"pr list --json title,number,state,url --head {headBranch} --state all", settings); var pullRequests = System.Text.Json.JsonSerializer.Deserialize>(output, new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web))!; @@ -51,10 +51,8 @@ internal static class GitHubOperations { return null; } - // var pullRequest = System.Text.Json.JsonSerializer.Deserialize(output, - // new System.Text.Json.JsonSerializerOptions(System.Text.Json.JsonSerializerDefaults.Web))!; - return null; + return GetPullRequest(headBranch, settings); } private static string ExecuteGitHubCommandAndReturnOutput(string command, GitHubOperationSettings settings) From 7993e1d1969aa99a4e37bfb285e43769d03c58fd Mon Sep 17 00:00:00 2001 From: Geoff Lamrock Date: Tue, 12 Nov 2024 08:24:13 +1100 Subject: [PATCH 6/6] Remove static AnsiConsole usage --- src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs index 562630e..7131919 100644 --- a/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs +++ b/src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs @@ -36,7 +36,7 @@ public override async Task ExecuteAsync(CommandContext context, CreatePullR } var currentBranch = gitOperations.GetCurrentBranch(settings.GetGitOperationSettings()); - var stackSelection = settings.Name ?? AnsiConsole.Prompt(Prompts.Stack(stacksForRemote, currentBranch)); + var stackSelection = settings.Name ?? console.Prompt(Prompts.Stack(stacksForRemote, currentBranch)); var stack = stacksForRemote.First(s => s.Name.Equals(stackSelection, StringComparison.OrdinalIgnoreCase)); console.MarkupLine($"Stack: {stack.Name}");