Module: GT::GitHub

Defined in:
lib/gt/github.rb

Constant Summary collapse

STACK_MARKER =
"<!-- gt-stack -->"

Class Method Summary collapse

Class Method Details

.find_stack_comment_id(pr_number) ⇒ Object



82
83
84
85
86
87
88
89
90
91
# File 'lib/gt/github.rb', line 82

def find_stack_comment_id(pr_number)
  out, _, status = Open3.capture3(
    "gh api repos/{owner}/{repo}/issues/#{pr_number}/comments " \
    "--jq '[.[] | select(.body | contains(\"#{STACK_MARKER}\")) | .id] | first'"
  )
  return nil unless status.success?

  id = out.strip
  (id.empty? || id == "null") ? nil : id.to_i
end

.merged_into(branch) ⇒ Object



28
29
30
31
32
33
34
# File 'lib/gt/github.rb', line 28

def merged_into(branch)
  out, _, status = Open3.capture3("gh pr view #{branch} --json mergeCommit --jq .mergeCommit.oid")
  return nil unless status.success?

  sha = out.strip
  sha.empty? ? nil : sha
end

.pr_merged?(branch) ⇒ Boolean

Returns:

  • (Boolean)


9
10
11
12
13
14
15
16
# File 'lib/gt/github.rb', line 9

def pr_merged?(branch)
  out, _, status = Open3.capture3("gh pr view #{branch} --json state --jq .state")
  return false unless status.success?

  out.strip == "MERGED"
rescue Errno::ENOENT
  false
end

.pr_numbers(branches) ⇒ Object

Returns { “branch-name” => pr_number } for all open PRs whose branch is in branches. Uses a single gh pr list call. Returns {} on any failure.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/gt/github.rb', line 50

def pr_numbers(branches)
  out, _, status = Open3.capture3(
    "gh pr list --json number,headRefName " \
    "--jq 'map({(.headRefName): .number}) | add'"
  )
  return {} unless status.success?

  data = JSON.parse(out.strip)
  return {} unless data.is_a?(Hash)

  data.select { |branch, _| branches.include?(branch) }
rescue JSON::ParserError, Errno::ENOENT
  {}
end

.pr_retarget(branch, base) ⇒ Object



18
19
20
21
22
23
24
25
26
# File 'lib/gt/github.rb', line 18

def pr_retarget(branch, base)
  out, err, status = Open3.capture3("gh pr edit #{branch} --base #{base}")
  unless status.success?
    GT::UI.warn("Failed to retarget PR '#{branch}' to '#{base}': #{(err.strip.empty? ? out : err).strip}")
  end
  status.success?
rescue Errno::ENOENT
  false
end

.repo_urlObject



38
39
40
41
42
43
44
45
46
# File 'lib/gt/github.rb', line 38

def repo_url
  out, _, status = Open3.capture3("gh repo view --json url --jq .url")
  return nil unless status.success?

  url = out.strip
  url.empty? ? nil : url
rescue Errno::ENOENT
  nil
end

.stack_comment_body(branches, current_branch, pr_nums, base_url = nil) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/gt/github.rb', line 65

def stack_comment_body(branches, current_branch, pr_nums, base_url = nil)
  lines = ["[gt](https://github.com/grodowski/gt) stack", ""]
  branches.each do |branch|
    num = pr_nums[branch]
    if branch == current_branch
      pr_url = base_url && num ? "#{base_url}/pull/#{num}" : (num ? "##{num}" : nil)
      lines << (pr_url ? "- 👉 #{pr_url}" : "- 👉 **#{branch}**")
    else
      pr_url = base_url && num ? "#{base_url}/pull/#{num}" : (num ? "##{num}" : nil)
      lines << (pr_url ? "- #{pr_url}" : "- #{branch}")
    end
  end
  lines << ""
  lines << STACK_MARKER
  lines.join("\n")
end

.update_stack_comments(branches) ⇒ Object



109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/gt/github.rb', line 109

def update_stack_comments(branches)
  nums = pr_numbers(branches)
  url = repo_url
  branches[1..].each do |branch|
    next unless nums[branch]

    body = stack_comment_body(branches, branch, nums, url)
    upsert_stack_comment(nums[branch], body)
  end
rescue StandardError
  # Best-effort — silently skip if GitHub API is unavailable
end

.upsert_stack_comment(pr_number, body) ⇒ Object



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/gt/github.rb', line 93

def upsert_stack_comment(pr_number, body)
  json = JSON.generate({ body: body })
  existing_id = find_stack_comment_id(pr_number)
  if existing_id
    Open3.capture3(
      "gh api repos/{owner}/{repo}/issues/comments/#{existing_id} -X PATCH --input -",
      stdin_data: json
    )
  else
    Open3.capture3(
      "gh api repos/{owner}/{repo}/issues/#{pr_number}/comments --input -",
      stdin_data: json
    )
  end
end