grack 0.0.2 → 0.1.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +7 -0
  3. data/.yardopts +1 -0
  4. data/LICENSE +22 -0
  5. data/NEWS.md +19 -0
  6. data/README.md +211 -0
  7. data/Rakefile +222 -0
  8. data/lib/git_adapter.rb +1 -0
  9. data/lib/grack.rb +1 -0
  10. data/lib/grack/app.rb +482 -0
  11. data/lib/grack/compatible_git_adapter.rb +99 -0
  12. data/lib/grack/file_streamer.rb +41 -0
  13. data/lib/grack/git_adapter.rb +142 -0
  14. data/lib/grack/io_streamer.rb +45 -0
  15. data/tests/app_test.rb +534 -0
  16. data/tests/compatible_git_adapter_test.rb +137 -0
  17. data/tests/example/_git/COMMIT_EDITMSG +1 -0
  18. data/tests/example/_git/HEAD +1 -0
  19. data/tests/example/_git/config +6 -0
  20. data/tests/example/_git/description +1 -0
  21. data/tests/example/_git/hooks/applypatch-msg.sample +15 -0
  22. data/tests/example/_git/hooks/commit-msg.sample +24 -0
  23. data/tests/example/_git/hooks/post-commit.sample +8 -0
  24. data/tests/example/_git/hooks/post-receive.sample +15 -0
  25. data/tests/example/_git/hooks/post-update.sample +8 -0
  26. data/tests/example/_git/hooks/pre-applypatch.sample +14 -0
  27. data/tests/example/_git/hooks/pre-commit.sample +50 -0
  28. data/tests/example/_git/hooks/pre-rebase.sample +169 -0
  29. data/tests/example/_git/hooks/prepare-commit-msg.sample +36 -0
  30. data/tests/example/_git/hooks/update.sample +128 -0
  31. data/tests/example/_git/index +0 -0
  32. data/tests/example/_git/info/exclude +6 -0
  33. data/tests/example/_git/info/refs +1 -0
  34. data/tests/example/_git/logs/HEAD +1 -0
  35. data/tests/example/_git/logs/refs/heads/master +1 -0
  36. data/tests/example/_git/objects/31/d73eb4914a8ddb6cb0e4adf250777161118f90 +0 -0
  37. data/tests/example/_git/objects/cb/067e06bdf6e34d4abebf6cf2de85d65a52c65e +0 -0
  38. data/tests/example/_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a +0 -0
  39. data/tests/example/_git/objects/info/packs +2 -0
  40. data/tests/example/_git/objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.idx +0 -0
  41. data/tests/example/_git/objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.pack +0 -0
  42. data/tests/example/_git/refs/heads/master +1 -0
  43. data/tests/file_streamer_test.rb +37 -0
  44. data/tests/git_adapter_test.rb +104 -0
  45. data/tests/io_streamer_test.rb +36 -0
  46. data/tests/test_helper.rb +36 -0
  47. metadata +292 -19
  48. data/lib/git_http.rb +0 -304
@@ -0,0 +1,99 @@
1
+ require 'pathname'
2
+
3
+ require 'grack/file_streamer'
4
+
5
+ module Grack
6
+ ##
7
+ # @deprecated Upgrade to a Git adapter implementation that implements the new
8
+ # interface.
9
+ #
10
+ # A Git adapter adapter (yes an adapter for an adapter) that allows old-style
11
+ # Git adapter classes to be used.
12
+ class CompatibleGitAdapter
13
+ ##
14
+ # Creates a new instance of this adapter.
15
+ #
16
+ # @param [GitAdapter-like] adapter an old-style Git adapter instance to
17
+ # wrap.
18
+ def initialize(adapter)
19
+ @adapter = adapter
20
+ end
21
+
22
+ ##
23
+ # The path to the repository on which to operate.
24
+ attr_reader :repository_path
25
+
26
+ ##
27
+ # Sets the path to the repository on which to operate.
28
+ def repository_path=(path)
29
+ @repository_path = Pathname.new(path)
30
+ end
31
+
32
+ ##
33
+ # @return [Boolean] +true+ if the repository exists; otherwise, +false+.
34
+ def exist?
35
+ repository_path.exist?
36
+ end
37
+
38
+ ##
39
+ # Process the pack file exchange protocol.
40
+ #
41
+ # @param [String] pack_type the type of pack exchange to perform.
42
+ # @param [#read] io_in a readable, IO-like object providing client input
43
+ # data.
44
+ # @param [#write] io_out a writable, IO-like object sending output data to
45
+ # the client.
46
+ # @param [Hash] opts options to pass to the Git adapter's #handle_pack
47
+ # method.
48
+ # @option opts [Boolean] :advertise_refs (false)
49
+ def handle_pack(pack_type, io_in, io_out, opts = {})
50
+ msg = ''
51
+ msg = io_in.read unless opts[:advertise_refs]
52
+
53
+ @adapter.send(
54
+ pack_type.sub(/^git-/, '').gsub('-', '_').to_sym,
55
+ repository_path.to_s,
56
+ opts.merge(:msg => msg)
57
+ ) do |result|
58
+ while chunk = result.read(8192) do
59
+ io_out.write(chunk)
60
+ end
61
+ end
62
+ end
63
+
64
+ ##
65
+ # Returns an object suitable for use as a Rack response body to provide the
66
+ # content of a file at _path_.
67
+ #
68
+ # @param [Pathname] path the path to a file within the repository.
69
+ #
70
+ # @return [FileStreamer] a Rack response body that can stream the file
71
+ # content at _path_.
72
+ # @return [nil] if _path_ does not exist.
73
+ def file(path)
74
+ full_path = @repository_path + path
75
+ return nil unless full_path.exist?
76
+ FileStreamer.new(full_path)
77
+ end
78
+
79
+ ##
80
+ # Triggers generation of data necessary to service Git Basic HTTP clients.
81
+ #
82
+ # @return [void]
83
+ def update_server_info
84
+ @adapter.update_server_info(repository_path.to_s)
85
+ end
86
+
87
+ ##
88
+ # @return [Boolean] +true+ if pushes should be allowed; otherwise; +false+.
89
+ def allow_push?
90
+ @adapter.get_config_setting('receivepack') == 'true'
91
+ end
92
+
93
+ ##
94
+ # @return [Boolean] +true+ if pulls should be allowed; otherwise; +false+.
95
+ def allow_pull?
96
+ @adapter.get_config_setting('uploadpack') != 'false'
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,41 @@
1
+ require 'pathname'
2
+
3
+ require 'grack/io_streamer'
4
+
5
+ module Grack
6
+ ##
7
+ # A Rack body implementation that streams a given file in chunks for a Rack
8
+ # response.
9
+ class FileStreamer < IOStreamer
10
+ ##
11
+ # Creates a new instance of this object.
12
+ #
13
+ # @param [Pathname, String] path a path to a file.
14
+ def initialize(path)
15
+ @path = Pathname.new(path).expand_path
16
+ end
17
+
18
+ ##
19
+ # In order to support X-Sendfile when available, this method returns the
20
+ # path to the file the web server would use to provide the content.
21
+ #
22
+ # @return [String] the path to the file.
23
+ def to_path
24
+ @path.to_s
25
+ end
26
+
27
+ ##
28
+ # The last modified time to report for the Rack response.
29
+ def mtime
30
+ @path.mtime
31
+ end
32
+
33
+ private
34
+
35
+ ##
36
+ # @yieldparam [#read] io the opened file.
37
+ def with_io(&b)
38
+ @path.open(&b)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,142 @@
1
+ require 'pathname'
2
+
3
+ require 'grack/file_streamer'
4
+
5
+ module Grack
6
+ ##
7
+ # A wrapper for interacting with Git repositories using the git command line
8
+ # tool.
9
+ class GitAdapter
10
+ ##
11
+ # The number of bytes to read at a time from IO streams.
12
+ READ_SIZE = 32768
13
+
14
+ ##
15
+ # Creates a new instance of this adapter.
16
+ #
17
+ # @param [String] bin_path the path to use for the Git binary.
18
+ def initialize(bin_path = 'git')
19
+ @repository_path = nil
20
+ @git_path = bin_path
21
+ end
22
+
23
+ ##
24
+ # The path to the repository on which to operate.
25
+ attr_reader :repository_path
26
+
27
+ ##
28
+ # Sets the path to the repository on which to operate.
29
+ def repository_path=(path)
30
+ @repository_path = Pathname.new(path)
31
+ end
32
+
33
+ ##
34
+ # @return [Boolean] +true+ if the repository exists; otherwise, +false+.
35
+ def exist?
36
+ repository_path.exist?
37
+ end
38
+
39
+ ##
40
+ # Process the pack file exchange protocol.
41
+ #
42
+ # @param [String] pack_type the type of pack exchange to perform.
43
+ # @param [#read] io_in a readable, IO-like object providing client input
44
+ # data.
45
+ # @param [#write] io_out a writable, IO-like object sending output data to
46
+ # the client.
47
+ # @param [Hash] opts options to pass to the Git adapter's #handle_pack
48
+ # method.
49
+ # @option opts [Boolean] :advertise_refs (false)
50
+ def handle_pack(pack_type, io_in, io_out, opts = {})
51
+ args = %w{--stateless-rpc}
52
+ if opts.fetch(:advertise_refs, false)
53
+ str = "# service=#{pack_type}\n"
54
+ io_out.write('%04x' % (str.size + 4))
55
+ io_out.write(str)
56
+ io_out.write('0000')
57
+ args << '--advertise-refs'
58
+ end
59
+ args << repository_path.to_s
60
+ command(pack_type.sub(/^git-/, ''), args, io_in, io_out)
61
+ end
62
+
63
+ ##
64
+ # Returns an object suitable for use as a Rack response body to provide the
65
+ # content of a file at _path_.
66
+ #
67
+ # @param [Pathname] path the path to a file within the repository.
68
+ #
69
+ # @return [FileStreamer] a Rack response body that can stream the file
70
+ # content at _path_.
71
+ # @return [nil] if _path_ does not exist.
72
+ def file(path)
73
+ full_path = @repository_path + path
74
+ return nil unless full_path.exist?
75
+ FileStreamer.new(full_path)
76
+ end
77
+
78
+ ##
79
+ # Triggers generation of data necessary to service Git Basic HTTP clients.
80
+ #
81
+ # @return [void]
82
+ def update_server_info
83
+ command('update-server-info', [], nil, nil, repository_path)
84
+ end
85
+
86
+ ##
87
+ # @return [Boolean] +true+ if pushes should be allowed; otherwise; +false+.
88
+ def allow_push?
89
+ config('http.receivepack') == 'true'
90
+ end
91
+
92
+ ##
93
+ # @return [Boolean] +true+ if pulls should be allowed; otherwise; +false+.
94
+ def allow_pull?
95
+ config('http.uploadpack') != 'false'
96
+ end
97
+
98
+
99
+ private
100
+
101
+ ##
102
+ # The path to use for running the git utility.
103
+ attr_reader :git_path
104
+
105
+ ##
106
+ # @param [String] key a key to look up in the Git repository configuration.
107
+ #
108
+ # @return [String] the value for the given key.
109
+ def config(key)
110
+ capture_io = StringIO.new
111
+ command('config', ['--local', key], nil, capture_io, repository_path.to_s)
112
+ capture_io.string.chomp
113
+ end
114
+
115
+ ##
116
+ # Runs the Git utilty with the given subcommand.
117
+ #
118
+ # @param [String] cmd the Git subcommand to invoke.
119
+ # @param [Array<String>] args additional arguments for the command.
120
+ # @param [#read, nil] io_in a readable, IO-like source of data to write to
121
+ # the Git command.
122
+ # @param [#write, nil] io_out a writable, IO-like sink for output produced
123
+ # by the Git command.
124
+ # @param [String, nil] dir a directory to switch to before invoking the Git
125
+ # command.
126
+ def command(cmd, args, io_in, io_out, dir = nil)
127
+ cmd = [git_path, cmd] + args
128
+ opts = {:err => :close}
129
+ opts[:chdir] = dir unless dir.nil?
130
+ cmd << opts
131
+ IO.popen(cmd, 'r+b') do |pipe|
132
+ while ! io_in.nil? && chunk = io_in.read(READ_SIZE) do
133
+ pipe.write(chunk)
134
+ end
135
+ pipe.close_write
136
+ while chunk = pipe.read(READ_SIZE) do
137
+ io_out.write(chunk) unless io_out.nil?
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,45 @@
1
+ module Grack
2
+ ##
3
+ # A Rack body implementation that streams a given IO object in chunks for a
4
+ # Rack response.
5
+ class IOStreamer
6
+ ##
7
+ # The number of bytes to read at a time from IO streams.
8
+ READ_SIZE = 32768
9
+
10
+ ##
11
+ # Creates a new instance of this object.
12
+ #
13
+ # @param [#read] io a readable, IO-like object.
14
+ # @param [Time] mtime a timestamp to use for the last modified header in the
15
+ # response.
16
+ def initialize(io, mtime)
17
+ @io = io
18
+ @mtime = mtime
19
+ end
20
+
21
+ ##
22
+ # The last modified time to report for the Rack response.
23
+ attr_reader :mtime
24
+
25
+ ##
26
+ # Iterates over the wrapped IO object in chunks, yielding each one.
27
+ #
28
+ # @yieldparam [String] chunk a chunk read from the wrapped IO object.
29
+ def each
30
+ with_io do |io|
31
+ while chunk = io.read(READ_SIZE) do
32
+ yield(chunk)
33
+ end
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ ##
40
+ # @yieldparam [#read] io the wrapped IO object.
41
+ def with_io
42
+ yield(@io)
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,534 @@
1
+ require_relative 'test_helper'
2
+
3
+ require 'digest/sha1'
4
+ require 'minitest/autorun'
5
+ require 'minitest/unit'
6
+ require 'mocha/setup'
7
+ require 'pathname'
8
+ require 'rack/test'
9
+ require 'tempfile'
10
+ require 'zlib'
11
+
12
+ require 'grack/app'
13
+ require 'grack/git_adapter'
14
+
15
+ class AppTest < Minitest::Test
16
+ include Rack::Test::Methods
17
+ include Grack
18
+
19
+ def example_repo_urn
20
+ '/example_repo.git'
21
+ end
22
+
23
+ def app_config
24
+ {
25
+ :root => repositories_root,
26
+ :allow_pull => true,
27
+ :allow_push => true,
28
+ :git_adapter_factory => ->{ GitAdapter.new(git_path) }
29
+ }
30
+ end
31
+
32
+ def app
33
+ App.new(app_config)
34
+ end
35
+
36
+ def setup
37
+ init_example_repository
38
+ end
39
+
40
+ def teardown
41
+ remove_example_repository
42
+ end
43
+
44
+ def test_upload_pack_advertisement
45
+ get "#{example_repo_urn}/info/refs?service=git-upload-pack"
46
+ assert_equal 200, r.status
47
+ assert_equal 'application/x-git-upload-pack-advertisement', r.headers['Content-Type']
48
+ assert_equal '001e# service=git-upload-pack', r.body.split("\n").first
49
+ assert_match 'multi_ack_detailed', r.body
50
+ end
51
+
52
+ def test_no_access_upload_pack_advertisement
53
+ session = Rack::Test::Session.new(
54
+ App.new(app_config.merge!(:allow_pull => false))
55
+ )
56
+
57
+ session.get "#{example_repo_urn}/info/refs?service=git-upload-pack"
58
+ assert_equal 403, session.last_response.status
59
+ end
60
+
61
+ def test_no_access_wrong_content_type_up
62
+ post "#{example_repo_urn}/git-upload-pack"
63
+ assert_equal 403, r.status
64
+ end
65
+
66
+ def test_no_access_wrong_content_type_rp
67
+ post "#{example_repo_urn}/git-receive-pack"
68
+ assert_equal 403, r.status
69
+ end
70
+
71
+ def test_no_access_wrong_method_rcp
72
+ get "#{example_repo_urn}/git-upload-pack"
73
+ assert_equal 400, r.status
74
+ get "#{example_repo_urn}/git-upload-pack", {}, {'SERVER_PROTOCOL' => 'HTTP/1.1'}
75
+ assert_equal 405, r.status
76
+ end
77
+
78
+ def test_no_access_wrong_command_rcp
79
+ post "#{example_repo_urn}/git-upload-packfile"
80
+ assert_equal 404, r.status
81
+ end
82
+
83
+ def test_no_access_wrong_path_rcp
84
+ post "/example-wrong/git-upload-pack"
85
+ assert_equal 404, r.status
86
+ end
87
+
88
+ def test_upload_pack_rpc
89
+ IO.stubs(:popen).returns(MockProcess.new)
90
+
91
+ post(
92
+ "#{example_repo_urn}/git-upload-pack",
93
+ {},
94
+ {'CONTENT_TYPE' => 'application/x-git-upload-pack-request'}
95
+ )
96
+ assert_equal 200, r.status
97
+ assert_equal 'application/x-git-upload-pack-result', r.headers['Content-Type']
98
+ end
99
+
100
+ def test_no_access_upload_pack_rpc
101
+ session = Rack::Test::Session.new(
102
+ App.new(app_config.merge!(:allow_pull => false))
103
+ )
104
+
105
+ session.post(
106
+ "#{example_repo_urn}/git-upload-pack",
107
+ {},
108
+ {'CONTENT_TYPE' => 'application/x-git-upload-pack-request'}
109
+ )
110
+ assert_equal 403, session.last_response.status
111
+ end
112
+
113
+ def test_upload_pack_rpc_compressed
114
+ IO.stubs(:popen).returns(MockProcess.new)
115
+
116
+ content = StringIO.new
117
+ gz = Zlib::GzipWriter.new(content)
118
+ gz.write('foo')
119
+ gz.close
120
+
121
+ post(
122
+ "#{example_repo_urn}/git-upload-pack",
123
+ content.string,
124
+ {
125
+ 'CONTENT_TYPE' => 'application/x-git-upload-pack-request',
126
+ 'HTTP_CONTENT_ENCODING' => 'gzip',
127
+ }
128
+ )
129
+ assert_equal 200, r.status
130
+ assert_equal 'application/x-git-upload-pack-result',
131
+ r.headers['Content-Type']
132
+ end
133
+
134
+ def test_receive_pack_advertisement
135
+ get "#{example_repo_urn}/info/refs?service=git-receive-pack"
136
+ assert_equal 200, r.status
137
+ assert_equal 'application/x-git-receive-pack-advertisement',
138
+ r.headers['Content-Type']
139
+ assert_equal '001f# service=git-receive-pack', r.body.split("\n").first
140
+ assert_match 'report-status', r.body
141
+ assert_match 'delete-refs', r.body
142
+ assert_match 'ofs-delta', r.body
143
+ end
144
+
145
+ def test_no_access_receive_pack_advertisement
146
+ session = Rack::Test::Session.new(
147
+ App.new(app_config.merge!(:allow_push => false))
148
+ )
149
+
150
+ session.get "#{example_repo_urn}/info/refs?service=git-receive-pack"
151
+ assert_equal 403, session.last_response.status
152
+ end
153
+
154
+ def test_receive_pack_rpc
155
+ IO.stubs(:popen).returns(MockProcess.new)
156
+
157
+ post(
158
+ "#{example_repo_urn}/git-receive-pack",
159
+ {},
160
+ {'CONTENT_TYPE' => 'application/x-git-receive-pack-request'}
161
+ )
162
+ assert_equal 200, r.status
163
+ assert_equal 'application/x-git-receive-pack-result',
164
+ r.headers['Content-Type']
165
+ end
166
+
167
+ def test_no_access_receive_pack_rpc
168
+ session = Rack::Test::Session.new(
169
+ App.new(app_config.merge!(:allow_push => false))
170
+ )
171
+
172
+ session.post "#{example_repo_urn}/git-receive-pack",
173
+ {},
174
+ {'CONTENT_TYPE' => 'application/x-git-receive-pack-request'}
175
+ assert_equal 403, session.last_response.status
176
+ end
177
+
178
+ def test_info_refs_dumb
179
+ get "#{example_repo_urn}/info/refs"
180
+ assert_equal 200, r.status
181
+ end
182
+
183
+ def test_no_access_info_refs_dumb
184
+ session = Rack::Test::Session.new(
185
+ App.new(app_config.merge!(:allow_pull => false))
186
+ )
187
+
188
+ session.get "#{example_repo_urn}/info/refs"
189
+ assert_equal 403, session.last_response.status
190
+ end
191
+
192
+ def test_info_packs
193
+ get "#{example_repo_urn}/objects/info/packs"
194
+ assert_equal 200, r.status
195
+ assert_match /P pack-(.*?).pack/, r.body
196
+ end
197
+
198
+ def test_no_access_info_packs
199
+ session = Rack::Test::Session.new(
200
+ App.new(app_config.merge!(:allow_pull => false))
201
+ )
202
+
203
+ session.get "#{example_repo_urn}/objects/info/packs"
204
+ assert_equal 403, session.last_response.status
205
+ end
206
+
207
+ def test_loose_objects
208
+ obj_path = 'objects/31/d73eb4914a8ddb6cb0e4adf250777161118f90'
209
+ obj_file = File.join(example_repo, obj_path)
210
+ content = File.open(obj_file, 'rb') { |f| f.read }
211
+
212
+ get "#{example_repo_urn}/#{obj_path}"
213
+ assert_equal 200, r.status
214
+ assert_equal content, r.body
215
+ end
216
+
217
+ def test_no_access_loose_objects
218
+ obj_path = 'objects/31/d73eb4914a8ddb6cb0e4adf250777161118f90'
219
+ session = Rack::Test::Session.new(
220
+ App.new(app_config.merge!(:allow_pull => false))
221
+ )
222
+
223
+ session.get "#{example_repo_urn}/#{obj_path}"
224
+ assert_equal 403, session.last_response.status
225
+ end
226
+
227
+ def test_pack_file
228
+ pack_path =
229
+ 'objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.pack'
230
+ pack_file = File.join(example_repo, pack_path)
231
+ content = File.open(pack_file, 'rb') { |f| f.read }
232
+
233
+ get "#{example_repo_urn}/#{pack_path}"
234
+ assert_equal 200, r.status
235
+ assert_equal content, r.body
236
+ end
237
+
238
+ def test_no_access_pack_file
239
+ pack_path =
240
+ 'objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.pack'
241
+ session = Rack::Test::Session.new(
242
+ App.new(app_config.merge!(:allow_pull => false))
243
+ )
244
+
245
+ session.get "#{example_repo_urn}/#{pack_path}"
246
+ assert_equal 403, session.last_response.status
247
+ end
248
+
249
+ def test_index_file
250
+ idx_path = 'objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.idx'
251
+ idx_file = File.join(example_repo, idx_path)
252
+ content = File.open(idx_file, 'rb') { |f| f.read }
253
+
254
+ get "#{example_repo_urn}/#{idx_path}"
255
+ assert_equal 200, r.status
256
+ assert_equal content, r.body
257
+ end
258
+
259
+ def test_no_access_index_file
260
+ idx_path = 'objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.idx'
261
+ session = Rack::Test::Session.new(
262
+ App.new(app_config.merge!(:allow_pull => false))
263
+ )
264
+
265
+ session.get "#{example_repo_urn}/#{idx_path}"
266
+ assert_equal 403, session.last_response.status
267
+ end
268
+
269
+ def test_text_file
270
+ head_file = File.join(example_repo, 'HEAD')
271
+ content = File.open(head_file, 'rb') { |f| f.read }
272
+
273
+ get "#{example_repo_urn}/HEAD"
274
+ assert_equal 200, r.status
275
+ assert_equal content, r.body
276
+ end
277
+
278
+ def test_no_access_text_file
279
+ session = Rack::Test::Session.new(
280
+ App.new(app_config.merge!(:allow_pull => false))
281
+ )
282
+
283
+ session.get "#{example_repo_urn}/HEAD"
284
+ assert_equal 403, session.last_response.status
285
+ end
286
+
287
+ def test_config_allow_pull_off
288
+ session = Rack::Test::Session.new(
289
+ App.new(app_config.merge(:allow_pull => false))
290
+ )
291
+ session.get "#{example_repo_urn}/info/refs?service=git-upload-pack"
292
+ assert_equal 403, session.last_response.status
293
+ end
294
+
295
+ def test_config_allow_push_off
296
+ session = Rack::Test::Session.new(
297
+ App.new(app_config.merge(:allow_push => false))
298
+ )
299
+ session.get "#{example_repo_urn}/info/refs?service=git-receive-pack"
300
+ assert_equal 403, session.last_response.status
301
+ end
302
+
303
+ def test_config_bad_service
304
+ get "#{example_repo_urn}/info/refs?service=git-receive-packfile"
305
+ assert_equal 404, r.status
306
+ end
307
+
308
+ def test_git_adapter_forbid_push
309
+ GitAdapter.any_instance.stubs(:allow_push?).returns(false)
310
+
311
+ app = App.new({
312
+ :root => repositories_root
313
+ })
314
+ session = Rack::Test::Session.new(app)
315
+ session.get "#{example_repo_urn}/info/refs?service=git-receive-pack"
316
+ assert_equal 403, session.last_response.status
317
+ end
318
+
319
+ def test_git_adapter_allow_push
320
+ GitAdapter.any_instance.stubs(:allow_push?).returns(true)
321
+
322
+ app = App.new(:root => repositories_root)
323
+ session = Rack::Test::Session.new(app)
324
+ session.get "#{example_repo_urn}/info/refs?service=git-receive-pack"
325
+ assert_equal 200, session.last_response.status
326
+ end
327
+
328
+ def test_git_adapter_forbid_pull
329
+ GitAdapter.any_instance.stubs(:allow_pull?).returns(false)
330
+
331
+ app = App.new(:root => repositories_root)
332
+ session = Rack::Test::Session.new(app)
333
+ session.get "#{example_repo_urn}/info/refs?service=git-upload-pack"
334
+ assert_equal 403, session.last_response.status
335
+ end
336
+
337
+ def test_git_adapter_allow_pull
338
+ GitAdapter.any_instance.stubs(:allow_pull?).returns(true)
339
+
340
+ app = App.new(:root => repositories_root)
341
+ session = Rack::Test::Session.new(app)
342
+ session.get "#{example_repo_urn}/info/refs?service=git-upload-pack"
343
+ assert_equal 200, session.last_response.status
344
+ end
345
+
346
+ def test_reject_bad_uri
347
+ get '/../HEAD'
348
+ assert_equal 400, r.status
349
+ get "#{example_repo_urn}/../HEAD"
350
+ assert_equal 400, r.status
351
+ get '/./HEAD'
352
+ assert_equal 400, r.status
353
+ get "#{example_repo_urn}/./HEAD"
354
+ assert_equal 400, r.status
355
+
356
+ get '/%2e%2e/HEAD'
357
+ assert_equal 400, r.status
358
+ get "#{example_repo_urn}/%2e%2e/HEAD"
359
+ assert_equal 400, r.status
360
+ get '/%2e/HEAD'
361
+ assert_equal 400, r.status
362
+ get "#{example_repo_urn}/%2e/HEAD"
363
+ assert_equal 400, r.status
364
+ end
365
+
366
+ def test_not_found_in_empty_repo
367
+ empty_dir = repositories_root + 'empty-dir'
368
+ empty_dir.mkdir
369
+
370
+ example_repo_urn = '/empty-dir'
371
+
372
+ get "#{example_repo_urn}/info/refs"
373
+ assert_equal 404, r.status
374
+ get "#{example_repo_urn}/info/alternates"
375
+ assert_equal 404, r.status
376
+ get "#{example_repo_urn}/info/http-alternates"
377
+ assert_equal 404, r.status
378
+ get "#{example_repo_urn}/info/packs"
379
+ assert_equal 404, r.status
380
+ get "#{example_repo_urn}/objects/00/00000000000000000000000000000000000000"
381
+ assert_equal 404, r.status
382
+ get "#{example_repo_urn}/objects/packs/pack-0000000000000000000000000000000000000000.pack"
383
+ assert_equal 404, r.status
384
+ get "#{example_repo_urn}/objects/packs/pack-0000000000000000000000000000000000000000.idx"
385
+ assert_equal 404, r.status
386
+ ensure
387
+ empty_dir.rmdir if empty_dir.exist?
388
+ end
389
+
390
+ def test_not_found_in_nonexistent_repo
391
+ example_repo_urn = '/no-dir'
392
+
393
+ get "#{example_repo_urn}/info/refs"
394
+ assert_equal 404, r.status
395
+ get "#{example_repo_urn}/info/alternates"
396
+ assert_equal 404, r.status
397
+ get "#{example_repo_urn}/info/http-alternates"
398
+ assert_equal 404, r.status
399
+ get "#{example_repo_urn}/info/packs"
400
+ assert_equal 404, r.status
401
+ get "#{example_repo_urn}/objects/00/00000000000000000000000000000000000000"
402
+ assert_equal 404, r.status
403
+ get "#{example_repo_urn}/objects/packs/pack-0000000000000000000000000000000000000000.pack"
404
+ assert_equal 404, r.status
405
+ get "#{example_repo_urn}/objects/packs/pack-0000000000000000000000000000000000000000.idx"
406
+ assert_equal 404, r.status
407
+ end
408
+
409
+ def test_config_project_root_used_when_root_not_set
410
+ session = Rack::Test::Session.new(
411
+ App.new(:project_root => repositories_root)
412
+ )
413
+
414
+ session.get "#{example_repo_urn}/info/refs"
415
+ assert_equal 200, session.last_response.status
416
+ end
417
+
418
+ def test_config_project_root_ignored_when_root_is_set
419
+ session = Rack::Test::Session.new(
420
+ App.new(:project_root => 'unlikely/path', :root => repositories_root)
421
+ )
422
+
423
+ session.get "#{example_repo_urn}/info/refs"
424
+ assert_equal 200, session.last_response.status
425
+ end
426
+
427
+ def test_config_upload_pack_used_when_allow_pull_not_set
428
+ session = Rack::Test::Session.new(
429
+ App.new(:root => repositories_root, :upload_pack => false)
430
+ )
431
+
432
+ session.get "#{example_repo_urn}/info/refs?service=git-upload-pack"
433
+ assert_equal 403, session.last_response.status
434
+ end
435
+
436
+ def test_config_upload_pack_ignored_when_allow_pull_is_set
437
+ session = Rack::Test::Session.new(
438
+ App.new(
439
+ :root => repositories_root, :upload_pack => true, :allow_pull => false
440
+ )
441
+ )
442
+
443
+ session.get "#{example_repo_urn}/info/refs?service=git-upload-pack"
444
+ assert_equal 403, session.last_response.status
445
+ end
446
+
447
+ def test_config_receive_pack_used_when_allow_push_not_set
448
+ session = Rack::Test::Session.new(
449
+ App.new(:root => repositories_root, :receive_pack => false)
450
+ )
451
+
452
+ session.get "#{example_repo_urn}/info/refs?service=git-receive-pack"
453
+ assert_equal 403, session.last_response.status
454
+ end
455
+
456
+ def test_config_receive_pack_ignored_when_allow_push_is_set
457
+ session = Rack::Test::Session.new(
458
+ App.new(
459
+ :root => repositories_root, :receive_pack => true, :allow_push => false
460
+ )
461
+ )
462
+
463
+ session.get "#{example_repo_urn}/info/refs?service=git-receive-pack"
464
+ assert_equal 403, session.last_response.status
465
+ end
466
+
467
+ def test_config_adapter_with_GitAdapter
468
+ session = Rack::Test::Session.new(
469
+ App.new(:root => repositories_root, :adapter => GitAdapter)
470
+ )
471
+
472
+ session.get "#{example_repo_urn}/objects/info/packs"
473
+ assert_equal 200, session.last_response.status
474
+ assert_match /P pack-(.*?).pack/, session.last_response.body
475
+ end
476
+
477
+ def test_config_adapter_with_custom_adapter
478
+ git_adapter = mock('git_adapter')
479
+ git_adapter.
480
+ expects(:update_server_info).
481
+ with("#{repositories_root}#{example_repo_urn}")
482
+ git_adapter_class = mock('git_adapter_class')
483
+ git_adapter_class.expects(:new).with.returns(git_adapter)
484
+ session = Rack::Test::Session.new(
485
+ App.new(
486
+ :root => repositories_root,
487
+ :allow_pull => true,
488
+ :adapter => git_adapter_class
489
+ )
490
+ )
491
+
492
+ session.get "#{example_repo_urn}/info/refs"
493
+ assert_equal 200, session.last_response.status
494
+ end
495
+
496
+ def test_config_adapter_ignored_when_adapter_factory_is_set
497
+ git_adapter_class = mock('git_adapter_class')
498
+ session = Rack::Test::Session.new(
499
+ App.new(
500
+ :root => repositories_root,
501
+ :adapter => git_adapter_class,
502
+ :git_adapter_factory => ->{ GitAdapter.new(git_path) }
503
+ )
504
+ )
505
+
506
+ session.get "#{example_repo_urn}/info/refs"
507
+ assert_equal 200, session.last_response.status
508
+ end
509
+
510
+ private
511
+
512
+ def r
513
+ last_response
514
+ end
515
+
516
+ end
517
+
518
+ class MockProcess
519
+
520
+ def initialize
521
+ @counter = 0
522
+ end
523
+
524
+ def close_write
525
+ end
526
+
527
+ def write(data)
528
+ end
529
+
530
+ def read(length = nil, buffer = nil)
531
+ nil
532
+ end
533
+
534
+ end