Class: GitReflow::GitServer::PullRequest

Inherits:
Object
  • Object
show all
Defined in:
lib/git_reflow/git_server/pull_request.rb

Direct Known Subclasses

BitBucket::PullRequest, GitHub::PullRequest

Defined Under Namespace

Classes: Build

Constant Summary collapse

DEFAULT_APPROVAL_REGEX =
/(?i-mx:lgtm|looks good to me|:\+1:|:thumbsup:|:shipit:)/

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(attributes) ⇒ PullRequest

Returns a new instance of PullRequest.



30
31
32
# File 'lib/git_reflow/git_server/pull_request.rb', line 30

def initialize(attributes)
  raise "PullRequest#initialize must be implemented"
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method_sym, *arguments, &block) ⇒ Object



152
153
154
155
156
157
158
# File 'lib/git_reflow/git_server/pull_request.rb', line 152

def method_missing(method_sym, *arguments, &block)
  if source_object and source_object.respond_to? method_sym
    source_object.send method_sym
  else
    super
  end
end

Instance Attribute Details

#base_branch_nameObject

Returns the value of attribute base_branch_name.



4
5
6
# File 'lib/git_reflow/git_server/pull_request.rb', line 4

def base_branch_name
  @base_branch_name
end

#buildObject

Returns the value of attribute build.



4
5
6
# File 'lib/git_reflow/git_server/pull_request.rb', line 4

def build
  @build
end

#descriptionObject

Returns the value of attribute description.



4
5
6
# File 'lib/git_reflow/git_server/pull_request.rb', line 4

def description
  @description
end

#feature_branch_nameObject

Returns the value of attribute feature_branch_name.



4
5
6
# File 'lib/git_reflow/git_server/pull_request.rb', line 4

def feature_branch_name
  @feature_branch_name
end

#html_urlObject

Returns the value of attribute html_url.



4
5
6
# File 'lib/git_reflow/git_server/pull_request.rb', line 4

def html_url
  @html_url
end

#numberObject

Returns the value of attribute number.



4
5
6
# File 'lib/git_reflow/git_server/pull_request.rb', line 4

def number
  @number
end

#source_objectObject

Returns the value of attribute source_object.



4
5
6
# File 'lib/git_reflow/git_server/pull_request.rb', line 4

def source_object
  @source_object
end

Class Method Details

.approval_regexObject



22
23
24
25
26
27
28
# File 'lib/git_reflow/git_server/pull_request.rb', line 22

def self.approval_regex
  if "#{GitReflow::Config.get('constants.approvalRegex')}".length > 0
    Regexp.new("#{GitReflow::Config.get('constants.approvalRegex')}")
  else
    DEFAULT_APPROVAL_REGEX
  end
end

.minimum_approvalsObject



18
19
20
# File 'lib/git_reflow/git_server/pull_request.rb', line 18

def self.minimum_approvals
  "#{GitReflow::Config.get('constants.minimumApprovals')}"
end

Instance Method Details

#all_comments_addressed?Boolean

Returns:

  • (Boolean)


99
100
101
# File 'lib/git_reflow/git_server/pull_request.rb', line 99

def all_comments_addressed?
  self.class.minimum_approvals.length <= 0 or !last_comment.match(self.class.approval_regex).nil?
end

#approval_minimums_reached?Boolean

Returns:

  • (Boolean)


95
96
97
# File 'lib/git_reflow/git_server/pull_request.rb', line 95

def approval_minimums_reached?
  self.class.minimum_approvals.length <= 0 or approvals.size >= self.class.minimum_approvals.to_i
end

#approvalsObject



54
55
56
# File 'lib/git_reflow/git_server/pull_request.rb', line 54

def approvals
  raise "#{self.class.to_s}#approvals method must be implemented"
end

#approved?Boolean

Returns:

  • (Boolean)


62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/git_reflow/git_server/pull_request.rb', line 62

def approved?
  has_comments_or_approvals = (has_comments? or approvals.any?)

  case self.class.minimum_approvals
  when "0"
    true
  when "", nil
    # Approvals from every commenter
    has_comments_or_approvals && reviewers_pending_response.empty?
  else
    approvals.size >= self.class.minimum_approvals.to_i
  end
end

#build_statusObject



76
77
78
# File 'lib/git_reflow/git_server/pull_request.rb', line 76

def build_status
  build.nil? ? nil : build.state
end

#cleanup_failure_messageObject



202
203
204
205
# File 'lib/git_reflow/git_server/pull_request.rb', line 202

def cleanup_failure_message
  GitReflow.say "Cleanup halted.  Local changes were not pushed to remote repo.", :deliver_halted
  GitReflow.say "To reset and go back to your branch run \`git reset --hard origin/#{self.base_branch_name} && git checkout #{self.feature_branch_name}\`"
end

#cleanup_feature_branch?Boolean

Returns:

  • (Boolean)


180
181
182
# File 'lib/git_reflow/git_server/pull_request.rb', line 180

def cleanup_feature_branch?
  cleanup_local_feature_branch? || cleanup_remote_feature_branch?
end

#cleanup_local_feature_branch?Boolean

Returns:

  • (Boolean)


184
185
186
187
188
189
# File 'lib/git_reflow/git_server/pull_request.rb', line 184

def cleanup_local_feature_branch?
  # backwards compat
  always_cleanup_local = GitReflow::Config.get('reflow.always-cleanup').to_s
  always_cleanup_local = GitReflow::Config.get('reflow.always-cleanup-local') if always_cleanup_local.empty?
  always_cleanup_local == "true" || (ask "Would you like to cleanup your local feature branch? ") =~ /^y/i
end

#cleanup_remote_feature_branch?Boolean

Returns:

  • (Boolean)


191
192
193
194
195
196
# File 'lib/git_reflow/git_server/pull_request.rb', line 191

def cleanup_remote_feature_branch?
  # backwards compat
  always_cleanup_remote = GitReflow::Config.get('reflow.always-cleanup').to_s
  always_cleanup_remote = GitReflow::Config.get('reflow.always-cleanup-remote') if always_cleanup_remote.empty?
  always_cleanup_remote == "true" || (ask "Would you like to cleanup your remote feature branch? ") =~ /^y/i
end

#commentsObject



38
39
40
# File 'lib/git_reflow/git_server/pull_request.rb', line 38

def comments
  raise "#{self.class.to_s}#comments method must be implemented"
end

#commit_authorObject



34
35
36
# File 'lib/git_reflow/git_server/pull_request.rb', line 34

def commit_author
  raise "#{self.class.to_s}#commit_author method must be implemented"
end

#commit_message_for_mergeObject



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/git_reflow/git_server/pull_request.rb', line 160

def commit_message_for_merge
  return GitReflow.merge_commit_template unless GitReflow.merge_commit_template.nil?

  message = ""

  if "#{self.description}".length > 0
    message << "#{self.description}"
  else
    message << "#{GitReflow.get_first_commit_message}"
  end

  message << "\nMerges ##{self.number}\n"

  if lgtm_authors = Array(self.approvals) and lgtm_authors.any?
    message << "\nLGTM given by: @#{lgtm_authors.join(', @')}\n"
  end

  "#{message}\n"
end

#deliver?Boolean

Returns:

  • (Boolean)


198
199
200
# File 'lib/git_reflow/git_server/pull_request.rb', line 198

def deliver?
  GitReflow::Config.get('reflow.always-deliver') == "true" || (ask "This is the current status of your Pull Request. Are you sure you want to deliver? ") =~ /^y/i
end

#display_pull_request_summaryObject



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/git_reflow/git_server/pull_request.rb', line 109

def display_pull_request_summary
  summary_data = {
    "branches"    => "#{self.feature_branch_name} -> #{self.base_branch_name}",
    "number"      => self.number,
    "url"         => self.html_url
  }

  notices = []
  reviewed_by = []

  # check for CI build status
  if self.build_status
    notices << "Your build status is not successful: #{self.build.url}.\n" unless self.build.state == "success"
    summary_data.merge!( "Build status" => GitReflow.git_server.colorized_build_description(self.build.state, self.build.description) )
  end

  # check for needed lgtm's
  if self.reviewers.any?
    reviewed_by = self.reviewers.map {|author| author.colorize(:red) }
    summary_data.merge!("Last comment"  => self.last_comment)

    if self.approvals.any?
      reviewed_by.map! { |author| approvals.include?(author.uncolorize) ? author.colorize(:green) : author }
    end

    notices << "You still need a LGTM from: #{reviewers_pending_response.join(', ')}\n" if reviewers_pending_response.any?
  else
    notices << "No one has reviewed your pull request.\n"
  end

  summary_data['reviewed by'] = reviewed_by.join(', ')

  padding_size = summary_data.keys.max_by(&:size).size + 2
  summary_data.keys.sort.each do |name|
    string_format = "    %-#{padding_size}s %s\n"
    printf string_format, "#{name}:", summary_data[name]
  end

  notices.each do |notice|
    GitReflow.say notice, :notice
  end
end

#good_to_merge?(force: false) ⇒ Boolean

Returns:

  • (Boolean)


103
104
105
106
107
# File 'lib/git_reflow/git_server/pull_request.rb', line 103

def good_to_merge?(force: false)
  return true if force

  (build_status.nil? or build_status == "success") and approved?
end

#has_comments?Boolean

Returns:

  • (Boolean)


42
43
44
# File 'lib/git_reflow/git_server/pull_request.rb', line 42

def has_comments?
  comments.count > 0
end

#last_commentObject



46
47
48
# File 'lib/git_reflow/git_server/pull_request.rb', line 46

def last_comment
  raise "#{self.class.to_s}#last_comment method must be implemented"
end

#merge!(options = {}) ⇒ Object



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/git_reflow/git_server/pull_request.rb', line 207

def merge!(options = {})
  if deliver?

    GitReflow.say "Merging pull request ##{self.number}: '#{self.title}', from '#{self.feature_branch_name}' into '#{self.base_branch_name}'", :notice

    GitReflow.update_current_branch
    GitReflow.fetch_destination(self.base_branch_name)

    message      = commit_message_for_merge
    merge_method = options[:merge_method] || GitReflow::Config.get("reflow.merge-method")
    merge_method = "squash" if "#{merge_method}".length < 1


    GitReflow.run_command_with_label "git checkout #{self.base_branch_name}"
    GitReflow.run_command_with_label "git pull origin #{self.base_branch_name}"

    case merge_method.to_s
    when /squash/i
      GitReflow.run_command_with_label "git merge --squash #{self.feature_branch_name}"
    else
      GitReflow.run_command_with_label "git merge #{self.feature_branch_name}"
    end

    GitReflow.append_to_merge_commit_message(message) if message.length > 0

    if GitReflow.run_command_with_label 'git commit', with_system: true
      GitReflow.say "Pull request ##{self.number} successfully merged.", :success

      if cleanup_feature_branch?
        GitReflow.run_command_with_label "git push origin #{self.base_branch_name}"
        GitReflow.run_command_with_label "git push origin :#{self.feature_branch_name}"
        GitReflow.run_command_with_label "git branch -D #{self.feature_branch_name}"
        GitReflow.say "Nice job buddy."
      else
        cleanup_failure_message
      end
    else
      GitReflow.say "There were problems commiting your feature... please check the errors above and try again.", :error
    end
  else
    GitReflow.say "Merge aborted", :deliver_halted
  end
end

#rejection_messageObject



80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/git_reflow/git_server/pull_request.rb', line 80

def rejection_message
  if !build_status.nil? and build_status != "success"
    "#{build.description}: #{build.url}"
  elsif !approval_minimums_reached?
    "You need approval from at least #{self.class.minimum_approvals} users!"
  elsif !all_comments_addressed?
    # Maybe add what the last comment is?
    "The last comment is holding up approval:\n#{last_comment}"
  elsif reviewers_pending_response.count > 0
    "You still need a LGTM from: #{reviewers_pending_response.join(', ')}"
  else
    "Your code has not been reviewed yet."
  end
end

#reviewersObject



50
51
52
# File 'lib/git_reflow/git_server/pull_request.rb', line 50

def reviewers
  raise "#{self.class.to_s}#reviewers method must be implemented"
end

#reviewers_pending_responseObject



58
59
60
# File 'lib/git_reflow/git_server/pull_request.rb', line 58

def reviewers_pending_response
  reviewers - approvals
end