Skip to content

Commit

Permalink
More improvements to pull request creation
Browse files Browse the repository at this point in the history
  • Loading branch information
geofflamrock committed Dec 11, 2024
1 parent c7bffb7 commit 25ec58f
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 179 deletions.

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions src/Stack/Commands/Helpers/Questions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ public static class Questions
public const string ConfirmSwitchToBranch = "Do you want to switch to the new branch?";
public static string ConfirmStartCreatePullRequests(int numberOfBranchesWithoutPullRequests) => $"There {"are".ToQuantity(numberOfBranchesWithoutPullRequests, ShowQuantityAs.None)} {"branch".ToQuantity(numberOfBranchesWithoutPullRequests)} to create pull requests for. Do you want to continue?";
public const string ConfirmCreatePullRequests = "Are you sure you want to create pull requests for branches in this stack?";
public static string PullRequestTitle(string sourceBranch, string targetBranch) => $"Title for pull request from {sourceBranch.Branch()} -> {targetBranch.Branch()}:";
public const string PullRequestTitle = "Title:";
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?";
public const string OpenPullRequests = "Open the new pull requests in a browser?";
public const string CreatePullRequestAsDraft = "Create pull request as draft?";
}
88 changes: 45 additions & 43 deletions src/Stack/Commands/PullRequests/CreatePullRequestsCommand.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
using System.Collections;
using System.ComponentModel;
using System.Diagnostics;
using Spectre.Console;
using Spectre.Console.Cli;
using Stack.Commands.Helpers;
Expand All @@ -15,10 +13,6 @@ 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 @@ -32,17 +26,18 @@ public override async Task<int> ExecuteAsync(CommandContext context, CreatePullR
new ConsoleOutputProvider(console),
new GitOperations(console, settings.GetGitOperationSettings()),
new GitHubOperations(console, settings.GetGitHubOperationSettings()),
new FileOperations(),
new StackConfig());

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

return 0;
}
}

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

public record CreatePullRequestsCommandResponse();
Expand All @@ -52,6 +47,7 @@ public class CreatePullRequestsCommandHandler(
IOutputProvider outputProvider,
IGitOperations gitOperations,
IGitHubOperations gitHubOperations,
IFileOperations fileOperations,
IStackConfig stackConfig)
{
public async Task<CreatePullRequestsCommandResponse> Handle(CreatePullRequestsCommandInputs inputs)
Expand Down Expand Up @@ -111,7 +107,7 @@ public async Task<CreatePullRequestsCommandResponse> Handle(CreatePullRequestsCo
{
if (inputProvider.Confirm(Questions.ConfirmStartCreatePullRequests(pullRequestCreateActions.Count)))
{
GetPullRequestTitles(inputProvider, pullRequestCreateActions);
GetPullRequestInformation(inputProvider, outputProvider, gitOperations, fileOperations, pullRequestCreateActions);

outputProvider.NewLine();

Expand All @@ -121,18 +117,7 @@ public async Task<CreatePullRequestsCommandResponse> Handle(CreatePullRequestsCo

if (inputProvider.Confirm(Questions.ConfirmCreatePullRequests))
{
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 newPullRequests = CreatePullRequests(outputProvider, gitHubOperations, status, pullRequestCreateActions);

var pullRequestsInStack = status.Branches.Values
.Where(branch => branch.HasPullRequest)
Expand All @@ -146,7 +131,7 @@ public async Task<CreatePullRequestsCommandResponse> Handle(CreatePullRequestsCo

if (inputProvider.Confirm(Questions.OpenPullRequests))
{
foreach (var pullRequest in pullRequestsInStack)
foreach (var pullRequest in newPullRequests)
{
gitHubOperations.OpenPullRequest(pullRequest);
}
Expand Down Expand Up @@ -219,36 +204,28 @@ private static void UpdatePullRequestStackDescriptions(IInputProvider inputProvi
}
}

private static void CreatePullRequests(
private static List<GitHubPullRequest> CreatePullRequests(
IOutputProvider outputProvider,
IGitOperations gitOperations,
IGitHubOperations gitHubOperations,
StackStatus status,
List<GitHubPullRequestCreateAction> pullRequestCreateActions,
bool draft)
List<GitHubPullRequestCreateAction> pullRequestCreateActions)
{
var pullRequestTemplatePath = Path.Join(gitOperations.GetRootOfRepository(), ".github", "pull_request_template.md");
var body = string.Empty;
var pullRequestTemplate = gitOperations.GetFileContents(pullRequestTemplatePath);

if (pullRequestTemplate is not null)
{
body = pullRequestTemplate;
outputProvider.Information("Using pull request template from repository");
}

var pullRequests = new List<GitHubPullRequest>();
foreach (var action in pullRequestCreateActions)
{
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, draft);
var pullRequest = gitHubOperations.CreatePullRequest(action.HeadBranch, action.BaseBranch, action.Title!, action.BodyFilePath!, action.Draft);

if (pullRequest is not null)
{
outputProvider.Information($"Pull request {pullRequest.GetPullRequestDisplay()} created for branch {action.HeadBranch.Branch()} to {action.BaseBranch.Branch()}");
pullRequests.Add(pullRequest);
branchDetail.PullRequest = pullRequest;
}
}

return pullRequests;
}

private static void OutputUpdatedStackStatus(IOutputProvider outputProvider, IGitOperations gitOperations, Config.Stack stack, StackStatus status, List<GitHubPullRequestCreateAction> pullRequestCreateActions)
Expand All @@ -259,14 +236,14 @@ private static void OutputUpdatedStackStatus(IOutputProvider outputProvider, IGi
foreach (var branch in stack.Branches)
{
var branchDetail = status.Branches[branch];
if (branchDetail.PullRequest is not null)
if (branchDetail.PullRequest is not null && branchDetail.PullRequest.State != GitHubPullRequestStates.Closed)
{
branchDisplayItems.Add(StackStatusHelpers.GetBranchAndPullRequestStatusOutput(branch, parentBranch, branchDetail, gitOperations));
}
else
{
var action = pullRequestCreateActions.FirstOrDefault(a => a.HeadBranch == branch);
branchDisplayItems.Add($"{StackStatusHelpers.GetBranchStatusOutput(branch, parentBranch, branchDetail, gitOperations)} *NEW* {action?.Title}");
branchDisplayItems.Add($"{StackStatusHelpers.GetBranchStatusOutput(branch, parentBranch, branchDetail, gitOperations)} *NEW* {action?.Title}{(action?.Draft == true ? " (draft)".Muted() : string.Empty)}");
}
parentBranch = branch;
}
Expand All @@ -276,17 +253,42 @@ private static void OutputUpdatedStackStatus(IOutputProvider outputProvider, IGi
[.. branchDisplayItems]);
}

private static void GetPullRequestTitles(IInputProvider inputProvider, List<GitHubPullRequestCreateAction> pullRequestCreateActions)
private static void GetPullRequestInformation(
IInputProvider inputProvider,
IOutputProvider outputProvider,
IGitOperations gitOperations,
IFileOperations fileOperations,
List<GitHubPullRequestCreateAction> pullRequestCreateActions)
{
var pullRequestTemplateFileNames = new List<string>(["PULL_REQUEST_TEMPLATE.md", "pull_request_template.md"]);

var pullRequestTemplatePath = pullRequestTemplateFileNames
.Select(fileName => Path.Join(gitOperations.GetRootOfRepository(), ".github", fileName))
.FirstOrDefault(fileOperations.Exists);

foreach (var action in pullRequestCreateActions)
{
action.Title = inputProvider.Text(Questions.PullRequestTitle(action.HeadBranch, action.BaseBranch));
outputProvider.NewLine();
var pullRequestHeader = $"New pull request from {action.HeadBranch.Branch()} -> {action.BaseBranch.Branch()}";
outputProvider.Rule(pullRequestHeader);

action.Title = inputProvider.Text(Questions.PullRequestTitle);
action.BodyFilePath = Path.Join(fileOperations.GetTempPath(), $"stack-pr-{Guid.NewGuid():N}.md");
if (pullRequestTemplatePath is not null)
fileOperations.Copy(pullRequestTemplatePath, action.BodyFilePath, true);

outputProvider.Information($"Edit body for pull request from {action.HeadBranch.Branch()} -> {action.BaseBranch.Branch()}, save and close when ready");

gitOperations.OpenFileInEditorAndWaitForClose(action.BodyFilePath);

action.Draft = inputProvider.Confirm(Questions.CreatePullRequestAsDraft, false);
}
}

record GitHubPullRequestCreateAction(string HeadBranch, string BaseBranch)
{
public string? Title { get; set; }
public string? BodyFilePath { get; set; }
public bool Draft { get; set; }
}
}

37 changes: 32 additions & 5 deletions src/Stack/Git/GitOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public interface IGitOperations
string GetRemoteUri();
string[] GetLocalBranchesOrderedByMostRecentCommitterDate();
string GetRootOfRepository();
string? GetFileContents(string path);
void OpenFileInEditorAndWaitForClose(string path);
}

public class GitOperations(IAnsiConsole console, GitOperationSettings settings) : IGitOperations
Expand Down Expand Up @@ -159,12 +159,34 @@ public string GetRootOfRepository()
return ExecuteGitCommandAndReturnOutput("rev-parse --show-toplevel").Trim();
}

public string? GetFileContents(string path)
public void OpenFileInEditorAndWaitForClose(string path)
{
if (!File.Exists(path))
return null;
var editor = GetConfiguredEditor();
if (string.IsNullOrWhiteSpace(editor))
{
console.MarkupLine("[red]No editor is configured in git. Please configure an editor using 'git config --global core.editor <editor>'.[/]");
return;
}

var editorSplit = editor.Split(' ');
var editorFileName = editorSplit[0];
var editorArguments = editorSplit.Length > 1 ? string.Join(' ', editorSplit.Skip(1)) : string.Empty;

var errorBuilder = new StringBuilder();

int result = ShellExecutor.ExecuteCommand(
editorFileName,
$"\"{path}\" {editorArguments}",
".",
(_) => { },
(_) => { },
(error) => errorBuilder.AppendLine(error));

return File.ReadAllText(path);
if (result != 0)
{
console.MarkupLine($"[red]{errorBuilder}[/]");
throw new Exception("Failed to open file in editor.");
}
}

private string ExecuteGitCommandAndReturnOutput(string command)
Expand Down Expand Up @@ -218,4 +240,9 @@ private void ExecuteGitCommand(string command)
}
}
}

private string GetConfiguredEditor()
{
return ExecuteGitCommandAndReturnOutput("config --get core.editor").Trim();
}
}
9 changes: 9 additions & 0 deletions src/Stack/Infrastructure/ConsoleOutputProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public interface IOutputProvider
void Warning(string message);
void Error(string message);
void Debug(string message);
void Rule(string message);
}

public static class OutputProviderExtensionMethods
Expand Down Expand Up @@ -38,6 +39,14 @@ public void Tree(string header, string[] items)

console.Write(tree);
}

public void Rule(string message)
{
var rule = new Rule(message);
rule.LeftJustified();
rule.DoubleBorder();
console.Write(rule);
}
}

public static class OutputStyleExtensionMethods
Expand Down
26 changes: 26 additions & 0 deletions src/Stack/Infrastructure/FileOperations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace Stack.Infrastructure;

public interface IFileOperations
{
void Copy(string sourceFileName, string destFileName, bool overwrite);
bool Exists(string path);
string GetTempPath();
}

public class FileOperations : IFileOperations
{
public void Copy(string sourceFileName, string destFileName, bool overwrite)
{
File.Copy(sourceFileName, destFileName, overwrite);
}

public bool Exists(string path)
{
return File.Exists(path);
}

public string GetTempPath()
{
return Path.GetTempPath();
}
}

0 comments on commit 25ec58f

Please sign in to comment.