gerricator 0.1.0

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ee449454a494c46f37194ccd50bd6a4ded5f4059
4
+ data.tar.gz: d5ec69ec969d164089100c413913962c52d1b590
5
+ SHA512:
6
+ metadata.gz: 123e5020fc9355e7b55e15b546fb3d0d2db08571c0b86962a21d72e346358ba19c9b35a51dece8adbc4a8ba6e48fc683f7487766c2eb8e0afd4d930a2838397a
7
+ data.tar.gz: 96762cdc978f1824cc6b981320feab49a3d3bb5b9f5ec19e844c558e1d9e158f1250d15ac354002d1bf3a7d1e6bac4b3dc2830438d6592faf6b1370b4a206e68
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2015 Jun Wu <quark@lihdd.net>
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,43 @@
1
+ gerricator
2
+ ==========
3
+
4
+ Command-line tool to create or update [Phabricator](http://phabricator.org/) diff from [Gerrit](https://code.google.com/p/gerrit/) change
5
+
6
+ Requirements
7
+ ------------
8
+ * git
9
+ * arc
10
+ * ruby (> 2.0)
11
+
12
+ Installation
13
+ ------------
14
+
15
+ ```bash
16
+ gem install gerricator
17
+ gerricator init
18
+ editor ~/.config/gerricator/config.yml
19
+ ```
20
+
21
+ Examples
22
+ --------
23
+
24
+ ```bash
25
+ # push patchset 1 in gerrit change 2020 to phabricator
26
+ gerricator push 2020 1 # outputs differential id, ex. 'D201'
27
+
28
+ # push patchset 3, update the same phabricator diff
29
+ gerricator push 2020 3
30
+
31
+ # push latest patchset in change 2020
32
+ gerricator push 2020
33
+
34
+ # does nothing because 2020#3 is already pushed
35
+ gerricator push 2020 3
36
+
37
+ # you can also use long change-id
38
+ gerricator push Id7c5ef224f847422350ecbdaa8da397ffd929f9a
39
+
40
+ # see debug logs
41
+ export VERBOSE=1
42
+ gerricator push 2020 2
43
+ ```
data/bin/gerricator ADDED
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'thor'
4
+ require_relative '../config/app'
5
+
6
+
7
+ class Gerricator < Thor
8
+ desc 'push CHANGE REVISION', 'push Gerrit change to Phabricator'
9
+ def push change_id, rev_no=nil
10
+ App::push_change change_id, rev_no
11
+ end
12
+
13
+ desc 'init', 'create an example config file'
14
+ method_option :force, type: :boolean, aliases: '-f', default: false, desc: 'overwrite existing config file'
15
+ def init
16
+ App::write_example_config force: options.force
17
+ end
18
+ end
19
+
20
+ Gerricator.start
data/config/app.rb ADDED
@@ -0,0 +1,145 @@
1
+ require 'active_record'
2
+ require 'fileutils'
3
+ require 'logger'
4
+ require 'sqlite3'
5
+ require 'thor'
6
+ require 'yaml'
7
+ require_relative '../lib/arcanist'
8
+ require_relative '../lib/gerrit'
9
+ require_relative '../lib/git'
10
+
11
+ CONFIG_PATH = File.expand_path('~/.config/gerricator/config.yml')
12
+
13
+ def logger
14
+ @logger ||=\
15
+ if ENV['VERBOSE'] || ENV['DEBUG']
16
+ Logger.new(STDERR)
17
+ else
18
+ Logger.new('/dev/null')
19
+ end
20
+ end
21
+
22
+ def config
23
+ @config ||=\
24
+ begin
25
+ config_path = Dir["{#{CONFIG_PATH},{./,./config/}config.yml{,.example}}"].first
26
+ if config_path.nil?
27
+ raise "Config file not found: #{CONFIG_PATH}"
28
+ else
29
+ logger.info "Config file: #{config_path}"
30
+ end
31
+ YAML::load_file(config_path)
32
+ end
33
+ end
34
+
35
+ def initialize_database
36
+ app_path = File.expand_path('../', __FILE__)
37
+ db_path = config['db_path']
38
+ FileUtils.mkdir_p File.dirname(db_path)
39
+ logger.info "Database: #{db_path}"
40
+
41
+ ActiveRecord::Base.logger = logger if STDERR.isatty
42
+ ActiveRecord::Base.establish_connection(
43
+ adapter: 'sqlite3',
44
+ database: db_path,
45
+ pool: 5,
46
+ timeout: 5000)
47
+ ActiveRecord::Migrator.migrate(File.join(app_path, '../db/migrate'))
48
+ end
49
+
50
+ def gerrit_bots
51
+ config['gerrit']['bots']
52
+ end
53
+
54
+ def gerrit
55
+ @gerrit ||= Gerrit.new(*%w[base_url username http_password].map {|x| config['gerrit'][x]})
56
+ end
57
+
58
+ def arc
59
+ @arc ||= Arcanist.new(*%w[conduit_uri username certificate].map {|x| config['phabricator'][x]})
60
+ end
61
+
62
+ class Link < ActiveRecord::Base; end
63
+
64
+ HTTPI.logger = logger
65
+
66
+ module App; class << self
67
+ # push gerrit change to phabricator
68
+ def push_change change_id, rev_no=nil
69
+ initialize_database
70
+ change = gerrit.change(change_id)
71
+ number = change.number
72
+ project = change.project
73
+ raise "Cannot find change #{change_id}" unless number.to_i > 0
74
+
75
+ oid, rev = if rev_no.nil?
76
+ # last revision
77
+ change.revisions.max_by {|k, v| v['_number']}
78
+ else
79
+ change.revisions.find {|k, v| v['_number'] == rev_no.to_i}
80
+ end
81
+ raise "Cannot find revision #{rev_no}" unless rev && oid
82
+ rev_no = rev['_number']
83
+ logger.info "Revision #{rev_no}: #{oid}"
84
+
85
+ make_temp_work_dir do |work_dir|
86
+ link = Link.find_by(change_number: change.number)
87
+ if link && link.patch_set_revisions.to_s.split.include?(rev_no.to_s)
88
+ logger.info "Revision #{rev_no} is already at D#{link.differential_id}"
89
+ else
90
+ checkout_commit project, work_dir, oid
91
+ if link
92
+ logger.info "Updating D#{link.differential_id}"
93
+ arc.diff work_dir, config['projects'][project]['phabricator_callsign'], message: "Patchset #{rev_no}", differential_id: link.differential_id
94
+ link.patch_set_revisions = "#{link.patch_set_revisions} #{rev_no}"
95
+ link.save!
96
+ else
97
+ change_url = File.join(config['gerrit']['base_url'], "#/c/#{number}")
98
+ message = "#{Git::message(work_dir)}\nGerrit URL: #{change_url}\n"
99
+ reviewers = [*config['projects'][project]['reviewers']].compact
100
+ reviewers = change.reviewers - gerrit_bots if reviewers.empty?
101
+ logger.info "Creating new diff, reviewers: #{reviewers}"
102
+ differential_id = arc.diff(work_dir, config['projects'][project]['phabricator_callsign'], message: message, reviewers: reviewers)
103
+ link = Link.create! differential_id: differential_id, change_number: number, patch_set_revisions: rev_no
104
+ end
105
+ end
106
+ puts "D#{link.differential_id}"
107
+ end
108
+ end
109
+
110
+ def write_example_config force: false
111
+ example_config_path = File.expand_path('../config.yml.example', __FILE__)
112
+ raise "#{CONFIG_PATH} already exists" if !force && File.exists?(CONFIG_PATH)
113
+ FileUtils.mkdir_p File.dirname(CONFIG_PATH)
114
+ FileUtils.cp example_config_path, CONFIG_PATH
115
+ logger.info "Example config written: #{CONFIG_PATH}"
116
+ end
117
+
118
+ private
119
+
120
+ def sync_repo project
121
+ File.join(File.expand_path(config['local_cache_path']), 'projects', project).tap do |dest|
122
+ url = config['projects'][project]['git_url']
123
+ logger.info "Mirroring #{project} (#{url}) to #{dest}"
124
+ Git::mirror url, dest
125
+ end
126
+ end
127
+
128
+ def checkout_commit project, work_dir, oid
129
+ sync_repo(project).tap do |dest|
130
+ logger.info "Checking out #{oid} at #{work_dir}"
131
+ Git::write_commit_tree dest, work_dir, oid
132
+ end
133
+ end
134
+
135
+ def make_temp_work_dir
136
+ work_dir = File.join(File.expand_path(config['local_cache_path']), 'work_dir', $$.to_s)
137
+ raise "#{work_dir} already exists" if File.exists?(work_dir)
138
+ begin
139
+ FileUtils.mkdir_p work_dir
140
+ yield work_dir
141
+ ensure
142
+ FileUtils.rm_rf work_dir
143
+ end
144
+ end
145
+ end; end
@@ -0,0 +1,21 @@
1
+ phabricator:
2
+ conduit_uri: http://phricator.example.com/
3
+ username: gerricator
4
+ certificate: f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0
5
+ gerrit:
6
+ base_url: https://gerrit.example.com/
7
+ username: gerricator
8
+ http_password: HTTPpasswd
9
+ bots: ['jenkins']
10
+ projects:
11
+ gerrit_project_name_a:
12
+ git_url: ssh://gerricator@gerrit.example.com/gerrit_project_name_a.git
13
+ phabricator_callsign: A
14
+ # if 'reviewers' is not set, it will be gerrit reviewers - gerrit bots.
15
+ # note: phabricator must have these reviewers accounts.
16
+ gerrit_project_name_b:
17
+ git_url: ssh://gerricator@gerrit.example.com/gerrit_project_name_b.git
18
+ phabricator_callsign: B
19
+ reviewers: ['alice', 'bob']
20
+ local_cache_path: ~/.cache/gerricator/
21
+ db_path: ~/.config/gerricator/main.sqlite3
@@ -0,0 +1,10 @@
1
+ class Schema < ActiveRecord::Migration
2
+ def change
3
+ create_table :links, force: true do |t|
4
+ t.integer :change_number, null: false
5
+ t.integer :differential_id, null: false
6
+ t.text :patch_set_revisions
7
+ end
8
+ add_index :links, :change_number, unique: true
9
+ end
10
+ end
@@ -0,0 +1,19 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = 'gerricator'
3
+ s.version = '0.1.0'
4
+ s.date = Date.civil(2015,1,25)
5
+ s.summary = 'Gerrit change to Phabricator diff'
6
+ s.description = 'Command-line tool to create or update Phabricator diff from Gerrit change'
7
+ s.authors = ['Jun Wu']
8
+ s.email = 'quark@lihdd.net'
9
+ s.homepage = 'https://github.com/quark-zju/gerricator'
10
+ s.require_paths = ['lib']
11
+ s.licenses = ['BSD']
12
+ s.files = %w(LICENSE README.md gerricator.gemspec)
13
+ s.files += Dir.glob('{bin/*,lib/*.rb,db/**/*.rb,config/*.{example,rb}}')
14
+ s.executables = ['gerricator']
15
+ s.add_dependency 'activerecord', '~> 4.1'
16
+ s.add_dependency 'sqlite3', '~> 1.3'
17
+ s.add_dependency 'thor', '~> 0.19'
18
+ s.add_dependency 'httpi', '~> 2.2'
19
+ end
data/lib/arcanist.rb ADDED
@@ -0,0 +1,66 @@
1
+ require 'json'
2
+ require 'shellwords'
3
+ require 'tempfile'
4
+ require_relative 'git'
5
+
6
+
7
+ class Arcanist < Struct.new(:conduit_uri, :user, :cert)
8
+
9
+ def diff work_dir, callsign, commit: 'HEAD^', differential_id: nil, message: nil, reviewers: ''
10
+ arcrc = write_arcrc
11
+ temps = [arcrc]
12
+ args = ['--no-ansi', '--conduit-uri', conduit_uri, '--config', "repository.callsign=#{callsign}", '--arcrc-file', arcrc.path]
13
+
14
+ if differential_id
15
+ # update existing diff
16
+ args += ['--update', differential_id.to_s, '--message', message || 'Update']
17
+ else
18
+ # create new diff
19
+ tmp = Tempfile.create 'arcmsg'
20
+ tmp.write <<"EOS"
21
+ #{message || Git::message(work_dir)}
22
+
23
+ Test Plan:
24
+ N/A (imported by gerricator)
25
+
26
+ Reviewers: #{[*reviewers].join(', ')}
27
+ EOS
28
+ tmp.flush
29
+ temps << tmp
30
+ args += ['--create', '--message-file', tmp.path]
31
+ end
32
+
33
+ Dir.chdir work_dir do
34
+ cmd = Shellwords.join(['arc', 'diff', *args, commit])
35
+ result = `#{cmd}`
36
+
37
+ if not differential_id
38
+ differential_id = result[/#{File.join(conduit_uri, 'D')}(\d+)/, 1]
39
+ end
40
+ end
41
+
42
+ differential_id
43
+ ensure
44
+ temps.each do |t|
45
+ File.unlink(t) if t && File.exists?(t)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def write_arcrc
52
+ Tempfile.create('arcrc').tap do |t|
53
+ content = {
54
+ hosts: {
55
+ File.join(conduit_uri, 'api/') => {
56
+ user: user,
57
+ cert: cert,
58
+ }
59
+ }
60
+ }
61
+ t.write JSON.dump(content)
62
+ t.flush
63
+ end
64
+ end
65
+
66
+ end
data/lib/gerrit.rb ADDED
@@ -0,0 +1,78 @@
1
+ require 'httpi'
2
+ require 'json'
3
+ require 'base64'
4
+
5
+ class Gerrit < Struct.new(:base_url, :username, :password)
6
+
7
+ def get endpoint
8
+ request endpoint
9
+ end
10
+
11
+ def post endpoint, body = ''
12
+ request endpoint, method: 'post', body: body.to_s
13
+ end
14
+
15
+ class Change < Struct.new(:gerrit, :change_id)
16
+ def number
17
+ @number ||= get['_number']
18
+ end
19
+
20
+ def project
21
+ @project ||= get['project']
22
+ end
23
+
24
+ def reviewers
25
+ get('reviewers').map {|x| x['username']}.compact
26
+ end
27
+
28
+ def revisions
29
+ get('?o=ALL_REVISIONS')['revisions']
30
+ end
31
+
32
+ def get subpath=''
33
+ path = File.join("changes/#{change_id}", subpath)
34
+ gerrit.get path
35
+ end
36
+ end
37
+
38
+ def change(change_id)
39
+ Change.new(self, change_id)
40
+ end
41
+
42
+ private
43
+
44
+ def request endpoint, method: 'get', body: nil
45
+ if method == 'get' && body.nil?
46
+ @get_caches ||= {}
47
+ @get_caches[endpoint] ||= send_request endpoint, method: method, body: body
48
+ else
49
+ send_request endpoint, method: method, body: body
50
+ end
51
+ end
52
+
53
+ def send_request endpoint, method: 'get', body: nil
54
+ req = HTTPI::Request.new File.join(base_url, username ? 'a' : '', endpoint)
55
+ req.auth.digest username, password if username && password && !username.empty?
56
+ req.auth.ssl.verify_mode = :none
57
+ if body
58
+ req.body = body
59
+ req.headers['Content-Type'] = 'application/json;charset=UTF-8'
60
+ end
61
+ res = HTTPI.send method, req
62
+ content_type = [*res.headers['Content-Type']].last.split(/[ ;]/).first
63
+ case content_type
64
+ when 'application/json'
65
+ JSON.parse res.body.lines[1..-1].join
66
+ when 'text/plain'
67
+ case res.headers['X-FYI-Content-Encoding']
68
+ when 'base64'
69
+ Base64::decode64 res.body
70
+ else
71
+ res.body
72
+ end
73
+ else
74
+ raise "Unknown content type: #{content_type}"
75
+ end
76
+ end
77
+
78
+ end
data/lib/git.rb ADDED
@@ -0,0 +1,67 @@
1
+ require 'fileutils'
2
+ require 'shellwords'
3
+
4
+
5
+ module Git; class << self
6
+ def mirror url, dest
7
+ if File.exists?(dest)
8
+ Dir.chdir(dest) do
9
+ system! 'git', 'fetch', '--tags', '--all', '--force', '--prune'
10
+ end
11
+ else
12
+ FileUtils.mkdir_p dest
13
+ system! 'git', 'clone', '--mirror', url, dest
14
+ end
15
+ end
16
+
17
+ def add_shared_objects path, object_path
18
+ raise "object_path is empty" unless object_path
19
+ Dir.chdir path do
20
+ alernates_path = '.git/objects/info/alternates'
21
+ content = (File.read(alernates_path) rescue '')
22
+ return if content.include?(object_path)
23
+ File.write alernates_path, "#{object_path}\n#{content}"
24
+ end
25
+ end
26
+
27
+ def init dest
28
+ FileUtils.mkdir_p dest
29
+ Dir.chdir dest do system! 'git', 'init' end
30
+ end
31
+
32
+ def checkout dest, commit
33
+ raise "commit is empty" unless commit
34
+ Dir.chdir dest do
35
+ system! 'git', 'checkout', '--quiet', '--force', '--detach', commit
36
+ end
37
+ end
38
+
39
+ def message dest, commit='HEAD'
40
+ Dir.chdir dest do
41
+ `git log --format=%B -n 1 #{Shellwords.join([commit])}`.chomp
42
+ end
43
+ end
44
+
45
+ def amend dest, message
46
+ Dir.chdir dest do
47
+ system! 'git', 'commit', '--amend', '--message', message
48
+ end
49
+ end
50
+
51
+ def write_commit_tree repo_path, dest, commit
52
+ init dest
53
+ object_path = Dir["#{repo_path}/{.git/,}objects"].first
54
+ add_shared_objects dest, object_path
55
+ checkout dest, commit
56
+ end
57
+
58
+ private
59
+
60
+ def system! *args
61
+ quiet = !ENV['VERBOSE'] && !ENV['DEBUG']
62
+ options = quiet ? {:out => '/dev/null', :err => '/dev/null'} : {}
63
+
64
+ raise "Cannot run #{Shellwords.join(args)}" unless system(*args, options)
65
+ end
66
+
67
+ end; end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: gerricator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jun Wu
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-01-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '4.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '4.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.3'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.3'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '0.19'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '0.19'
55
+ - !ruby/object:Gem::Dependency
56
+ name: httpi
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.2'
69
+ description: Command-line tool to create or update Phabricator diff from Gerrit change
70
+ email: quark@lihdd.net
71
+ executables:
72
+ - gerricator
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE
77
+ - README.md
78
+ - bin/gerricator
79
+ - config/app.rb
80
+ - config/config.yml.example
81
+ - db/migrate/001_schema.rb
82
+ - gerricator.gemspec
83
+ - lib/arcanist.rb
84
+ - lib/gerrit.rb
85
+ - lib/git.rb
86
+ homepage: https://github.com/quark-zju/gerricator
87
+ licenses:
88
+ - BSD
89
+ metadata: {}
90
+ post_install_message:
91
+ rdoc_options: []
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - ">="
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
99
+ required_rubygems_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ requirements: []
105
+ rubyforge_project:
106
+ rubygems_version: 2.2.0
107
+ signing_key:
108
+ specification_version: 4
109
+ summary: Gerrit change to Phabricator diff
110
+ test_files: []
111
+ has_rdoc: