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.
- checksums.yaml +7 -0
- data/.travis.yml +7 -0
- data/.yardopts +1 -0
- data/LICENSE +22 -0
- data/NEWS.md +19 -0
- data/README.md +211 -0
- data/Rakefile +222 -0
- data/lib/git_adapter.rb +1 -0
- data/lib/grack.rb +1 -0
- data/lib/grack/app.rb +482 -0
- data/lib/grack/compatible_git_adapter.rb +99 -0
- data/lib/grack/file_streamer.rb +41 -0
- data/lib/grack/git_adapter.rb +142 -0
- data/lib/grack/io_streamer.rb +45 -0
- data/tests/app_test.rb +534 -0
- data/tests/compatible_git_adapter_test.rb +137 -0
- data/tests/example/_git/COMMIT_EDITMSG +1 -0
- data/tests/example/_git/HEAD +1 -0
- data/tests/example/_git/config +6 -0
- data/tests/example/_git/description +1 -0
- data/tests/example/_git/hooks/applypatch-msg.sample +15 -0
- data/tests/example/_git/hooks/commit-msg.sample +24 -0
- data/tests/example/_git/hooks/post-commit.sample +8 -0
- data/tests/example/_git/hooks/post-receive.sample +15 -0
- data/tests/example/_git/hooks/post-update.sample +8 -0
- data/tests/example/_git/hooks/pre-applypatch.sample +14 -0
- data/tests/example/_git/hooks/pre-commit.sample +50 -0
- data/tests/example/_git/hooks/pre-rebase.sample +169 -0
- data/tests/example/_git/hooks/prepare-commit-msg.sample +36 -0
- data/tests/example/_git/hooks/update.sample +128 -0
- data/tests/example/_git/index +0 -0
- data/tests/example/_git/info/exclude +6 -0
- data/tests/example/_git/info/refs +1 -0
- data/tests/example/_git/logs/HEAD +1 -0
- data/tests/example/_git/logs/refs/heads/master +1 -0
- data/tests/example/_git/objects/31/d73eb4914a8ddb6cb0e4adf250777161118f90 +0 -0
- data/tests/example/_git/objects/cb/067e06bdf6e34d4abebf6cf2de85d65a52c65e +0 -0
- data/tests/example/_git/objects/ce/013625030ba8dba906f756967f9e9ca394464a +0 -0
- data/tests/example/_git/objects/info/packs +2 -0
- data/tests/example/_git/objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.idx +0 -0
- data/tests/example/_git/objects/pack/pack-62c9f443d8405cd6da92dcbb4f849cc01a339c06.pack +0 -0
- data/tests/example/_git/refs/heads/master +1 -0
- data/tests/file_streamer_test.rb +37 -0
- data/tests/git_adapter_test.rb +104 -0
- data/tests/io_streamer_test.rb +36 -0
- data/tests/test_helper.rb +36 -0
- metadata +292 -19
- 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
|
data/tests/app_test.rb
ADDED
@@ -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
|