gerricator 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: