gergich 0.1.15 → 0.1.16
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +8 -6
- data/bin/run_tests.sh +11 -7
- data/lib/gergich.rb +108 -27
- data/lib/gergich/cli/master_bouncer.rb +4 -4
- data/spec/gergich_spec.rb +151 -13
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3c315c6166bed8d6a67b00c7356fce017a579139
|
4
|
+
data.tar.gz: 4c9825057793fa28ad9970a73ed2c68cae76f41a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 967db7d75cd976e2aa3ebe6413a9063eba44e7a418892c5565837f0a96b83ba449829fbfff57a9cf53b4bdc521357edf51025280135770e6f535b892c162db62
|
7
|
+
data.tar.gz: 7cf0feb214e120c58df7d33468bd27c156182f6f2e72fe142249258f64ca796a92b73fea2d277227e49b134166b56070788fc3ae870fb69bac94de0b2967b335
|
data/README.md
CHANGED
@@ -22,12 +22,14 @@ Gergich publishes the review to Gerrit.
|
|
22
22
|
## Limitations
|
23
23
|
|
24
24
|
Because everything is synchronized/stored in a local sqlite db, you
|
25
|
-
should only call Gergich from a single box/build per patchset
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
should only call Gergich from a single box/build per patchset unless you
|
26
|
+
have a unique `GERGICH_COMMENT_PREFIX` set for each box/build per patchset.
|
27
|
+
Gergich does a check when publishing to ensure he hasn't already posted on
|
28
|
+
this patchset before (w/ the same `GERGICH_COMMENT_PREFIX`); if he has,
|
29
|
+
publish will be a no-op. This protects against reposts (say, on a retrigger),
|
30
|
+
but it does mean that you shouldn't have completely different builds posting
|
31
|
+
Gergich comments on the same revision, unless you set up different
|
32
|
+
credentials for each.
|
31
33
|
|
32
34
|
## Installation
|
33
35
|
|
data/bin/run_tests.sh
CHANGED
@@ -35,17 +35,21 @@ run_command bundle exec rubocop
|
|
35
35
|
export COVERAGE=1
|
36
36
|
|
37
37
|
run_command bundle exec rspec
|
38
|
-
run_command bin/gergich citest
|
39
|
-
run_command bin/master_bouncer check
|
40
|
-
DRY_RUN=1 run_command bin/master_bouncer check_all
|
41
38
|
|
42
|
-
#
|
43
|
-
|
44
|
-
|
39
|
+
# these actually hit gerrit; only run them in CI land (you can do it
|
40
|
+
# locally if you set all the docker-compose env vars)
|
41
|
+
if [[ "$GERRIT_PATCHSET_REVISION" ]]; then
|
42
|
+
run_command bin/gergich citest
|
43
|
+
run_command bin/master_bouncer check
|
44
|
+
DRY_RUN=1 run_command bin/master_bouncer check_all
|
45
|
+
# ensure gergich works without .git directories
|
46
|
+
rm -rf .git
|
47
|
+
run_command bin/gergich status
|
48
|
+
fi
|
45
49
|
|
46
50
|
run_command bin/check_coverage
|
47
51
|
|
48
|
-
if [[ $GEMNASIUM_TOKEN && $GEMNASIUM_ENABLED ]]; then
|
52
|
+
if [[ "$GEMNASIUM_TOKEN" && "$GEMNASIUM_ENABLED" ]]; then
|
49
53
|
# Push our dependency specification files to gemnasium for analysis
|
50
54
|
run_command gemnasium dependency_files push -f=gergich.gemspec
|
51
55
|
fi
|
data/lib/gergich.rb
CHANGED
@@ -42,7 +42,10 @@ module Gergich
|
|
42
42
|
change_id = ENV["GERRIT_CHANGE_ID"] \
|
43
43
|
|| raise(GergichError, "No .git directory, and GERRIT_CHANGE_ID not set")
|
44
44
|
end
|
45
|
-
|
45
|
+
project = ENV["GERRIT_PROJECT"]
|
46
|
+
branch = ENV["GERRIT_BRANCH"]
|
47
|
+
|
48
|
+
{ revision_id: revision_id, change_id: change_id, project: project, branch: branch }
|
46
49
|
end
|
47
50
|
end
|
48
51
|
|
@@ -65,14 +68,22 @@ module Gergich
|
|
65
68
|
|
66
69
|
def revision_number
|
67
70
|
@revision_number ||= begin
|
71
|
+
patchset_number = ENV["GERRIT_PATCHSET_NUMBER"]
|
72
|
+
return patchset_number unless patchset_number.nil?
|
73
|
+
|
68
74
|
gerrit_info = API.get("/changes/?q=#{change_id}&o=ALL_REVISIONS")[0]
|
69
75
|
raise GergichError, "Gerrit patchset not found" unless gerrit_info
|
76
|
+
|
70
77
|
gerrit_info["revisions"][revision_id]["_number"]
|
71
78
|
end
|
72
79
|
end
|
73
80
|
|
74
81
|
def change_id
|
75
|
-
info[:
|
82
|
+
if info[:project] && info[:branch]
|
83
|
+
"#{info[:project]}~#{info[:branch]}~#{info[:change_id]}"
|
84
|
+
else
|
85
|
+
info[:change_id]
|
86
|
+
end
|
76
87
|
end
|
77
88
|
end
|
78
89
|
|
@@ -87,10 +98,8 @@ module Gergich
|
|
87
98
|
# Public: publish all draft comments/labels/messages
|
88
99
|
def publish!(allow_repost = false)
|
89
100
|
# only publish if we have something to say or if our last score was negative
|
90
|
-
return unless anything_to_publish?
|
101
|
+
return unless anything_to_publish?
|
91
102
|
|
92
|
-
# TODO: rather than just bailing, fetch the comments and only post
|
93
|
-
# ones that don't exist (if any)
|
94
103
|
return if already_commented? && !allow_repost
|
95
104
|
|
96
105
|
API.post(generate_url, generate_payload)
|
@@ -109,8 +118,24 @@ module Gergich
|
|
109
118
|
|
110
119
|
def anything_to_publish?
|
111
120
|
!review_info[:comments].empty? ||
|
112
|
-
!review_info[:
|
113
|
-
|
121
|
+
!review_info[:cover_message_parts].empty? ||
|
122
|
+
new_score?
|
123
|
+
end
|
124
|
+
|
125
|
+
def new_score?
|
126
|
+
if current_label_is_for_current_revision?
|
127
|
+
review_info[:score] < current_score.to_i
|
128
|
+
else
|
129
|
+
true
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def upcoming_score
|
134
|
+
if current_label_is_for_current_revision?
|
135
|
+
[current_score.to_i, review_info[:score]].min
|
136
|
+
else
|
137
|
+
review_info[:score]
|
138
|
+
end
|
114
139
|
end
|
115
140
|
|
116
141
|
# Public: show the current draft for this patchset
|
@@ -133,7 +158,7 @@ module Gergich
|
|
133
158
|
|
134
159
|
puts
|
135
160
|
puts "Cover Message:"
|
136
|
-
puts
|
161
|
+
puts cover_message
|
137
162
|
|
138
163
|
return if review_info[:comments].empty?
|
139
164
|
|
@@ -148,24 +173,28 @@ module Gergich
|
|
148
173
|
end
|
149
174
|
end
|
150
175
|
|
151
|
-
def
|
152
|
-
|
153
|
-
|
154
|
-
.last
|
155
|
-
|
156
|
-
text = last_message && last_message["message"] || ""
|
157
|
-
text =~ /^-[12]/
|
158
|
-
|
159
|
-
($& || "").to_i
|
176
|
+
def multi_build_setup?
|
177
|
+
# convert to boolean if this variable exists or not
|
178
|
+
!ENV["GERGICH_COMMENT_PREFIX"].nil?
|
160
179
|
end
|
161
180
|
|
162
|
-
def
|
163
|
-
|
181
|
+
def unique_comment_prefix
|
182
|
+
ENV["GERGICH_COMMENT_PREFIX"]
|
164
183
|
end
|
165
184
|
|
166
185
|
def already_commented?
|
186
|
+
if multi_build_setup?
|
187
|
+
my_messages_on_current_revision.any? do |message|
|
188
|
+
message["message"] =~ /^#{unique_comment_prefix}/
|
189
|
+
end
|
190
|
+
else
|
191
|
+
my_messages_on_current_revision.any?
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def my_messages_on_current_revision
|
167
196
|
revision_number = commit.revision_number
|
168
|
-
my_messages.
|
197
|
+
my_messages.select { |message| message["_revision_number"] == revision_number }
|
169
198
|
end
|
170
199
|
|
171
200
|
def my_messages
|
@@ -173,6 +202,61 @@ module Gergich
|
|
173
202
|
.select { |message| message["author"] && message["author"]["username"] == GERGICH_USER }
|
174
203
|
end
|
175
204
|
|
205
|
+
# currently, cover message only supports the GERGICH_REVIEW_LABEL.
|
206
|
+
# i.e., even if gergich has "Code-Review: -2"
|
207
|
+
def current_label
|
208
|
+
@current_label ||= begin
|
209
|
+
API.get("/changes/#{commit.change_id}/detail")["labels"]
|
210
|
+
.fetch(GERGICH_REVIEW_LABEL, {})
|
211
|
+
.fetch("all", [])
|
212
|
+
.select { |label| label["username"] == GERGICH_USER }
|
213
|
+
.first
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def current_label_date
|
218
|
+
@current_label_date ||= current_label && current_label["date"]
|
219
|
+
end
|
220
|
+
|
221
|
+
# unfortunately, the revision is not a field in the label json.
|
222
|
+
# however, we can match the label timestamp w/ one of our comment timestamps,
|
223
|
+
# then grab the comment's revision.
|
224
|
+
def current_label_revision
|
225
|
+
@current_label_revision ||= begin
|
226
|
+
date = current_label_date
|
227
|
+
comment_for_current_label = my_messages.find { |message| message["date"] == date } ||
|
228
|
+
my_messages.last
|
229
|
+
comment_for_current_label["_revision_number"]
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def current_label_is_for_current_revision?
|
234
|
+
return false unless current_label
|
235
|
+
current_label_revision == commit.revision_number
|
236
|
+
end
|
237
|
+
|
238
|
+
def current_score
|
239
|
+
current_label && current_label["value"] || 0
|
240
|
+
end
|
241
|
+
|
242
|
+
def cover_message
|
243
|
+
parts = review_info[:cover_message_parts]
|
244
|
+
prefix = cover_message_prefix
|
245
|
+
parts.unshift prefix if prefix != ""
|
246
|
+
parts.join("\n\n")
|
247
|
+
end
|
248
|
+
|
249
|
+
def cover_message_prefix
|
250
|
+
score = upcoming_score
|
251
|
+
prefix_parts = []
|
252
|
+
prefix_parts << unique_comment_prefix if multi_build_setup?
|
253
|
+
prefix_parts << score if score < 0
|
254
|
+
prefix_parts.join(":")
|
255
|
+
# [].join(":") => ""
|
256
|
+
# [-2].join(":") => "-2"
|
257
|
+
# ["some build prefix", -2].join(":") => "some build prefix:-2"
|
258
|
+
end
|
259
|
+
|
176
260
|
def whats_his_face
|
177
261
|
"#{%w[Garry Larry Terry Jerry].sample} Gergich (Bot)"
|
178
262
|
end
|
@@ -187,7 +271,7 @@ module Gergich
|
|
187
271
|
|
188
272
|
def generate_payload
|
189
273
|
{
|
190
|
-
message:
|
274
|
+
message: cover_message,
|
191
275
|
labels: review_info[:labels],
|
192
276
|
comments: review_info[:comments],
|
193
277
|
# we don't want the post to fail if another
|
@@ -434,7 +518,7 @@ module Gergich
|
|
434
518
|
|
435
519
|
{
|
436
520
|
comments: comments,
|
437
|
-
|
521
|
+
cover_message_parts: cover_message_parts,
|
438
522
|
total_comments: all_comments.map(&:count).inject(&:+),
|
439
523
|
score: labels[GERGICH_REVIEW_LABEL],
|
440
524
|
labels: labels
|
@@ -464,13 +548,10 @@ module Gergich
|
|
464
548
|
message
|
465
549
|
end
|
466
550
|
|
467
|
-
def
|
468
|
-
score = labels[GERGICH_REVIEW_LABEL]
|
551
|
+
def cover_message_parts
|
469
552
|
parts = messages
|
470
|
-
parts.unshift score.to_s if score < 0
|
471
|
-
|
472
553
|
parts << orphaned_message unless other_comments.empty?
|
473
|
-
parts
|
554
|
+
parts
|
474
555
|
end
|
475
556
|
end
|
476
557
|
|
@@ -42,15 +42,15 @@ def maybe_bounce_commit!(commit)
|
|
42
42
|
end
|
43
43
|
|
44
44
|
review = Gergich::Review.new(commit, draft)
|
45
|
-
|
45
|
+
current_score = review.current_score
|
46
46
|
|
47
|
-
puts "#{detail}, " + (score ==
|
47
|
+
puts "#{detail}, " + (score == current_score ?
|
48
48
|
"score still #{score}" :
|
49
|
-
"changing score from #{
|
49
|
+
"changing score from #{current_score} to #{score}")
|
50
50
|
|
51
51
|
# since we run on a daily cron, we might be checking the same patchset
|
52
52
|
# many times, so bail if nothing has changed
|
53
|
-
return if score ==
|
53
|
+
return if score == current_score
|
54
54
|
|
55
55
|
draft.add_label MASTER_BOUNCER_REVIEW_LABEL, score
|
56
56
|
draft.add_message message if message
|
data/spec/gergich_spec.rb
CHANGED
@@ -8,6 +8,8 @@ RSpec.describe Gergich::API do
|
|
8
8
|
end
|
9
9
|
|
10
10
|
it "provides helpful error when Change-Id not found" do
|
11
|
+
# Get ride of CI_TEST_RUN environment variable so the api preforms normally
|
12
|
+
ENV["CI_TEST_RUN"] = nil
|
11
13
|
expect { described_class.get("/a/changes/1234") }
|
12
14
|
.to raise_error(/Cannot find Change-Id: 1234/)
|
13
15
|
end
|
@@ -90,23 +92,29 @@ RSpec.describe Gergich::Draft do
|
|
90
92
|
end
|
91
93
|
end
|
92
94
|
|
93
|
-
describe "[:
|
94
|
-
subject { super()[:
|
95
|
+
describe "[:cover_message_parts]" do
|
96
|
+
subject { super()[:cover_message_parts] }
|
97
|
+
let(:message_1) { "this is good" }
|
98
|
+
let(:message_2) { "loljk it's terrible" }
|
95
99
|
|
96
|
-
it "includes
|
97
|
-
draft.
|
98
|
-
|
99
|
-
end
|
100
|
+
it "includes explicitly added messages" do
|
101
|
+
draft.add_message message_1
|
102
|
+
draft.add_message message_2
|
100
103
|
|
101
|
-
|
102
|
-
|
103
|
-
expect(subject).to_not match(/^0/)
|
104
|
+
expect(subject).to include(message_1)
|
105
|
+
expect(subject).to include(message_2)
|
104
106
|
end
|
105
107
|
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
108
|
+
context "orphaned file comments exist" do
|
109
|
+
let(:orphaned_comment) { "fix invalid" }
|
110
|
+
|
111
|
+
before :each do
|
112
|
+
draft.add_comment "invalid.rb", 1, orphaned_comment, "info"
|
113
|
+
end
|
114
|
+
|
115
|
+
it "includes orphan file message" do
|
116
|
+
expect(subject.first).to match(/#{orphaned_comment}/)
|
117
|
+
end
|
110
118
|
end
|
111
119
|
end
|
112
120
|
|
@@ -182,3 +190,133 @@ RSpec.describe Gergich::Draft do
|
|
182
190
|
end
|
183
191
|
end
|
184
192
|
end
|
193
|
+
|
194
|
+
RSpec.describe Gergich::Review do
|
195
|
+
let!(:commit) do
|
196
|
+
double(
|
197
|
+
:commit,
|
198
|
+
files: [
|
199
|
+
"foo.rb",
|
200
|
+
"bar/baz.lol"
|
201
|
+
],
|
202
|
+
revision_id: "test",
|
203
|
+
revision_number: 1,
|
204
|
+
change_id: "test"
|
205
|
+
)
|
206
|
+
end
|
207
|
+
let!(:draft) do
|
208
|
+
Gergich::Draft.new commit
|
209
|
+
end
|
210
|
+
let!(:review) { described_class.new(commit, draft) }
|
211
|
+
|
212
|
+
after do
|
213
|
+
draft.reset!
|
214
|
+
end
|
215
|
+
|
216
|
+
describe "#publish!" do
|
217
|
+
context "nothing to publish" do
|
218
|
+
before :each do
|
219
|
+
allow(review).to receive(:anything_to_publish?).and_return(false)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "does nothing" do
|
223
|
+
expect(Gergich::API).not_to receive(:post)
|
224
|
+
|
225
|
+
review.publish!
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
context "something to publish" do
|
230
|
+
before :each do
|
231
|
+
allow(review).to receive(:anything_to_publish?).and_return(true)
|
232
|
+
allow(review).to receive(:already_commented?).and_return(false)
|
233
|
+
allow(review).to receive(:generate_payload).and_return({})
|
234
|
+
end
|
235
|
+
|
236
|
+
it "publishes via the api" do
|
237
|
+
expect(Gergich::API).to receive(:post)
|
238
|
+
allow(review).to receive(:change_name?).and_return(false)
|
239
|
+
review.publish!
|
240
|
+
end
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
describe "#anything_to_publish?" do
|
245
|
+
before :each do
|
246
|
+
allow(review).to receive(:current_label).and_return("BAHA")
|
247
|
+
allow(review).to receive(:current_label_revision).and_return("Revision trash stuff")
|
248
|
+
end
|
249
|
+
|
250
|
+
context "no comments exist" do
|
251
|
+
it "returns false" do
|
252
|
+
allow(review).to receive(:new_score?).and_return(false)
|
253
|
+
expect(review.anything_to_publish?).to eq false
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
context "comments exist" do
|
258
|
+
it "returns true" do
|
259
|
+
draft.info[:comments] = "Hello there this is a comment"
|
260
|
+
expect(review.anything_to_publish?).to eq true
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
describe "#new_score?" do
|
266
|
+
before :each do
|
267
|
+
allow(review).to receive(:current_label_is_for_current_revision?).and_return(true)
|
268
|
+
allow(review).to receive(:current_score).and_return(0)
|
269
|
+
end
|
270
|
+
|
271
|
+
context "score is the same" do
|
272
|
+
it "returns false" do
|
273
|
+
draft.info[:score] = 0
|
274
|
+
expect(review.new_score?).to eq false
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
context "score is different" do
|
279
|
+
it "returns true" do
|
280
|
+
draft.info[:score] = -1
|
281
|
+
expect(review.new_score?).to eq true
|
282
|
+
end
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
describe "#upcoming_score" do
|
287
|
+
context "current_label_is_for_current_revision? is true" do
|
288
|
+
it "Should return the min value of draft.info[:score] and current_score" do
|
289
|
+
allow(review).to receive(:current_label_is_for_current_revision?).and_return(true)
|
290
|
+
allow(review).to receive(:current_score).and_return(0)
|
291
|
+
review.draft.info[:score] = 1
|
292
|
+
expect(review.upcoming_score).to eq 0
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
context "current_label_is_for_current_revision? is false" do
|
297
|
+
it "Should return the value of draft.info[:score]" do
|
298
|
+
allow(review).to receive(:current_label_is_for_current_revision?).and_return(false)
|
299
|
+
review.draft.info[:score] = 1
|
300
|
+
expect(review.upcoming_score).to eq 1
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
describe "#cover_message" do
|
306
|
+
context "score is negative" do
|
307
|
+
it "includes the Code-Review score if negative" do
|
308
|
+
allow(review).to receive(:upcoming_score).and_return(-1)
|
309
|
+
review.draft.add_label "Code-Review", -1
|
310
|
+
expect(review.cover_message).to match(/^-1/)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
context "score is non-negative" do
|
315
|
+
it "doesn't include the score if not negative" do
|
316
|
+
allow(review).to receive(:upcoming_score).and_return(0)
|
317
|
+
draft.add_label "Code-Review", 0
|
318
|
+
expect(subject).to_not match(/^0/)
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gergich
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.16
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jon Jensen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-05
|
11
|
+
date: 2017-09-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sqlite3
|
@@ -155,7 +155,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
155
155
|
version: '0'
|
156
156
|
requirements: []
|
157
157
|
rubyforge_project:
|
158
|
-
rubygems_version: 2.
|
158
|
+
rubygems_version: 2.6.11
|
159
159
|
signing_key:
|
160
160
|
specification_version: 4
|
161
161
|
summary: Command-line tool for adding Gerrit comments
|