Class: Dependabot::Clients::Bitbucket

Inherits:
Object
  • Object
show all
Defined in:
lib/dependabot/clients/bitbucket.rb

Defined Under Namespace

Classes: Forbidden, NotFound, TimedOut, Unauthorized

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(credentials:) ⇒ Bitbucket

Client #



35
36
37
38
# File 'lib/dependabot/clients/bitbucket.rb', line 35

def initialize(credentials:)
  @credentials = credentials
  @auth_header = auth_header_for(credentials&.fetch("token", nil))
end

Class Method Details

.for_source(source:, credentials:) ⇒ Object

Constructor methods #



22
23
24
25
26
27
28
29
# File 'lib/dependabot/clients/bitbucket.rb', line 22

def self.for_source(source:, credentials:)
  credential =
    credentials
    .select { |cred| cred["type"] == "git_source" }
    .find { |cred| cred["host"] == source.hostname }

  new(credentials: credential)
end

Instance Method Details

#branch(repo, branch_name) ⇒ Object



78
79
80
81
82
83
# File 'lib/dependabot/clients/bitbucket.rb', line 78

def branch(repo, branch_name)
  branch_path = "#{repo}/refs/branches/#{branch_name}"
  response = get(base_url + branch_path)

  JSON.parse(response.body)
end

#commits(repo, branch_name = nil) ⇒ Object



72
73
74
75
76
# File 'lib/dependabot/clients/bitbucket.rb', line 72

def commits(repo, branch_name = nil)
  commits_path = "#{repo}/commits/#{branch_name}?pagelen=100"
  next_page_url = base_url + commits_path
  paginate({ "next" => next_page_url })
end

#compare(repo, previous_tag, new_tag) ⇒ Object



202
203
204
205
206
207
# File 'lib/dependabot/clients/bitbucket.rb', line 202

def compare(repo, previous_tag, new_tag)
  path = "#{repo}/commits/?include=#{new_tag}&exclude=#{previous_tag}"
  response = get(base_url + path)

  JSON.parse(response.body).fetch("values")
end

#create_commit(repo, branch_name, base_commit, commit_message, files, author_details) ⇒ Object

rubocop:disable Metrics/ParameterLists



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/dependabot/clients/bitbucket.rb', line 107

def create_commit(repo, branch_name, base_commit, commit_message, files,
                  author_details)
  parameters = {
    message: commit_message, # TODO: Format markup in commit message
    author: "#{author_details.fetch(:name)} <#{author_details.fetch(:email)}>",
    parents: base_commit,
    branch: branch_name
  }

  files.each do |file|
    parameters[file.path] = file.content
  end

  body = encode_form_parameters(parameters)

  commit_path = "#{repo}/src"
  post(base_url + commit_path, body, "application/x-www-form-urlencoded")
end

#create_pull_request(repo, pr_name, source_branch, target_branch, pr_description, _labels, _work_item = nil) ⇒ Object

rubocop:disable Metrics/ParameterLists



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/dependabot/clients/bitbucket.rb', line 128

def create_pull_request(repo, pr_name, source_branch, target_branch,
                        pr_description, _labels, _work_item = nil)
  reviewers = default_reviewers(repo)

  content = {
    title: pr_name,
    source: {
      branch: {
        name: source_branch
      }
    },
    destination: {
      branch: {
        name: target_branch
      }
    },
    description: pr_description,
    reviewers: reviewers,
    close_source_branch: true
  }

  pr_path = "#{repo}/pullrequests"
  post(base_url + pr_path, content.to_json)
end

#current_userObject



171
172
173
174
175
176
177
# File 'lib/dependabot/clients/bitbucket.rb', line 171

def current_user
  base_url = "https://api.bitbucket.org/2.0/user?fields=uuid"
  response = get(base_url)
  JSON.parse(response.body).fetch("uuid")
rescue Unauthorized
  [nil]
end

#decline_pull_request(repo, pr_id, comment = nil) ⇒ Object

rubocop:enable Metrics/ParameterLists



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/dependabot/clients/bitbucket.rb', line 154

def decline_pull_request(repo, pr_id, comment = nil)
  # https://developer.atlassian.com/cloud/bitbucket/rest/api-group-pullrequests/
  decline_path = "#{repo}/pullrequests/#{pr_id}/decline"
  post(base_url + decline_path, "")

  comment = "Dependabot declined the pull request." if comment.nil?

  content = {
    content: {
      raw: comment
    }
  }

  comment_path = "#{repo}/pullrequests/#{pr_id}/comments"
  post(base_url + comment_path, content.to_json)
end

#default_reviewers(repo) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/dependabot/clients/bitbucket.rb', line 179

def default_reviewers(repo)
  current_uuid = current_user
  path = "#{repo}/default-reviewers?pagelen=100&fields=values.uuid,next"
  reviewers_url = base_url + path

  default_reviewers = paginate({ "next" => reviewers_url })

  reviewer_data = []

  default_reviewers.each do |reviewer|
    reviewer_data.append({ uuid: reviewer.fetch("uuid") }) unless current_uuid == reviewer.fetch("uuid")
  end

  reviewer_data
end

#fetch_commit(repo, branch) ⇒ Object



40
41
42
43
44
45
# File 'lib/dependabot/clients/bitbucket.rb', line 40

def fetch_commit(repo, branch)
  path = "#{repo}/refs/branches/#{branch}"
  response = get(base_url + path)

  JSON.parse(response.body).fetch("target").fetch("hash")
end

#fetch_default_branch(repo) ⇒ Object



47
48
49
50
51
# File 'lib/dependabot/clients/bitbucket.rb', line 47

def fetch_default_branch(repo)
  response = get(base_url + repo)

  JSON.parse(response.body).fetch("mainbranch").fetch("name")
end

#fetch_file_contents(repo, commit, path) ⇒ Object



65
66
67
68
69
70
# File 'lib/dependabot/clients/bitbucket.rb', line 65

def fetch_file_contents(repo, commit, path)
  path = "#{repo}/src/#{commit}/#{path.gsub(%r{/+$}, '')}"
  response = get(base_url + path)

  response.body
end

#fetch_repo_contents(repo, commit = nil, path = nil) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
# File 'lib/dependabot/clients/bitbucket.rb', line 53

def fetch_repo_contents(repo, commit = nil, path = nil)
  raise "Commit is required if path provided!" if commit.nil? && path

  api_path = "#{repo}/src"
  api_path += "/#{commit}" if commit
  api_path += "/#{path.gsub(%r{/+$}, '')}" if path
  api_path += "?pagelen=100"
  response = get(base_url + api_path)

  JSON.parse(response.body).fetch("values")
end

#get(url) ⇒ Object

Raises:



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
# File 'lib/dependabot/clients/bitbucket.rb', line 209

def get(url)
  response = Excon.get(
    URI::DEFAULT_PARSER.escape(url),
    user: credentials&.fetch("username", nil),
    password: credentials&.fetch("password", nil),
    # Setting to false to prevent Excon retries, use BitbucketWithRetries for retries.
    idempotent: false,
    **Dependabot::SharedHelpers.excon_defaults(
      headers: auth_header
    )
  )
  raise Unauthorized if response.status == 401
  raise Forbidden if response.status == 403
  raise NotFound if response.status == 404

  if response.status >= 400
    raise "Unhandled Bitbucket error!\n" \
          "Status: #{response.status}\n" \
          "Body: #{response.body}"
  end

  response
end

#post(url, body, content_type = "application/json") ⇒ Object

Raises:



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/dependabot/clients/bitbucket.rb', line 233

def post(url, body, content_type = "application/json")
  headers = auth_header

  headers = if body.empty?
              headers.merge({ "Accept" => "application/json" })
            else
              headers.merge({ "Content-Type" => content_type })
            end

  response = Excon.post(
    url,
    body: body,
    user: credentials&.fetch("username", nil),
    password: credentials&.fetch("password", nil),
    idempotent: false,
    **SharedHelpers.excon_defaults(
      headers: headers
    )
  )
  raise Unauthorized if response.status == 401
  raise Forbidden if response.status == 403
  raise NotFound if response.status == 404
  raise TimedOut if response.status == 555

  response
end

#pull_requests(repo, source_branch, target_branch, status = %w(OPEN MERGED DECLINED SUPERSEDED))) ⇒ Object



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/dependabot/clients/bitbucket.rb', line 85

def pull_requests(repo, source_branch, target_branch, status = %w(OPEN MERGED DECLINED SUPERSEDED))
  pr_path = "#{repo}/pullrequests?"
  # Get pull requests with given status
  status.each { |n| pr_path += "status=#{n}&" }
  next_page_url = base_url + pr_path
  pull_requests = paginate({ "next" => next_page_url })

  pull_requests unless source_branch && target_branch

  pull_requests.select do |pr|
    if source_branch.nil?
      source_branch_matches = true
    else
      pr_source_branch = pr.fetch("source").fetch("branch").fetch("name")
      source_branch_matches = pr_source_branch == source_branch
    end
    pr_target_branch = pr.fetch("destination").fetch("branch").fetch("name")
    source_branch_matches && pr_target_branch == target_branch
  end
end

#tags(repo) ⇒ Object



195
196
197
198
199
200
# File 'lib/dependabot/clients/bitbucket.rb', line 195

def tags(repo)
  path = "#{repo}/refs/tags?pagelen=100"
  response = get(base_url + path)

  JSON.parse(response.body).fetch("values")
end