Class: Ace::Git::Worktree::Molecules::PrCreator

Inherits:
Object
  • Object
show all
Defined in:
lib/ace/git/worktree/molecules/pr_creator.rb

Overview

PR creator molecule

Creates draft pull requests on GitHub using the gh CLI. Provides a simple interface for creating PRs with graceful degradation when gh CLI is unavailable or not authenticated.

Examples:

Create a draft PR

creator = PrCreator.new
result = creator.create_draft(
  branch: "125-upstream-setup",
  base: "main",
  title: "125 - upstream-setup-and-pr-creation"
)
result[:pr_number] # => 456
result[:pr_url] # => "https://github.com/owner/repo/pull/456"

Handle unavailable gh CLI

creator = PrCreator.new
unless creator.gh_available?
  puts "gh CLI not available"
end

Defined Under Namespace

Classes: GhNotAuthenticatedError, GhNotAvailableError, NetworkError, PrAlreadyExistsError

Instance Method Summary collapse

Constructor Details

#initialize(timeout: 30) ⇒ PrCreator

Initialize a new PrCreator

Parameters:

  • timeout (Integer) (defaults to: 30)

    Timeout in seconds (default: 30)



47
48
49
# File 'lib/ace/git/worktree/molecules/pr_creator.rb', line 47

def initialize(timeout: 30)
  @timeout = timeout
end

Instance Method Details

#create_draft(branch:, base:, title:, body: nil) ⇒ Hash

Create a draft pull request

Examples:

result = creator.create_draft(branch: "feature", base: "main", title: "Add feature")
result[:success] # => true
result[:pr_number] # => 123
result[:pr_url] # => "https://github.com/owner/repo/pull/123"

Parameters:

  • branch (String)

    Head branch for the PR

  • base (String)

    Base branch to merge into

  • title (String)

    PR title

  • body (String, nil) (defaults to: nil)

    PR body/description

Returns:

  • (Hash)

    Result hash with :success, :pr_number, :pr_url, :error



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/ace/git/worktree/molecules/pr_creator.rb', line 64

def create_draft(branch:, base:, title:, body: nil)
  # Check gh availability and authentication
  unless gh_available?
    return error_result("gh CLI is not installed")
  end

  unless gh_authenticated?
    return error_result("gh CLI is not authenticated. Run: gh auth login")
  end

  # Check if PR already exists for this branch
  existing_pr = find_existing_pr(branch: branch)
  if existing_pr
    return {
      success: true,
      pr_number: existing_pr[:number],
      pr_url: existing_pr[:url],
      existing: true,
      message: "PR already exists for branch"
    }
  end

  # Build gh command
  cmd = ["gh", "pr", "create", "--draft", "--head", branch, "--base", base, "--title", title]
  cmd += ["--body", body || title]

  # Execute command
  stdout, stderr, status = execute_with_timeout(cmd, @timeout)

  if status.success?
    # Parse PR URL from output
    pr_url = stdout.strip
    pr_number = extract_pr_number(pr_url)

    {
      success: true,
      pr_number: pr_number,
      pr_url: pr_url,
      existing: false,
      error: nil
    }
  else
    handle_creation_error(stderr)
  end
rescue => e
  error_result("Unexpected error: #{e.message}")
end

#find_existing_pr(branch:) ⇒ Hash?

Find an existing PR for a branch

Examples:

pr = creator.find_existing_pr(branch: "feature-branch")
pr[:number] # => 123
pr[:url] # => "https://github.com/owner/repo/pull/123"

Parameters:

  • branch (String)

    Branch name to search for

Returns:

  • (Hash, nil)

    PR info hash or nil if not found



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/ace/git/worktree/molecules/pr_creator.rb', line 142

def find_existing_pr(branch:)
  return nil unless gh_available?

  # Search for open PRs with this head branch
  cmd = [
    "gh", "pr", "list",
    "--head", branch,
    "--state", "open",
    "--json", "number,url",
    "--limit", "1"
  ]

  stdout, _stderr, status = execute_with_timeout(cmd, @timeout)
  return nil unless status.success?

  prs = JSON.parse(stdout)
  return nil if prs.empty?

  pr = prs.first
  {
    number: pr["number"],
    url: pr["url"]
  }
rescue JSON::ParserError
  nil
rescue
  nil
end

#gh_authenticated?Boolean

Check if gh CLI is authenticated (cached)

Returns:

  • (Boolean)

    true if gh is authenticated



124
125
126
127
128
129
130
131
# File 'lib/ace/git/worktree/molecules/pr_creator.rb', line 124

def gh_authenticated?
  return @gh_authenticated unless @gh_authenticated.nil?

  _, _stderr, status = Open3.capture3("gh", "auth", "status")
  @gh_authenticated = status.success?
rescue
  @gh_authenticated = false
end

#gh_available?Boolean

Check if gh CLI is available (cached)

Returns:

  • (Boolean)

    true if gh is installed and accessible



115
116
117
118
119
# File 'lib/ace/git/worktree/molecules/pr_creator.rb', line 115

def gh_available?
  return @gh_available unless @gh_available.nil?

  @gh_available = system("which gh > /dev/null 2>&1")
end

#gh_not_available_messageString

Get helpful error message when gh CLI is unavailable

Returns:

  • (String)

    User-friendly error message with installation guidance



174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/ace/git/worktree/molecules/pr_creator.rb', line 174

def gh_not_available_message
  <<~MESSAGE
    gh CLI is required for PR creation but is not installed.

    Install gh CLI:
    - macOS: brew install gh
    - Linux: See https://github.com/cli/cli#installation
    - Windows: See https://github.com/cli/cli#installation

    After installation, authenticate with: gh auth login
  MESSAGE
end