gitlab-grack 2.0.0.rc2 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f620ff134c43ad395371b842da7bbeb812cf0434
4
- data.tar.gz: edb62816e986e9eb510d66d077bba78514c575f4
3
+ metadata.gz: fdf3609511e6c0a489c572a4add4f89af65bae4b
4
+ data.tar.gz: 8ad35782833f609c0dba191250746d5a2dfd20bc
5
5
  SHA512:
6
- metadata.gz: 662499bdfca3cb601ed8b64c14bde3160087f108e57062452610f47b1e34e873d4cb81131cc23be990e42ac2101f879015d06ee231fc87ace57b8eb8bf700505
7
- data.tar.gz: 49a2126c89906af736b3bb4d4f3243dd8efa5656405ca0010f36f5a597ce5be4759d5bfa5166984a81d985f88e96648637e6299e325d9a68b069f8ea22bfd5a9
6
+ metadata.gz: 4f506cdcc801556af8b5ab796d8ebf46585ef4025805307bc2cef094a653c5a3a2b3efa941bf61f48593def2696b99ba31c4a026e0195309d63e1ede3d70f0e9
7
+ data.tar.gz: 0c5bb7dd97d1d5f07e57d3642a0f27b3bdc3d65b00586780d0e23a435e16f2ff5fd2f2185b563070ad77cd0ff15e695471466498dc7868af04ba4416e4ebfbd1
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  coverage/
2
+ examples/*.git
2
3
  pkg/
3
4
  *.gem
data/CHANGELOG CHANGED
@@ -1,2 +1,10 @@
1
+ 2.0.0
2
+ - Use safer shell commands and avoid Dir.chdir
3
+ - Restrict the environment for shell commands
4
+ - Make Grack::Server thread-safe (zimbatm)
5
+ - Make sure child processes get reaped after popen
6
+ - Verify requested path is actually a Git directory (Ryan Canty)
7
+
8
+
1
9
  1.1.0
2
10
  - Modifies service_rpc to use chunked transfer (https://github.com/gitlabhq/grack/pull/1)
data/Gemfile CHANGED
@@ -4,5 +4,6 @@ gemspec
4
4
 
5
5
  group :development do
6
6
  gem 'rake'
7
+ gem 'pry'
7
8
  gem 'rack-test'
8
9
  end
@@ -1,19 +1,26 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gitlab-grack (2.0.0.pre)
4
+ gitlab-grack (2.0.0.rc2)
5
5
  rack (~> 1.5.1)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
+ coderay (1.1.0)
10
11
  metaclass (0.0.1)
12
+ method_source (0.8.2)
11
13
  mocha (0.14.0)
12
14
  metaclass (~> 0.0.1)
15
+ pry (0.10.1)
16
+ coderay (~> 1.1.0)
17
+ method_source (~> 0.8.1)
18
+ slop (~> 3.4)
13
19
  rack (1.5.2)
14
20
  rack-test (0.6.2)
15
21
  rack (>= 1.0)
16
22
  rake (10.1.0)
23
+ slop (3.6.0)
17
24
 
18
25
  PLATFORMS
19
26
  ruby
@@ -21,5 +28,6 @@ PLATFORMS
21
28
  DEPENDENCIES
22
29
  gitlab-grack!
23
30
  mocha (~> 0.11)
31
+ pry
24
32
  rack-test
25
33
  rake
data/Rakefile CHANGED
@@ -19,7 +19,7 @@ end
19
19
  namespace :grack do
20
20
  desc "Start Grack"
21
21
  task :start do
22
- system "rackup config.ru -p 8080"
22
+ system('./bin/testserver')
23
23
  end
24
24
  end
25
25
 
@@ -0,0 +1,6 @@
1
+ #! /bin/bash
2
+
3
+ set -e
4
+
5
+ cd $(dirname "$0")/..
6
+ exec /usr/bin/env bundle exec pry -Ilib -r grack
@@ -0,0 +1,14 @@
1
+ #! /usr/bin/env ruby
2
+ libdir = File.absolute_path( File.join( File.dirname(__FILE__), '../lib' ) )
3
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
4
+
5
+ require 'grack'
6
+ require 'rack'
7
+ root = File.absolute_path( File.join( File.dirname(__FILE__), '../examples' ) )
8
+ app = Grack::Server.new({
9
+ project_root: root,
10
+ upload_pack: true,
11
+ receive_pack:true
12
+ })
13
+
14
+ Rack::Server.start app: app, Port: 3001
@@ -0,0 +1,69 @@
1
+ module Grack
2
+ class Git
3
+ attr_reader :repo
4
+
5
+ def initialize(git_path, repo_path)
6
+ @git_path = git_path
7
+ @repo = repo_path
8
+ end
9
+
10
+ def update_server_info
11
+ execute(%W(update-server-info))
12
+ end
13
+
14
+ def command(cmd)
15
+ [@git_path || 'git'] + cmd
16
+ end
17
+
18
+ def capture(cmd)
19
+ IO.popen(popen_env, cmd, popen_options).read
20
+ end
21
+
22
+ def execute(cmd)
23
+ cmd = command(cmd)
24
+ if block_given?
25
+ IO.popen(popen_env, cmd, File::RDWR, popen_options) do |pipe|
26
+ yield(pipe)
27
+ end
28
+ else
29
+ capture(cmd).chomp
30
+ end
31
+ end
32
+
33
+ def popen_options
34
+ { chdir: repo, unsetenv_others: true }
35
+ end
36
+
37
+ def popen_env
38
+ { 'PATH' => ENV['PATH'], 'GL_ID' => ENV['GL_ID'] }
39
+ end
40
+
41
+ def config_setting(service_name)
42
+ service_name = service_name.gsub('-', '')
43
+ setting = config("http.#{service_name}")
44
+
45
+ if service_name == 'uploadpack'
46
+ setting != 'false'
47
+ else
48
+ setting == 'true'
49
+ end
50
+ end
51
+
52
+ def config(config_name)
53
+ execute(%W(config #{config_name}))
54
+ end
55
+
56
+ def valid_repo?
57
+ return false unless File.exists?(repo) && File.realpath(repo) == repo
58
+
59
+ match = execute(%W(rev-parse --git-dir)).match(/\.$|\.git$/)
60
+
61
+ if match.to_s == '.git'
62
+ # Since the parent could be a git repo, we want to make sure the actual repo contains a git dir.
63
+ return false unless Dir.entries(repo).include?('.git')
64
+ end
65
+
66
+ match
67
+ end
68
+ end
69
+ end
@@ -4,8 +4,11 @@ require 'rack/response'
4
4
  require 'rack/utils'
5
5
  require 'time'
6
6
 
7
+ require 'grack/git'
8
+
7
9
  module Grack
8
10
  class Server
11
+ attr_reader :git
9
12
 
10
13
  SERVICES = [
11
14
  ["POST", 'service_rpc', "(.*?)/git-upload-pack$", 'upload-pack'],
@@ -45,12 +48,12 @@ module Grack
45
48
  cmd, path, @reqfile, @rpc = match_routing
46
49
 
47
50
  return render_method_not_allowed if cmd == 'not_allowed'
48
- return render_not_found if !cmd
51
+ return render_not_found unless cmd
49
52
 
50
- @dir = get_git_dir(path)
51
- return render_not_found if !@dir
53
+ @git = get_git(path)
54
+ return render_not_found unless git.valid_repo?
52
55
 
53
- self.method(cmd).call()
56
+ self.method(cmd).call
54
57
  end
55
58
 
56
59
  # ---------------------------------
@@ -64,7 +67,8 @@ module Grack
64
67
  CRLF = "\r\n"
65
68
 
66
69
  def service_rpc
67
- return render_no_access if !has_access(@rpc, true)
70
+ return render_no_access unless has_access?(@rpc, true)
71
+
68
72
  input = read_body
69
73
 
70
74
  @res = Rack::Response.new
@@ -74,14 +78,14 @@ module Grack
74
78
  @res["Cache-Control"] = "no-cache"
75
79
 
76
80
  @res.finish do
77
- command = git_command(%W(#{@rpc} --stateless-rpc #{@dir}))
78
- IO.popen(popen_env, command, File::RDWR, popen_options) do |pipe|
81
+ git.execute([@rpc, '--stateless-rpc', git.repo]) do |pipe|
79
82
  pipe.write(input)
80
83
  pipe.close_write
81
- while !pipe.eof?
82
- block = pipe.read(8192) # 8KB at a time
83
- @res.write encode_chunk(block) # stream it to the client
84
+
85
+ while block = pipe.read(8192) # 8KB at a time
86
+ @res.write encode_chunk(block) # stream it to the client
84
87
  end
88
+
85
89
  @res.write terminating_chunk
86
90
  end
87
91
  end
@@ -89,35 +93,33 @@ module Grack
89
93
 
90
94
  def encode_chunk(chunk)
91
95
  size_in_hex = chunk.size.to_s(16)
92
- [ size_in_hex, CRLF, chunk, CRLF ].join
96
+ [size_in_hex, CRLF, chunk, CRLF].join
93
97
  end
94
98
 
95
99
  def terminating_chunk
96
- [ 0, CRLF, CRLF ].join
100
+ [0, CRLF, CRLF].join
97
101
  end
98
102
 
99
103
  def get_info_refs
100
104
  service_name = get_service_type
105
+ return dumb_info_refs unless has_access?(service_name)
101
106
 
102
- if has_access(service_name)
103
- cmd = git_command(%W(#{service_name} --stateless-rpc --advertise-refs #{@dir}))
104
- refs = capture(cmd)
107
+ refs = git.execute([service_name, '--stateless-rpc', '--advertise-refs', git.repo])
105
108
 
106
- @res = Rack::Response.new
107
- @res.status = 200
108
- @res["Content-Type"] = "application/x-git-%s-advertisement" % service_name
109
- hdr_nocache
110
- @res.write(pkt_write("# service=git-#{service_name}\n"))
111
- @res.write(pkt_flush)
112
- @res.write(refs)
113
- @res.finish
114
- else
115
- dumb_info_refs
116
- end
109
+ @res = Rack::Response.new
110
+ @res.status = 200
111
+ @res["Content-Type"] = "application/x-git-%s-advertisement" % service_name
112
+ hdr_nocache
113
+
114
+ @res.write(pkt_write("# service=git-#{service_name}\n"))
115
+ @res.write(pkt_flush)
116
+ @res.write(refs)
117
+
118
+ @res.finish
117
119
  end
118
120
 
119
121
  def dumb_info_refs
120
- update_server_info
122
+ git.update_server_info
121
123
  send_file(@reqfile, "text/plain; charset=utf-8") do
122
124
  hdr_nocache
123
125
  end
@@ -158,38 +160,33 @@ module Grack
158
160
  # logic helping functions
159
161
  # ------------------------
160
162
 
161
- F = ::File
162
-
163
163
  # some of this borrowed from the Rack::File implementation
164
164
  def send_file(reqfile, content_type)
165
- reqfile = File.join(@dir, reqfile)
166
- return render_not_found if !F.exists?(reqfile)
165
+ reqfile = File.join(git.repo, reqfile)
166
+ return render_not_found unless File.exists?(reqfile)
167
167
 
168
- if reqfile == File.realpath(reqfile)
169
- # reqfile looks legit: no path traversal, no leading '|'
170
- else
171
- # reqfile does not look trustworthy; abort
172
- return render_not_found
173
- end
168
+ return render_not_found unless reqfile == File.realpath(reqfile)
169
+
170
+ # reqfile looks legit: no path traversal, no leading '|'
174
171
 
175
172
  @res = Rack::Response.new
176
173
  @res.status = 200
177
174
  @res["Content-Type"] = content_type
178
- @res["Last-Modified"] = F.mtime(reqfile).httpdate
175
+ @res["Last-Modified"] = File.mtime(reqfile).httpdate
179
176
 
180
177
  yield
181
178
 
182
- if size = F.size?(reqfile)
179
+ if size = File.size?(reqfile)
183
180
  @res["Content-Length"] = size.to_s
184
181
  @res.finish do
185
- F.open(reqfile, "rb") do |file|
182
+ File.open(reqfile, "rb") do |file|
186
183
  while part = file.read(8192)
187
184
  @res.write part
188
185
  end
189
186
  end
190
187
  end
191
188
  else
192
- body = [F.read(reqfile)]
189
+ body = [File.read(reqfile)]
193
190
  size = Rack::Utils.bytesize(body.first)
194
191
  @res["Content-Length"] = size
195
192
  @res.write body
@@ -197,21 +194,15 @@ module Grack
197
194
  end
198
195
  end
199
196
 
200
- def get_git_dir(path)
197
+ def get_git(path)
201
198
  root = @config[:project_root] || Dir.pwd
202
199
  path = File.join(root, path)
203
- if !File.exists?(path)
204
- false
205
- elsif File.realpath(path) != path # looks like path traversal
206
- false
207
- else
208
- path # TODO: check is a valid git directory
209
- end
200
+ Grack::Git.new(@config[:git_path], path)
210
201
  end
211
202
 
212
203
  def get_service_type
213
204
  service_type = @req.params['service']
214
- return false if !service_type
205
+ return false unless service_type
215
206
  return false if service_type[0, 4] != 'git-'
216
207
  service_type.gsub('git-', '')
217
208
  end
@@ -219,81 +210,54 @@ module Grack
219
210
  def match_routing
220
211
  cmd = nil
221
212
  path = nil
213
+
222
214
  SERVICES.each do |method, handler, match, rpc|
223
- if m = Regexp.new(match).match(@req.path_info)
224
- return ['not_allowed'] if method != @req.request_method
225
- cmd = handler
226
- path = m[1]
227
- file = @req.path_info.sub(path + '/', '')
228
- return [cmd, path, file, rpc]
229
- end
215
+ next unless m = Regexp.new(match).match(@req.path_info)
216
+
217
+ return ['not_allowed'] unless method == @req.request_method
218
+
219
+ cmd = handler
220
+ path = m[1]
221
+ file = @req.path_info.sub(path + '/', '')
222
+
223
+ return [cmd, path, file, rpc]
230
224
  end
231
- return nil
225
+
226
+ nil
232
227
  end
233
228
 
234
- def has_access(rpc, check_content_type = false)
229
+ def has_access?(rpc, check_content_type = false)
235
230
  if check_content_type
236
- return false if @req.content_type != "application/x-git-%s-request" % rpc
231
+ conten_type = "application/x-git-%s-request" % rpc
232
+ return false unless @req.content_type == conten_type
237
233
  end
238
- return false if !['upload-pack', 'receive-pack'].include? rpc
234
+
235
+ return false unless ['upload-pack', 'receive-pack'].include?(rpc)
236
+
239
237
  if rpc == 'receive-pack'
240
- return @config[:receive_pack] if @config.include? :receive_pack
238
+ return @config[:receive_pack] if @config.include?(:receive_pack)
241
239
  end
242
- if rpc == 'upload-pack'
243
- return @config[:upload_pack] if @config.include? :upload_pack
244
- end
245
- return get_config_setting(rpc)
246
- end
247
240
 
248
- def get_config_setting(service_name)
249
- service_name = service_name.gsub('-', '')
250
- setting = get_git_config("http.#{service_name}")
251
- if service_name == 'uploadpack'
252
- return setting != 'false'
253
- else
254
- return setting == 'true'
241
+ if rpc == 'upload-pack'
242
+ return @config[:upload_pack] if @config.include?(:upload_pack)
255
243
  end
256
- end
257
244
 
258
- def get_git_config(config_name)
259
- cmd = git_command(%W(config #{config_name}))
260
- capture(cmd).chomp
245
+ git.config_setting(rpc)
261
246
  end
262
247
 
263
248
  def read_body
264
249
  if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
265
- input = Zlib::GzipReader.new(@req.body).read
250
+ Zlib::GzipReader.new(@req.body).read
266
251
  else
267
- input = @req.body.read
252
+ @req.body.read
268
253
  end
269
254
  end
270
255
 
271
- def update_server_info
272
- cmd = git_command(%W(update-server-info))
273
- capture(cmd)
274
- end
275
-
276
- def git_command(command)
277
- [@config[:git_path] || 'git'] + command
278
- end
279
-
280
- def capture(command)
281
- IO.popen(popen_env, command, popen_options) { |p| p.read }
282
- end
283
-
284
- def popen_options
285
- {chdir: @dir, unsetenv_others: true}
286
- end
287
-
288
- def popen_env
289
- {'PATH' => ENV['PATH'], 'GL_ID' => ENV['GL_ID']}
290
- end
291
-
292
256
  # --------------------------------------
293
257
  # HTTP error response handling functions
294
258
  # --------------------------------------
295
259
 
296
- PLAIN_TYPE = {"Content-Type" => "text/plain"}
260
+ PLAIN_TYPE = { "Content-Type" => "text/plain" }
297
261
 
298
262
  def render_method_not_allowed
299
263
  if @env['SERVER_PROTOCOL'] == "HTTP/1.1"
@@ -321,10 +285,9 @@ module Grack
321
285
  end
322
286
 
323
287
  def pkt_write(str)
324
- (str.size + 4).to_s(base=16).rjust(4, '0') + str
288
+ (str.size + 4).to_s(16).rjust(4, '0') + str
325
289
  end
326
290
 
327
-
328
291
  # ------------------------
329
292
  # header writing functions
330
293
  # ------------------------
@@ -341,6 +304,5 @@ module Grack
341
304
  @res["Expires"] = (now + 31536000).to_s;
342
305
  @res["Cache-Control"] = "public, max-age=31536000";
343
306
  end
344
-
345
307
  end
346
308
  end
@@ -1,3 +1,3 @@
1
1
  module Grack
2
- VERSION = "2.0.0.rc2"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -5,6 +5,7 @@ require 'mocha'
5
5
  require 'digest/sha1'
6
6
 
7
7
  require_relative '../lib/grack/server.rb'
8
+ require_relative '../lib/grack/git.rb'
8
9
  require 'pp'
9
10
 
10
11
  class GitHttpTest < Test::Unit::TestCase
@@ -52,11 +53,13 @@ class GitHttpTest < Test::Unit::TestCase
52
53
  end
53
54
 
54
55
  def test_no_access_wrong_path_rcp
56
+ Grack::Git.any_instance.stubs(:valid_repo?).returns(false)
55
57
  post "/example-wrong/git-upload-pack"
56
58
  assert_equal 404, r.status
57
59
  end
58
60
 
59
61
  def test_upload_pack_rpc
62
+ Grack::Git.any_instance.stubs(:valid_repo?).returns(true)
60
63
  IO.stubs(:popen).returns(MockProcess.new)
61
64
  post "/example/git-upload-pack", {}, {"CONTENT_TYPE" => "application/x-git-upload-pack-request"}
62
65
  assert_equal 200, r.status
@@ -74,6 +77,7 @@ class GitHttpTest < Test::Unit::TestCase
74
77
  end
75
78
 
76
79
  def test_recieve_pack_rpc
80
+ Grack::Git.any_instance.stubs(:valid_repo?).returns(true)
77
81
  IO.stubs(:popen).yields(MockProcess.new)
78
82
  post "/example/git-receive-pack", {}, {"CONTENT_TYPE" => "application/x-git-receive-pack-request"}
79
83
  assert_equal 200, r.status
@@ -151,52 +155,56 @@ class GitHttpTest < Test::Unit::TestCase
151
155
 
152
156
  def test_git_config_receive_pack
153
157
  app1 = Grack::Server.new({:project_root => example})
158
+ app1.instance_variable_set(:@git, Grack::Git.new('git', example ))
154
159
  session = Rack::Test::Session.new(app1)
155
-
156
- app1.stubs(:get_git_config).with('http.receivepack').returns('')
160
+ git = Grack::Git
161
+ git.any_instance.stubs(:config).with('http.receivepack').returns('')
157
162
  session.get "/example/info/refs?service=git-receive-pack"
158
163
  assert_equal 404, session.last_response.status
159
164
 
160
- app1.stubs(:get_git_config).with('http.receivepack').returns('true')
165
+ git.any_instance.stubs(:config).with('http.receivepack').returns('true')
161
166
  session.get "/example/info/refs?service=git-receive-pack"
162
167
  assert_equal 200, session.last_response.status
163
168
 
164
- app1.stubs(:get_git_config).with('http.receivepack').returns('false')
169
+ git.any_instance.stubs(:config).with('http.receivepack').returns('false')
165
170
  session.get "/example/info/refs?service=git-receive-pack"
166
171
  assert_equal 404, session.last_response.status
167
172
  end
168
173
 
169
174
  def test_git_config_upload_pack
170
175
  app1 = Grack::Server.new({:project_root => example})
176
+ # app1.instance_variable_set(:@git, Grack::Git.new('git', example ))
171
177
  session = Rack::Test::Session.new(app1)
172
-
173
- app1.stubs(:get_git_config).with('http.uploadpack').returns('')
178
+ git = Grack::Git
179
+ git.any_instance.stubs(:config).with('http.uploadpack').returns('')
174
180
  session.get "/example/info/refs?service=git-upload-pack"
175
181
  assert_equal 200, session.last_response.status
176
182
 
177
- app1.stubs(:get_git_config).with('http.uploadpack').returns('true')
183
+ git.any_instance.stubs(:config).with('http.uploadpack').returns('true')
178
184
  session.get "/example/info/refs?service=git-upload-pack"
179
185
  assert_equal 200, session.last_response.status
180
186
 
181
- app1.stubs(:get_git_config).with('http.uploadpack').returns('false')
187
+ git.any_instance.stubs(:config).with('http.uploadpack').returns('false')
182
188
  session.get "/example/info/refs?service=git-upload-pack"
183
189
  assert_equal 404, session.last_response.status
184
190
  end
185
191
 
186
192
  def test_send_file
187
193
  app1 = app
188
- app1.instance_variable_set(:@dir, Dir.pwd)
194
+ app1.instance_variable_set(:@git, Grack::Git.new('git', Dir.pwd))
189
195
  # Reject path traversal
190
196
  assert_equal 404, app1.send_file('tests/../tests', 'text/plain').first
191
197
  # Reject paths starting with '|', avoid File.read('|touch /tmp/pawned; ls /tmp')
192
198
  assert_equal 404, app1.send_file('|tests', 'text/plain').first
193
199
  end
194
200
 
195
- def test_get_git_dir
201
+ def test_get_git
196
202
  # Guard against non-existent directories
197
- assert_equal false, app.get_git_dir('foobar')
203
+ git1 = Grack::Git.new('git', 'foobar')
204
+ assert_equal false, git1.valid_repo?
198
205
  # Guard against path traversal
199
- assert_equal false, app.get_git_dir('/../tests')
206
+ git2 = Grack::Git.new('git', '/../tests')
207
+ assert_equal false, git2.valid_repo?
200
208
  end
201
209
 
202
210
  private
@@ -241,7 +249,8 @@ class MockProcess
241
249
  def write(data)
242
250
  end
243
251
 
244
- def read(data)
252
+ def read(data = nil)
253
+ ''
245
254
  end
246
255
 
247
256
  def eof?
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-grack
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0.rc2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Chacon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-23 00:00:00.000000000 Z
11
+ date: 2015-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -41,7 +41,9 @@ dependencies:
41
41
  description: Ruby/Rack Git Smart-HTTP Server Handler
42
42
  email:
43
43
  - schacon@gmail.com
44
- executables: []
44
+ executables:
45
+ - console
46
+ - testserver
45
47
  extensions: []
46
48
  extra_rdoc_files: []
47
49
  files:
@@ -53,12 +55,15 @@ files:
53
55
  - Gemfile.lock
54
56
  - README.md
55
57
  - Rakefile
58
+ - bin/console
59
+ - bin/testserver
56
60
  - examples/dispatch.fcgi
57
61
  - grack.gemspec
58
62
  - install.txt
59
63
  - lib/grack.rb
60
64
  - lib/grack/auth.rb
61
65
  - lib/grack/bundle.rb
66
+ - lib/grack/git.rb
62
67
  - lib/grack/server.rb
63
68
  - lib/grack/version.rb
64
69
  - tests/main_test.rb
@@ -76,9 +81,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
76
81
  version: '0'
77
82
  required_rubygems_version: !ruby/object:Gem::Requirement
78
83
  requirements:
79
- - - ">"
84
+ - - ">="
80
85
  - !ruby/object:Gem::Version
81
- version: 1.3.1
86
+ version: '0'
82
87
  requirements: []
83
88
  rubyforge_project:
84
89
  rubygems_version: 2.2.2
@@ -87,3 +92,4 @@ specification_version: 4
87
92
  summary: Ruby/Rack Git Smart-HTTP Server Handler
88
93
  test_files:
89
94
  - tests/main_test.rb
95
+ has_rdoc: