gergich 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,109 @@
1
+ require_relative "../cli"
2
+
3
+ ENV["GERGICH_USER"] = ENV.fetch("MASTER_BOUNCER_USER", "master_bouncer")
4
+ ENV["GERGICH_KEY"] = ENV["MASTER_BOUNCER_KEY"] || error("no MASTER_BOUNCER_KEY set")
5
+
6
+ require_relative "../../gergich"
7
+
8
+ PROJECT = ENV["GERRIT_PROJECT"] || error("no GERRIT_PROJECT set")
9
+ # TODO: configurable thresholds per-project, also time-based thresholds
10
+ WARN_DISTANCE = 100
11
+ ERROR_DISTANCE = 200
12
+
13
+ def potentially_mergeable_changes
14
+ url = "/changes/?q=status:open+" \
15
+ "p:#{PROJECT}+" \
16
+ "label:Verified=1+" \
17
+ "is:mergeable+" \
18
+ "branch:master" \
19
+ "&o=CURRENT_REVISION"
20
+ changes = Gergich::API.get(url)
21
+ changes.select { |c| c["subject"] !~ /\Awip($|\W)/i }
22
+ end
23
+
24
+ def maybe_bounce_commit!(commit)
25
+ draft = Gergich::Draft.new(commit)
26
+ draft.reset!
27
+
28
+ distance = Gergich.git("rev-list origin/master ^#{commit.ref} --count").to_i
29
+ detail = "#{distance} commits behind master"
30
+
31
+ score = 0
32
+ message = nil
33
+ if distance > ERROR_DISTANCE
34
+ score = -2
35
+ message = "This commit is probably not safe to merge (#{detail}). You'll " \
36
+ "need to rebase it to ensure all the tests still pass."
37
+ elsif distance > WARN_DISTANCE
38
+ score = -1
39
+ message = "This commit may not be safe to merge (#{detail}). Please " \
40
+ "rebase to make sure all the tests still pass."
41
+ end
42
+
43
+ review = Gergich::Review.new(commit, draft)
44
+ previous_score = review.previous_score
45
+
46
+ puts "#{detail}, " + (score == previous_score ?
47
+ "score still #{score}" :
48
+ "changing score from #{previous_score} to #{score}")
49
+
50
+ # since we run on a daily cron, we might be checking the same patchset
51
+ # many times, so bail if nothing has changed
52
+ return if score == previous_score
53
+
54
+ draft.add_label "Code-Review", score
55
+ draft.add_message message if message
56
+
57
+ # otherwise we always publish ... even in the score=0 case it's
58
+ # important, as we might be undoing a previous negative score.
59
+ # similarly, over time the same patchset will become more out of date,
60
+ # so we allow_repost (so to speak) so we can add increasingly negative
61
+ # reviews
62
+ review.publish!(:allow_repost)
63
+ end
64
+
65
+ commands = {}
66
+
67
+ commands["check"] = {
68
+ summary: "Check the current commit's age",
69
+ action: ->(_args) {
70
+ maybe_bounce_commit! Gergich::Commit.new
71
+ },
72
+ help: ->() {
73
+ <<-TEXT
74
+ master_bouncer check
75
+
76
+ Check the current commit's age, and bounce it if it's too old (-1 or -2,
77
+ depending on the threshold)
78
+ TEXT
79
+ }
80
+ }
81
+
82
+ commands["check_all"] = {
83
+ summary: "Check the age of all potentially mergeable changes",
84
+ action: ->(_args) {
85
+ Gergich.git("fetch")
86
+
87
+ potentially_mergeable_changes.each do |change|
88
+ print "Checking g/#{change['_number']}... "
89
+
90
+ sha = change["current_revision"]
91
+ revinfo = change["revisions"][sha]
92
+ refspec = revinfo["ref"]
93
+ number = revinfo["_number"]
94
+ Gergich.git("fetch ssh://gerrit.instructure.com:29418/#{PROJECT} #{refspec}")
95
+
96
+ maybe_bounce_commit! Gergich::Commit.new(sha, number)
97
+ sleep 1
98
+ end
99
+ },
100
+ help: ->() {
101
+ <<-TEXT
102
+ master_bouncer check_all
103
+
104
+ Check all open Verified+1 patchsets and bounce any that are too old.
105
+ TEXT
106
+ }
107
+ }
108
+
109
+ run_app commands
@@ -0,0 +1,59 @@
1
+ require_relative "../../lib/gergich/capture"
2
+
3
+ RSpec.describe Gergich::Capture do
4
+ let!(:draft) { double }
5
+
6
+ before do
7
+ allow(Gergich::Draft).to receive(:new).and_return(draft)
8
+ end
9
+
10
+ context "rubocop" do
11
+ it "should catch errors" do
12
+ allow(described_class).to receive(:run_command).and_return([0, <<-OUTPUT])
13
+ bin/gergich:47:8: C: Prefer double-quoted strings
14
+ if ENV['DEBUG']
15
+ ^^^^^^^
16
+ OUTPUT
17
+ expect(draft).to receive(:add_comment).with(
18
+ "bin/gergich",
19
+ 47,
20
+ "[rubocop] C: Prefer double-quoted strings\n\n if ENV['DEBUG']\n ^^^^^^^\n",
21
+ "error"
22
+ )
23
+ described_class.run("rubocop", "false")
24
+ end
25
+ end
26
+
27
+ context "eslint" do
28
+ it "should catch errors" do
29
+ allow(described_class).to receive(:run_command).and_return([0, <<-OUTPUT])
30
+ jsapp/models/user.js
31
+ 4:21 error Missing semicolon semi
32
+ OUTPUT
33
+ expect(draft).to receive(:add_comment).with(
34
+ "jsapp/models/user.js",
35
+ 4,
36
+ "[eslint] Missing semicolon",
37
+ "error"
38
+ )
39
+ described_class.run("eslint", "false")
40
+ end
41
+ end
42
+
43
+ context "i18nliner" do
44
+ it "should catch errors" do
45
+ allow(described_class).to receive(:run_command).and_return([0, <<-OUTPUT])
46
+ 1)
47
+ invalid signature on line 4: <unsupported expression>
48
+ jsapp/models/user.js
49
+ OUTPUT
50
+ expect(draft).to receive(:add_comment).with(
51
+ "jsapp/models/user.js",
52
+ 4,
53
+ "[i18n] invalid signature: <unsupported expression>",
54
+ "error"
55
+ )
56
+ described_class.run("i18nliner", "false")
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,133 @@
1
+ require_relative "../lib/gergich"
2
+
3
+ RSpec.describe Gergich::Draft do
4
+ let!(:draft) do
5
+ commit = double(:commit, {
6
+ files: [
7
+ "foo.rb",
8
+ "bar/baz.lol"
9
+ ],
10
+ revision_id: "test",
11
+ change_id: "test"
12
+ })
13
+ described_class.new commit
14
+ end
15
+
16
+ after do
17
+ draft.reset!
18
+ end
19
+
20
+ describe "#info" do
21
+ subject { draft.info }
22
+
23
+ describe "[:comments]" do
24
+ subject { super()[:comments] }
25
+
26
+ it "includes file comments" do
27
+ draft.add_comment "foo.rb", 1, "fix foo", "info"
28
+ expect(subject).to eq({ "foo.rb" => [{ line: 1, message: "[INFO] fix foo" }] })
29
+ end
30
+
31
+ it "includes COMMIT_MSG comments" do
32
+ draft.add_comment "/COMMIT_MSG", 1, "fix commit", "info"
33
+ expect(subject).to eq({ "/COMMIT_MSG" => [{ line: 1, message: "[INFO] fix commit" }] })
34
+ end
35
+
36
+ it "doesn't include orphaned file comments" do
37
+ draft.add_comment "invalid.rb", 1, "fix invalid", "info"
38
+ expect(subject).to eq({})
39
+ end
40
+ end
41
+
42
+ describe "[:cover_message]" do
43
+ subject { super()[:cover_message] }
44
+
45
+ it "includes the Code-Review score if negative" do
46
+ draft.add_label "Code-Review", -1
47
+ expect(subject).to match(/^-1/)
48
+ end
49
+
50
+ it "doesn't include the score if not negative" do
51
+ draft.add_label "Code-Review", 0
52
+ expect(subject).to_not match(/^0/)
53
+ end
54
+
55
+ it "includes explicitly added messages" do
56
+ draft.add_message "this is good"
57
+ draft.add_message "loljk it's terrible"
58
+ expect(subject).to include("this is good\n\nloljk it's terrible")
59
+ end
60
+ end
61
+
62
+ describe "[:total_comments]" do
63
+ subject { super()[:total_comments] }
64
+
65
+ it "includes inline and orphaned comments" do
66
+ draft.add_comment "foo.rb", 1, "fix foo", "info"
67
+ draft.add_comment "invalid.rb", 1, "fix invalid", "info"
68
+ expect(subject).to eq 2
69
+ end
70
+ end
71
+
72
+ describe "[:labels]" do
73
+ subject { super()[:labels] }
74
+
75
+ it "uses the lowest score for each label" do
76
+ draft.add_label "QA-Review", 1
77
+ draft.add_label "QA-Review", -1
78
+ draft.add_label "Code-Review", -2
79
+ draft.add_label "Code-Review", 1
80
+
81
+ expect(subject).to eq({
82
+ "QA-Review" => -1,
83
+ "Code-Review" => -2
84
+ })
85
+ end
86
+
87
+ it "disallows \"Verified\"" do
88
+ expect { draft.add_label "Verified", 1 }.to raise_error(/can't set Verified/)
89
+ end
90
+
91
+ it "disallows scores > 1" do
92
+ expect { draft.add_label "Foo", 2 }.to raise_error(/invalid score/)
93
+ end
94
+
95
+ describe "[\"Code-Review\"]" do
96
+ subject { super()["Code-Review"] }
97
+
98
+ it "defaults to zero" do
99
+ expect(subject).to eq(0)
100
+ end
101
+
102
+ it "is the lowest comment severity if not set" do
103
+ draft.add_comment "foo.rb", 1, "fix foo", "info"
104
+ draft.add_comment "foo.rb", 2, "fix foo", "error"
105
+ draft.add_comment "foo.rb", 3, "fix foo", "warn"
106
+
107
+ expect(subject).to eq(-2)
108
+ end
109
+
110
+ it "is trumped by a lower comment severity if negative" do
111
+ draft.add_label "Code-Review", 1
112
+ draft.add_comment "foo.rb", 1, "fix foo", "warn"
113
+
114
+ expect(subject).to eq(-1)
115
+ end
116
+
117
+ it "is not trumped by a lower comment severity if zero" do
118
+ draft.add_label "Code-Review", 1
119
+ draft.add_comment "foo.rb", 1, "this is ok", "info"
120
+
121
+ expect(subject).to eq(1)
122
+ end
123
+
124
+ it "is not trumped by a higher comment severity" do
125
+ draft.add_label "Code-Review", -1
126
+ draft.add_comment "foo.rb", 1, "this is ok", "info"
127
+
128
+ expect(subject).to eq(-1)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,89 @@
1
+ require "simplecov" if ENV["COVERAGE"]
2
+
3
+ # This file was generated by the `rspec --init` command. Conventionally, all
4
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
5
+ # The generated `.rspec` file contains `--require spec_helper` which will cause
6
+ # this file to always be loaded, without a need to explicitly require it in any
7
+ # files.
8
+ #
9
+ # Given that it is always loaded, you are encouraged to keep this file as
10
+ # light-weight as possible. Requiring heavyweight dependencies from this file
11
+ # will add to the boot time of your test suite on EVERY test run, even for an
12
+ # individual file that may not need all of that loaded. Instead, consider making
13
+ # a separate helper file that requires the additional dependencies and performs
14
+ # the additional setup, and require it from the spec files that actually need
15
+ # it.
16
+ #
17
+ # The `.rspec` file also contains a few flags that are not defaults but that
18
+ # users commonly want.
19
+ #
20
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
21
+ RSpec.configure do |config|
22
+ # rspec-expectations config goes here. You can use an alternate
23
+ # assertion/expectation library such as wrong or the stdlib/minitest
24
+ # assertions if you prefer.
25
+ config.expect_with :rspec do |expectations|
26
+ # This option will default to `true` in RSpec 4. It makes the `description`
27
+ # and `failure_message` of custom matchers include text for helper methods
28
+ # defined using `chain`, e.g.:
29
+ # be_bigger_than(2).and_smaller_than(4).description
30
+ # # => "be bigger than 2 and smaller than 4"
31
+ # ...rather than:
32
+ # # => "be bigger than 2"
33
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
34
+ end
35
+
36
+ # rspec-mocks config goes here. You can use an alternate test double
37
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
38
+ config.mock_with :rspec do |mocks|
39
+ # Prevents you from mocking or stubbing a method that does not exist on
40
+ # a real object. This is generally recommended, and will default to
41
+ # `true` in RSpec 4.
42
+ mocks.verify_partial_doubles = true
43
+ end
44
+
45
+ # These two settings work together to allow you to limit a spec run
46
+ # to individual examples or groups you care about by tagging them with
47
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
48
+ # get run.
49
+ config.filter_run :focus
50
+ config.run_all_when_everything_filtered = true
51
+
52
+ # Limits the available syntax to the non-monkey patched syntax that is
53
+ # recommended. For more details, see:
54
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
55
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
56
+ # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching
57
+ config.disable_monkey_patching!
58
+
59
+ # This setting enables warnings. It's recommended, but in some cases may
60
+ # be too noisy due to issues in dependencies.
61
+ config.warnings = true
62
+
63
+ # Many RSpec users commonly either run the entire suite or an individual
64
+ # file, and it's useful to allow more verbose output when running an
65
+ # individual spec file.
66
+ if config.files_to_run.one?
67
+ # Use the documentation formatter for detailed output,
68
+ # unless a formatter has already been configured
69
+ # (e.g. via a command-line flag).
70
+ config.default_formatter = "doc"
71
+ end
72
+
73
+ # Print the 10 slowest examples and example groups at the
74
+ # end of the spec run, to help surface which specs are running
75
+ # particularly slow.
76
+ config.profile_examples = 10
77
+
78
+ # Run specs in random order to surface order dependencies. If you find an
79
+ # order dependency and want to debug it, you can fix the order by providing
80
+ # the seed, which is printed after each run.
81
+ # --seed 1234
82
+ config.order = :random
83
+
84
+ # Seed global randomization in this process using the `--seed` CLI option.
85
+ # Setting this allows you to use `--seed` to deterministically reproduce
86
+ # test failures related to randomization by passing the same `--seed` value
87
+ # as the one that triggered the failure.
88
+ Kernel.srand config.seed
89
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gergich
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jon Jensen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-05 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sqlite3
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.3'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.3'
27
+ - !ruby/object:Gem::Dependency
28
+ name: httparty
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.6'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.6'
41
+ description: Gergich is a little command-line tool for wiring up linters to Gerrit
42
+ so you can get nice inline comments right on the review
43
+ email: jon@instructure.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE
49
+ - README.md
50
+ - bin/check_coverage
51
+ - bin/gergich
52
+ - bin/master_bouncer
53
+ - lib/gergich.rb
54
+ - lib/gergich/capture.rb
55
+ - lib/gergich/capture/eslint_capture.rb
56
+ - lib/gergich/capture/i18nliner_capture.rb
57
+ - lib/gergich/capture/rubocop_capture.rb
58
+ - lib/gergich/cli.rb
59
+ - lib/gergich/cli/gergich.rb
60
+ - lib/gergich/cli/master_bouncer.rb
61
+ - spec/gergich/capture_spec.rb
62
+ - spec/gergich_spec.rb
63
+ - spec/spec_helper.rb
64
+ homepage: https://github.com/instructure/gergich
65
+ licenses:
66
+ - MIT
67
+ metadata: {}
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 1.9.3
77
+ required_rubygems_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ requirements: []
83
+ rubyforge_project:
84
+ rubygems_version: 2.2.5
85
+ signing_key:
86
+ specification_version: 4
87
+ summary: Command-line tool for adding Gerrit comments
88
+ test_files: []
89
+ has_rdoc: