gergich 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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: