Skip to content

Commit 6d7bbce

Browse files
authored
Merge pull request #29 from github/dry-run
feat: add `--dry-run` flag
2 parents 9c29a67 + c57152f commit 6d7bbce

File tree

4 files changed

+100
-52
lines changed

4 files changed

+100
-52
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ Combine all open pull requests in a repository that are created by dependabot:
5050
gh combine owner/repo --dependabot
5151
```
5252

53+
### In Dry Run Mode
54+
55+
You can run in dry run mode to see what would happen without actually creating a pull request or combining any pull requests:
56+
57+
```bash
58+
gh combine owner/repo --dry-run
59+
```
60+
5361
### With Passing CI
5462

5563
Combine multiple pull requests together but only if their CI checks are passing:

internal/cmd/combine_prs.go

Lines changed: 69 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -20,76 +20,97 @@ type RESTClientInterface interface {
2020
Patch(endpoint string, body io.Reader, response interface{}) error
2121
}
2222

23+
// CombineOpts holds options for combining PRs
24+
// Use this struct to pass options to CombinePRsWithStats and related functions
25+
// This makes the code more maintainable and clear
26+
type CombineOpts struct {
27+
Noop bool
28+
Command string
29+
Repo github.Repo
30+
Pulls github.Pulls
31+
}
32+
2333
// CombinePRsWithStats combines PRs and returns stats for summary output
24-
func CombinePRsWithStats(ctx context.Context, graphQlClient *api.GraphQLClient, restClient RESTClientInterface, repo github.Repo, pulls github.Pulls, command string) (combined []string, mergeConflicts []string, combinedPRLink string, err error) {
34+
func CombinePRsWithStats(ctx context.Context, graphQlClient *api.GraphQLClient, restClient RESTClientInterface, opts CombineOpts) (combined []string, mergeConflicts []string, combinedPRLink string, err error) {
2535
workingBranchName := combineBranchName + workingBranchSuffix
2636

27-
repoDefaultBranch, err := getDefaultBranch(ctx, restClient, repo)
37+
repoDefaultBranch, err := getDefaultBranch(ctx, restClient, opts.Repo)
2838
if err != nil {
2939
return nil, nil, "", fmt.Errorf("failed to get default branch: %w", err)
3040
}
3141

32-
baseBranchSHA, err := getBranchSHA(ctx, restClient, repo, repoDefaultBranch)
42+
baseBranchSHA, err := getBranchSHA(ctx, restClient, opts.Repo, repoDefaultBranch)
3343
if err != nil {
3444
return nil, nil, "", fmt.Errorf("failed to get SHA of main branch: %w", err)
3545
}
36-
// Delete any pre-existing working branch
3746

38-
// Delete any pre-existing working branch
39-
err = deleteBranch(ctx, restClient, repo, workingBranchName)
40-
if err != nil {
41-
Logger.Debug("Working branch not found, continuing", "branch", workingBranchName)
42-
43-
// Delete any pre-existing combined branch
47+
if opts.Noop {
48+
Logger.Debug("Dry-run mode enabled. No changes will be made.")
49+
Logger.Debug("Simulating branch operations", "workingBranch", workingBranchName, "defaultBranch", repoDefaultBranch)
4450
}
4551

46-
// Delete any pre-existing combined branch
47-
err = deleteBranch(ctx, restClient, repo, combineBranchName)
48-
if err != nil {
49-
Logger.Debug("Combined branch not found, continuing", "branch", combineBranchName)
50-
}
52+
if !opts.Noop {
53+
err = deleteBranch(ctx, restClient, opts.Repo, workingBranchName)
54+
if err != nil {
55+
Logger.Debug("Working branch not found, continuing", "branch", workingBranchName)
56+
}
5157

52-
err = createBranch(ctx, restClient, repo, combineBranchName, baseBranchSHA)
53-
if err != nil {
54-
return nil, nil, "", fmt.Errorf("failed to create combined branch: %w", err)
55-
}
56-
err = createBranch(ctx, restClient, repo, workingBranchName, baseBranchSHA)
57-
if err != nil {
58-
return nil, nil, "", fmt.Errorf("failed to create working branch: %w", err)
59-
}
58+
err = deleteBranch(ctx, restClient, opts.Repo, combineBranchName)
59+
if err != nil {
60+
Logger.Debug("Combined branch not found, continuing", "branch", combineBranchName)
61+
}
6062

61-
for _, pr := range pulls {
62-
err := mergeBranch(ctx, restClient, repo, workingBranchName, pr.Head.Ref)
63+
err = createBranch(ctx, restClient, opts.Repo, combineBranchName, baseBranchSHA)
6364
if err != nil {
64-
if isMergeConflictError(err) {
65-
Logger.Debug("Merge conflict", "branch", pr.Head.Ref, "error", err)
65+
return nil, nil, "", fmt.Errorf("failed to create combined branch: %w", err)
66+
}
67+
68+
err = createBranch(ctx, restClient, opts.Repo, workingBranchName, baseBranchSHA)
69+
if err != nil {
70+
return nil, nil, "", fmt.Errorf("failed to create working branch: %w", err)
71+
}
72+
}
73+
74+
for _, pr := range opts.Pulls {
75+
if opts.Noop {
76+
Logger.Debug("Simulating merge of branch", "branch", pr.Head.Ref)
77+
combined = append(combined, fmt.Sprintf("#%d - %s", pr.Number, pr.Title))
78+
} else {
79+
err := mergeBranch(ctx, restClient, opts.Repo, workingBranchName, pr.Head.Ref)
80+
if err != nil {
81+
if isMergeConflictError(err) {
82+
Logger.Debug("Merge conflict", "branch", pr.Head.Ref, "error", err)
83+
} else {
84+
Logger.Warn("Failed to merge branch", "branch", pr.Head.Ref, "error", err)
85+
}
86+
mergeConflicts = append(mergeConflicts, fmt.Sprintf("#%d", pr.Number))
6687
} else {
67-
Logger.Warn("Failed to merge branch", "branch", pr.Head.Ref, "error", err)
88+
Logger.Debug("Merged branch", "branch", pr.Head.Ref)
89+
combined = append(combined, fmt.Sprintf("#%d - %s", pr.Number, pr.Title))
6890
}
69-
mergeConflicts = append(mergeConflicts, fmt.Sprintf("#%d", pr.Number))
70-
} else {
71-
Logger.Debug("Merged branch", "branch", pr.Head.Ref)
72-
combined = append(combined, fmt.Sprintf("#%d - %s", pr.Number, pr.Title))
7391
}
7492
}
7593

76-
err = updateRef(ctx, restClient, repo, combineBranchName, workingBranchName)
77-
if err != nil {
78-
return combined, mergeConflicts, "", fmt.Errorf("failed to update combined branch: %w", err)
79-
}
80-
err = deleteBranch(ctx, restClient, repo, workingBranchName)
81-
if err != nil {
82-
Logger.Warn("Failed to delete working branch", "branch", workingBranchName, "error", err)
83-
}
94+
if !opts.Noop {
95+
err = updateRef(ctx, restClient, opts.Repo, combineBranchName, workingBranchName)
96+
if err != nil {
97+
return combined, mergeConflicts, "", fmt.Errorf("failed to update combined branch: %w", err)
98+
}
8499

85-
prBody := generatePRBody(combined, mergeConflicts, command)
86-
prTitle := "Combined PRs"
87-
prNumber, prErr := createPullRequestWithNumber(ctx, restClient, repo, prTitle, combineBranchName, repoDefaultBranch, prBody, addLabels, addAssignees)
88-
if prErr != nil {
89-
return combined, mergeConflicts, "", fmt.Errorf("failed to create combined PR: %w", prErr)
90-
}
91-
if prNumber > 0 {
92-
combinedPRLink = fmt.Sprintf("https://github.com/%s/%s/pull/%d", repo.Owner, repo.Repo, prNumber)
100+
err = deleteBranch(ctx, restClient, opts.Repo, workingBranchName)
101+
if err != nil {
102+
Logger.Warn("Failed to delete working branch", "branch", workingBranchName, "error", err)
103+
}
104+
105+
prBody := generatePRBody(combined, mergeConflicts, opts.Command)
106+
prTitle := "Combined PRs"
107+
prNumber, prErr := createPullRequestWithNumber(ctx, restClient, opts.Repo, prTitle, combineBranchName, repoDefaultBranch, prBody, addLabels, addAssignees)
108+
if prErr != nil {
109+
return combined, mergeConflicts, "", fmt.Errorf("failed to create combined PR: %w", prErr)
110+
}
111+
if prNumber > 0 {
112+
combinedPRLink = fmt.Sprintf("https://github.com/%s/%s/pull/%d", opts.Repo.Owner, opts.Repo.Repo, prNumber)
113+
}
93114
}
94115

95116
return combined, mergeConflicts, combinedPRLink, nil

internal/cmd/output.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -236,14 +236,20 @@ func displaySummaryTable(stats *StatsCollector) {
236236
21, // Fixed width for the skipped column
237237
)
238238

239+
summaryRowPRCount := interface{}(len(stats.CombinedPRLinks))
240+
241+
if summaryRowPRCount == 0 && dryRun {
242+
summaryRowPRCount = "DRY RUN"
243+
}
244+
239245
// Generate the summary row
240246
summaryRow := fmt.Sprintf(
241-
"│ %-13d │ %-13d │ %s%s │ %-13d │",
247+
"│ %-13d │ %-13d │ %s%s │ %-13v │",
242248
stats.ReposProcessed,
243249
stats.PRsCombined,
244250
skippedSummaryText,
245251
summaryPadding,
246-
len(stats.CombinedPRLinks),
252+
summaryRowPRCount,
247253
)
248254

249255
// Print the summary table

internal/cmd/root.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var (
3939
noColor bool
4040
noStats bool
4141
outputFormat string
42+
dryRun bool
4243
)
4344

4445
// StatsCollector tracks stats for the CLI run
@@ -114,6 +115,7 @@ func NewRootCmd() *cobra.Command {
114115
gh combine owner/repo --add-assignees octocat,hubot # Assign users to the new PR
115116
116117
# Additional options
118+
gh combine owner/repo --dry-run # Simulate the actions without making any changes
117119
gh combine owner/repo --autoclose # Close source PRs when combined PR is merged
118120
gh combine owner/repo --base-branch main # Use a different base branch for the combined PR
119121
gh combine owner/repo --no-color # Disable color output
@@ -154,6 +156,7 @@ func NewRootCmd() *cobra.Command {
154156
rootCmd.Flags().BoolVar(&noColor, "no-color", false, "Disable color output")
155157
rootCmd.Flags().BoolVar(&noStats, "no-stats", false, "Disable stats summary display")
156158
rootCmd.Flags().StringVar(&outputFormat, "output", "table", "Output format: table, plain, or json")
159+
rootCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Simulate the actions without making any changes")
157160

158161
// Add deprecated flags for backward compatibility
159162
// rootCmd.Flags().IntVar(&minimum, "min-combine", 2, "Minimum number of PRs to combine (deprecated, use --minimum)")
@@ -336,9 +339,16 @@ func processRepository(ctx context.Context, client *api.RESTClient, graphQlClien
336339
RESTClientInterface
337340
}{client}
338341

339-
// Combine the PRs and collect stats
340342
commandString := buildCommandString([]string{repo.String()})
341-
combined, mergeConflicts, combinedPRLink, err := CombinePRsWithStats(ctx, graphQlClient, restClientWrapper, repo, matchedPRs, commandString)
343+
344+
opts := CombineOpts{
345+
Noop: dryRun,
346+
Command: commandString,
347+
Repo: repo,
348+
Pulls: matchedPRs,
349+
}
350+
351+
combined, mergeConflicts, combinedPRLink, err := CombinePRsWithStats(ctx, graphQlClient, restClientWrapper, opts)
342352
if err != nil {
343353
return fmt.Errorf("failed to combine PRs: %w", err)
344354
}
@@ -477,6 +487,9 @@ func buildCommandString(args []string) string {
477487
if outputFormat != "table" && outputFormat != "" {
478488
cmd = append(cmd, "--output", outputFormat)
479489
}
490+
if dryRun {
491+
cmd = append(cmd, "--dry-run")
492+
}
480493

481494
return strings.Join(cmd, " ")
482495
}

0 commit comments

Comments
 (0)