grack 0.0.2 → 0.1.0.pre1

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.
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