Class: Github::PullRequest

Inherits:
ActiveRecord::Base
  • Object
show all
Defined in:
app/models/github/pull_request.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#actorObject

Returns the value of attribute actor.



6
7
8
# File 'app/models/github/pull_request.rb', line 6

def actor
  @actor
end

Class Method Details

._fetch_issues_for!(repo) ⇒ Object



71
72
73
74
75
76
77
78
79
# File 'app/models/github/pull_request.rb', line 71

def _fetch_issues_for!(repo)
  if repo.end_with? "/*"
    Houston.github.org_issues(repo[0...-2], filter: "all", state: "open")
  else
    Houston.github.issues(repo, filter: "all", state: "open")
  end
rescue Octokit::NotFound
  []
end

._repo_name_from_url(url) ⇒ Object



67
68
69
# File 'app/models/github/pull_request.rb', line 67

def _repo_name_from_url(url)
  url[/\Agit@github\.com:(.*)\.git\Z/, 1] || url[/\Agit:\/\/github.com\/(.*)\.git\Z/, 1]
end

.close!(github_pr, options = {}) ⇒ Object



111
112
113
114
115
116
117
118
119
# File 'app/models/github/pull_request.rb', line 111

def close!(github_pr, options={})
  pr = find_by(
    repo: github_pr["base"]["repo"]["name"],
    number: github_pr["number"])
  return unless pr

  pr.actor = options[:as]
  pr.destroy
end

.fetch!(projects = Project.unretired) ⇒ Object

Makes X + Y requests to GitHub where X is the number of projects in Houston on GitHub and Y is the number of pull requests for those projects

We could group repos by their owner and fetch ‘org_issues` but that will only work for organizations, not personal accounts.

This method can chomp through your rate limit rather quickly. Also, on my computer it took 19 seconds to fetch 39 pull requests from 52 repos.



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'app/models/github/pull_request.rb', line 37

def fetch!(projects = Project.unretired)
  repos = projects
    .where("extended_attributes->'git_location' LIKE '%github.com%'")
    .pluck("extended_attributes->'git_location'")
    .map { |url| _repo_name_from_url(url) }
    .compact

  Houston.benchmark "Fetching pull requests" do
    requests = 0
    issues = repos.flat_map do |repo|
      _fetch_issues_for!(repo).tap do |results|
        requests += 1 + (results.length / 30)
      end
    end

    pulls = issues
      .select { |issue| !issue.pull_request.nil? }
      .map { |issue|
        requests += 1
        repo = issue.pull_request.url[/https:\/\/api.github.com\/repos\/(.*)\/pulls\/\d+/, 1]
        Houston.github.pull_request(repo, issue.number)
          .to_h
          .merge(labels: issue.labels)
          .with_indifferent_access }

    Rails.logger.info "[pulls] #{requests} requests; #{Houston.github.last_response.headers["x-ratelimit-remaining"]} remaining"
    pulls
  end
end

.labeled(*labels) ⇒ Object Also known as: with_labels



141
142
143
# File 'app/models/github/pull_request.rb', line 141

def labeled(*labels)
  where(["exists (select 1 from jsonb_array_elements(pull_requests.json_labels) as \"label\" where \"label\"->>'name' IN (?))", labels])
end

.sync!(projects = Project.unretired) ⇒ Object



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
# File 'app/models/github/pull_request.rb', line 81

def sync!(projects = Project.unretired)
  expected_pulls = fetch!(projects)
  Houston.benchmark "Syncing pull requests" do
    existing_pulls = all.to_a

    # Delete unexpected pulls
    existing_pulls.each do |existing_pr|
      unless expected_pulls.detect { |expected_pr|
        expected_pr["base"]["repo"]["name"] == existing_pr.repo &&
        expected_pr["number"] == existing_pr.number }
        existing_pr.destroy
      end
    end

    # Create or Update existing pulls
    expected_pulls.map do |expected_pr|
      existing_pr = existing_pulls.detect { |existing_pr|
        expected_pr["base"]["repo"]["name"] == existing_pr.repo &&
        expected_pr["number"] == existing_pr.number }

      existing_pr ||= Github::PullRequest.new
      existing_pr.merge_attributes(expected_pr)
      unless existing_pr.save
        Rails.logger.warn "\e[31m[pulls] Invalid PR: #{existing_pr.errors.full_messages.join("; ")}\e[0m"
      end
      existing_pr
    end
  end
end

.upsert(github_pr) ⇒ Object



134
135
136
137
138
139
# File 'app/models/github/pull_request.rb', line 134

def upsert(github_pr)
  Github::PullRequest.find_or_initialize_by(
    repo: github_pr["base"]["repo"]["name"],
    number: github_pr["number"])
    .merge_attributes(github_pr)
end

.upsert!(github_pr, options = {}) ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
# File 'app/models/github/pull_request.rb', line 121

def upsert!(github_pr, options={})
  retry_count ||= 0
  upsert(github_pr).tap do |pr|
    if pr.valid?
      pr.actor = options[:as]
      pr.save
    end
  end
rescue ActiveRecord::RecordNotUnique
  retry unless (retry_count += 1) > 1
  raise
end

.without_labels(*labels) ⇒ Object



146
147
148
# File 'app/models/github/pull_request.rb', line 146

def without_labels(*labels)
  where(["not exists (select 1 from jsonb_array_elements(pull_requests.json_labels) as \"label\" where \"label\"->>'name' IN (?))", labels])
end

Instance Method Details

#add_label!(label, options = {}) ⇒ Object



169
170
171
172
173
174
175
176
177
# File 'app/models/github/pull_request.rb', line 169

def add_label!(label, options={})
  label = label.to_h.stringify_keys.pick("name", "color")

  transaction do
    pr = self.class.lock.find id
    new_labels = pr.json_labels.reject { |l| l["name"] == label["name"] } + [label]
    pr.update_attributes! json_labels: new_labels, actor: options[:as]
  end
end

#labeled?(*values) ⇒ Boolean

Returns:

  • (Boolean)


157
158
159
# File 'app/models/github/pull_request.rb', line 157

def labeled?(*values)
  values.all? { |value| labels.any? { |label| label["name"] == value } }
end

#labeled_any?(*values) ⇒ Boolean

Returns:

  • (Boolean)


161
162
163
# File 'app/models/github/pull_request.rb', line 161

def labeled_any?(*values)
  values.any? { |value| labels.any? { |label| label["name"] == value } }
end

#labelsObject



165
166
167
# File 'app/models/github/pull_request.rb', line 165

def labels
  json_labels
end

#labels=(value) ⇒ Object



153
154
155
# File 'app/models/github/pull_request.rb', line 153

def labels=(value)
  self.json_labels = value.map { |label| label.to_h.stringify_keys.pick("name", "color") }
end

#merge_attributes(pr) ⇒ Object



203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
# File 'app/models/github/pull_request.rb', line 203

def merge_attributes(pr)
  self.repo = pr["base"]["repo"]["name"] unless repo
  self.number = pr["number"] unless number
  self.username = pr["user"]["login"] unless username
  self.avatar_url = pr["user"]["avatar_url"] unless avatar_url
  self.url = pr["html_url"] unless url
  self.base_ref = pr["base"]["ref"] unless base_ref
  self.head_ref = pr["head"]["ref"] unless head_ref
  self.created_at = pr["created_at"] unless created_at

  self.title = pr["title"]
  self.body = pr["body"]
  self.base_sha = pr["base"]["sha"]
  self.head_sha = pr["head"]["sha"]
  self.labels = pr["labels"] if pr.key?("labels")

  self
end

#publish_commit_status!(status = {}) ⇒ Object



197
198
199
# File 'app/models/github/pull_request.rb', line 197

def publish_commit_status!(status={})
  project.repo.create_commit_status(head_sha, status)
end

#remove_label!(label, options = {}) ⇒ Object



179
180
181
182
183
184
185
186
187
# File 'app/models/github/pull_request.rb', line 179

def remove_label!(label, options={})
  label = label.to_h.stringify_keys.pick("name", "color")

  transaction do
    pr = self.class.lock.find id
    new_labels = pr.json_labels.reject { |l| l["name"] == label["name"] }
    pr.update_attributes! json_labels: new_labels, actor: options[:as]
  end
end

#to_sObject



191
192
193
# File 'app/models/github/pull_request.rb', line 191

def to_s
  "#{repo}##{number}"
end