git-webby 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.rdoc +74 -0
  2. data/Rakefile +44 -0
  3. data/doc/releases/v0.1.0.rdoc +11 -0
  4. data/git-webby.gemspec +32 -0
  5. data/lib/git/webby.rb +145 -0
  6. data/lib/git/webby/http_backend.rb +248 -0
  7. data/lib/git/webby/version.rb +14 -0
  8. data/test/config_test.rb +30 -0
  9. data/test/fixtures/config.yml +7 -0
  10. data/test/fixtures/htpasswd +4 -0
  11. data/test/fixtures/mycode.git/HEAD +1 -0
  12. data/test/fixtures/mycode.git/config +4 -0
  13. data/test/fixtures/mycode.git/description +1 -0
  14. data/test/fixtures/mycode.git/hooks/applypatch-msg.sample +15 -0
  15. data/test/fixtures/mycode.git/hooks/commit-msg.sample +24 -0
  16. data/test/fixtures/mycode.git/hooks/post-commit.sample +8 -0
  17. data/test/fixtures/mycode.git/hooks/post-receive.sample +15 -0
  18. data/test/fixtures/mycode.git/hooks/post-update.sample +8 -0
  19. data/test/fixtures/mycode.git/hooks/pre-applypatch.sample +14 -0
  20. data/test/fixtures/mycode.git/hooks/pre-commit.sample +46 -0
  21. data/test/fixtures/mycode.git/hooks/pre-rebase.sample +169 -0
  22. data/test/fixtures/mycode.git/hooks/prepare-commit-msg.sample +36 -0
  23. data/test/fixtures/mycode.git/hooks/update.sample +128 -0
  24. data/test/fixtures/mycode.git/info/exclude +6 -0
  25. data/test/fixtures/mycode.git/info/refs +3 -0
  26. data/test/fixtures/mycode.git/objects/02/83eb96425444e17b97182e1ba9f216cc67c132 +0 -0
  27. data/test/fixtures/mycode.git/objects/03/9927042df267a1bc606fc4485b7a79b6a9e3cd +1 -0
  28. data/test/fixtures/mycode.git/objects/4b/825dc642cb6eb9a060e54bf8d69288fbee4904 +0 -0
  29. data/test/fixtures/mycode.git/objects/5e/54a0767e0c380f3baab17938d68c7f464cf171 +1 -0
  30. data/test/fixtures/mycode.git/objects/71/6e9568eed27d5ee4378b3ecf6dd095a547bde9 +1 -0
  31. data/test/fixtures/mycode.git/objects/be/118435b9d908fd4a689cd8b0cc98059911a31a +0 -0
  32. data/test/fixtures/mycode.git/objects/db/aefcb5bde664671c73b99515c386dcbc7f22b6 +0 -0
  33. data/test/fixtures/mycode.git/objects/eb/669b878d2013ac70aa5dee75e6357ea81d16ea +0 -0
  34. data/test/fixtures/mycode.git/objects/ed/10cfcf72862e140c97fe899cba2a55f4cb4c20 +0 -0
  35. data/test/fixtures/mycode.git/objects/info/packs +2 -0
  36. data/test/fixtures/mycode.git/objects/pack/pack-40a8636b62258fffd78ec1e8d254116e72d385a9.idx +0 -0
  37. data/test/fixtures/mycode.git/objects/pack/pack-40a8636b62258fffd78ec1e8d254116e72d385a9.pack +0 -0
  38. data/test/fixtures/mycode.git/packed-refs +4 -0
  39. data/test/fixtures/mycode.git/refs/heads/master +1 -0
  40. data/test/fixtures/mycode.git/refs/tags/v0.1.0 +1 -0
  41. data/test/helpers.rb +67 -0
  42. data/test/htpasswd_test.rb +68 -0
  43. data/test/http_backend_authentication_test.rb +69 -0
  44. data/test/http_backend_test.rb +134 -0
  45. metadata +134 -0
data/README.rdoc ADDED
@@ -0,0 +1,74 @@
1
+ = Git-Webby - Web smarty for Git
2
+
3
+ == DESCRIPTION
4
+
5
+ This project was inspired in the {Grack}[http://github.com/schacon/grack]
6
+ Smart-HTTP server handler (written by
7
+ {Scott Chacon}[http://github.com/schacon]) but developed using
8
+ {Sinatra}[http://www.sinatrarb.com] and aims replace the original
9
+ `git-http-backend` including new features.
10
+
11
+ The main goal of the <b>Git-Webby</b> is implement the following useful features.
12
+
13
+ - Smart-HTTP, based on _git-http-backend_.
14
+ - Authentication flexible based on database or configuration file like .+htpasswd+.
15
+ - API to get information about repository.
16
+
17
+ == SINOPSIS
18
+
19
+ Install the Git-Webby using {Rubygems}[http://rubygems.org/gems/git-webby].
20
+
21
+ gem install git-webby
22
+
23
+ Or fork the project hosted on {Github}[http://github.com/codigorama/git-webby].
24
+
25
+ git clone https://github.com/codigorama/git-webby.git
26
+ ...
27
+ cd git-webby
28
+ rake install
29
+
30
+ Configure your Rackup file (<tt>config.ru</tt>) using the following instructions:
31
+
32
+ # config.ru
33
+ require "git/webby"
34
+
35
+ Git::Webby::HttpBackend.configure do |server|
36
+ server.project_root = "/home/git/repositories"
37
+ server.git_path = "/usr/bin/git"
38
+ server.get_any_file = true
39
+ server.upload_pack = true
40
+ server.receive_pack = false
41
+ server.authenticate = true
42
+ end
43
+
44
+ $ rackup --port 2011 --daemonize
45
+ $ git clone http://localhost:2011/mycode.git
46
+
47
+ You can use the <tt>.netrc</tt> for improve your connection. Put this:
48
+
49
+ machine <host> login <username> password <password>
50
+
51
+ The Git-Webby is under development, so there are still many improvements to be
52
+ made. Please, help us to improve the project with your feedback to
53
+ {issues}[http://github.com/codigorama/git-webby] or sending email to
54
+ {opensource@codigorama.com}[mailto:opensource@codigorama.com].
55
+
56
+ Discuss in {Google Groups}[http://groups.google.com/group/git-webby].
57
+
58
+ == AUTHORS
59
+
60
+ Written by Hallison Batista <hallison@codigorama.com>.
61
+
62
+ == BUGS
63
+
64
+ If you find a bug, please report it at the Git-Webby project's
65
+ {issues tracker}[http://github.com/codigorama/git-webby] on Github.
66
+
67
+ == LICENSE
68
+
69
+ Git-Webby is Copyright (c) 2011 Hallison Batista, Codigorama.
70
+
71
+ It is free software, and may be redistributed under the terms specified in
72
+ LICENSE.txt.
73
+
74
+
data/Rakefile ADDED
@@ -0,0 +1,44 @@
1
+ #:nopkg:
2
+ ENV["RUBYLIB"] = "#{File.dirname(__FILE__)}/lib"
3
+ ENV["RUBYOPT"] = "-rubygems"
4
+ #:
5
+
6
+ require "git/webby"
7
+
8
+ def spec
9
+ @spec ||= Gem::Specification.load("git-webby.gemspec")
10
+ end
11
+
12
+ desc "Run tests"
13
+ task :test, [:file] do |spec, args|
14
+ Dir["test/#{args.file}*_test.rb"].each do |file|
15
+ sh "ruby #{file}"
16
+ end
17
+ end
18
+
19
+ desc "API Documentation (RDoc)"
20
+ task :doc do
21
+ sh "rdoc -o doc/api -H -f hanna -m README.rdoc"
22
+ end
23
+
24
+ desc "Build #{spec.file_name}"
25
+ task :build => "#{spec.name}.gemspec" do
26
+ sh "gem build #{spec.name}.gemspec"
27
+ end
28
+
29
+ desc "Release #{spec.file_name}"
30
+ task :release do
31
+ sh "gem push #{spec.file_name}"
32
+ end
33
+
34
+ desc "Install gem file #{spec.file_name}"
35
+ task :install => :build do
36
+ sh "gem install -l #{spec.file_name}"
37
+ end
38
+
39
+ desc "Uninstall gem #{spec.name} v#{spec.version}"
40
+ task :uninstall do
41
+ sh "gem uninstall -l #{spec.name} -v #{spec.version}"
42
+ end
43
+
44
+ task :default => :test
@@ -0,0 +1,11 @@
1
+ == Git-Webby v0.1.0
2
+
3
+ Implementation of the basic features:
4
+
5
+ - Smart-HTTP implementation.
6
+ - Authentication using +htpasswd+ features (only crypt algorithm).
7
+ - Basic configuration in Rackup file.
8
+
9
+ This is a beta version. For more informations about this release,
10
+ please visit <http://github.com/codigorama/git-webby>.
11
+
data/git-webby.gemspec ADDED
@@ -0,0 +1,32 @@
1
+ require "git/webby"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.platform = Gem::Platform::RUBY
5
+ spec.name = "git-webby"
6
+ spec.summary = "Git Web implementation of the Smart HTTP and other features"
7
+ spec.authors = ["Hallison Batista"]
8
+ spec.email = "hallison@codigorama.com"
9
+ spec.homepage = "http://github.com/codigorama/git-webby"
10
+ spec.rubyforge_project = spec.name
11
+ spec.version = Git::Webby::VERSION
12
+ spec.date = Git::Webby::RELEASE
13
+ spec.test_files = spec.files.select{ |path| path =~ /^test\/.*/ }
14
+ spec.require_paths = ["lib"]
15
+ spec.files = %x[git ls-files].split.reject do |out|
16
+ out =~ %r{^\.} || out =~ %r{/^doc/api/}
17
+ end
18
+ spec.description = <<-end.gsub /^ /,''
19
+ Git::Webby is a implementation of the several features:
20
+ - Smart HTTP which works like as git-http-backend.
21
+ - Show info pages about the projects.
22
+ end
23
+ spec.post_install_message = <<-end.gsub(/^[ ]{4}/,'')
24
+ #{'-'*78}
25
+ Git::Webby v#{spec.version}
26
+
27
+ Thanks for use Git::Webby.
28
+ #{'-'*78}
29
+ end
30
+ spec.add_dependency "sinatra", ">= 1.0"
31
+ end
32
+
data/lib/git/webby.rb ADDED
@@ -0,0 +1,145 @@
1
+ # See <b>Git::Webby</b> for documentation.
2
+ module Git
3
+
4
+ # Internal requirements
5
+ require "git/webby/version"
6
+
7
+ # The main goal of the <b>Git::Webby</b> is implement the following useful
8
+ # features.
9
+ #
10
+ # - Smart-HTTP, based on _git-http-backend_.
11
+ # - Authentication flexible based on database or configuration file like <tt>.htpasswd</tt>.
12
+ # - API to get information about repository.
13
+ module Webby
14
+
15
+ module RepositoryUtils # :nodoc:
16
+ def project_path_to(*args)
17
+ File.join(settings.project_root.to_s, *(args.map(&:to_s)))
18
+ end
19
+
20
+ def git_dir(name)
21
+ unless name =~ /\w\.git/ # not bare directory
22
+ File.join(name, ".git")
23
+ else
24
+ name
25
+ end
26
+ end
27
+
28
+ def path_to(name, *args)
29
+ project_path_to(git_dir(name), *args)
30
+ end
31
+
32
+ def read_file(dirname, *file)
33
+ File.read(path_to(dirname, *file))
34
+ end
35
+
36
+ def chdir(dirname, &block)
37
+ Dir.chdir(path_to(dirname), &block)
38
+ end
39
+
40
+ def git_cli(command, *args)
41
+ %Q[#{settings.git_path} #{args.unshift(command.to_s.gsub("_","-")).compact.join(" ")}]
42
+ end
43
+
44
+ def git_run(command, *args)
45
+ %x[#{git_cli command, *args}]
46
+ end
47
+
48
+ end
49
+
50
+ # This class configure the needed variables used by application.
51
+ #
52
+ # For HTTP-Backend configuration
53
+ #
54
+ # The following attributes was necessary by +http_backend+:
55
+ #
56
+ # *project_root* :: Directory that contains all git repositories.
57
+ # *git_path* :: Path to git command line program.
58
+ # *get_any_file* :: Like <tt>http.getanyfile</tt> configuration.
59
+ # *upload_pack* :: Like <tt>http.uploadpack</tt> configuration.
60
+ # *receive_pack* :: Like <tt>http.receivepack</tt> configuration.
61
+ class Config
62
+
63
+ # Configuration for HTTP Backend variables
64
+ attr_accessor :http_backend
65
+
66
+ def initialize(attributes = {}) # :yields: config
67
+ attributes.each do |key, value|
68
+ self.send("#{key}=", value) if self.respond_to? key
69
+ end
70
+ yield self if block_given?
71
+ end
72
+
73
+ def self.load_file(file)
74
+ require "yaml"
75
+ new(YAML.load_file(file))
76
+ end
77
+
78
+ end
79
+
80
+ class Htgroup #:nodoc:
81
+ require "webrick/httpauth/htgroup"
82
+
83
+ def initialize(file)
84
+ @handler = WEBrick::HTTPAuth::Htgroup.new(file)
85
+ yield self if block_given?
86
+ end
87
+ end
88
+
89
+ class Htpasswd #:nodoc:
90
+ require "webrick/httpauth/htpasswd"
91
+
92
+ def initialize(file)
93
+ @handler = WEBrick::HTTPAuth::Htpasswd.new(file)
94
+ yield self if block_given?
95
+ end
96
+
97
+ def find(username)
98
+ password = @handler.get_passwd(nil, username, false)
99
+ if block_given?
100
+ yield password ? [password, password[0,2]] : [nil, nil]
101
+ else
102
+ password
103
+ end
104
+ end
105
+
106
+ def authenticated?(username, password)
107
+ self.find username do |crypted, salt|
108
+ crypted && salt && crypted == password.crypt(salt)
109
+ end
110
+ end
111
+
112
+ def create(username, password)
113
+ @handler.set_passwd(nil, username, password)
114
+ end
115
+ alias update create
116
+
117
+ def destroy(username)
118
+ @handler.delete_passwd(nil, username)
119
+ end
120
+
121
+ def include?(username)
122
+ users.include? username
123
+ end
124
+
125
+ def size
126
+ users.size
127
+ end
128
+
129
+ def write!
130
+ @handler.flush
131
+ end
132
+
133
+ private
134
+
135
+ def users
136
+ @handler.each{|username, password| username }
137
+ end
138
+ end
139
+
140
+ # Applications
141
+ autoload :HttpBackend, "git/webby/http_backend"
142
+
143
+ end
144
+
145
+ end
@@ -0,0 +1,248 @@
1
+ module Git::Webby
2
+
3
+ module HttpBackendUtils #:nodoc:
4
+
5
+ include RepositoryUtils
6
+
7
+ def repository
8
+ params[:repository]
9
+ end
10
+
11
+ def content_type_for_git(name, *suffixes)
12
+ content_type("application/x-git-#{name}-#{suffixes.compact.join("-")}")
13
+ end
14
+
15
+ def service_request?
16
+ not params[:service].nil?
17
+ end
18
+
19
+ # select_service feature
20
+ def service
21
+ @service = params[:service]
22
+ return false if @service.nil?
23
+ return false if @service[0, 4] != "git-"
24
+ @service = @service.gsub("git-", "")
25
+ end
26
+
27
+ # pkt_write feature
28
+ def packet_write(line)
29
+ (line.size + 4).to_s(base=16).rjust(4, "0") + line
30
+ end
31
+
32
+ # pkt_flush feature
33
+ def packet_flush
34
+ "0000"
35
+ end
36
+
37
+ # hdr_nocache feature
38
+ def header_nocache
39
+ headers "Expires" => "Fri, 01 Jan 1980 00:00:00 GMT",
40
+ "Pragma" => "no-cache",
41
+ "Cache-Control" => "no-cache, max-age=0, must-revalidate"
42
+ end
43
+
44
+ # hdr_cache_forever feature
45
+ def header_cache_forever
46
+ now = Time.now
47
+ headers "Date" => now.to_s,
48
+ "Expires" => (now + 31536000).to_s,
49
+ "Cache-Control" => "public, max-age=31536000"
50
+ end
51
+
52
+ # select_getanyfile feature
53
+ def read_any_file
54
+ unless settings.get_any_file
55
+ halt 403, "Unsupported service: getanyfile"
56
+ end
57
+ end
58
+
59
+ # get_text_file feature
60
+ def read_text_file(repository, *file)
61
+ read_any_file
62
+ header_nocache
63
+ content_type "text/plain"
64
+ read_file(repository, *file)
65
+ end
66
+
67
+ # get_loose_object feature
68
+ def send_loose_object(repository, hash_prefix, hash_suffix)
69
+ read_any_file
70
+ header_cache_forever
71
+ content_type_for_git :loose, :object
72
+ send_file(path_to(repository, "objects", hash_prefix, hash_suffix))
73
+ end
74
+
75
+ # get_pack_file and get_idx_file
76
+ def send_pack_idx_file(repository, pack, idx = false)
77
+ read_any_file
78
+ header_cache_forever
79
+ content_type_for_git :packed, :objects, (idx ? :toc : nil)
80
+ send_file(path_to(repository, "objects", "pack", pack))
81
+ end
82
+
83
+ def send_info_packs(repository)
84
+ read_any_file
85
+ header_nocache
86
+ content_type "text/plain; charset=utf-8"
87
+ send_file(path_to(repository, "objects", "info", "packs"))
88
+ end
89
+
90
+ # run_service feature
91
+ def run_advertisement(repository, service)
92
+ header_nocache
93
+ content_type_for_git service, :advertisement
94
+ chdir repository do
95
+ response.body = ""
96
+ response.body += packet_write("# service=git-#{service}\n")
97
+ response.body += packet_flush
98
+ response.body += git_run(service, "--stateless-rpc --advertise-refs .")
99
+ response.finish
100
+ end
101
+ end
102
+
103
+ def run_process(repository, service)
104
+ content_type_for_git service, :result
105
+ input = request.body.read
106
+ command = git_cli(service, "--stateless-rpc .")
107
+ chdir repository do
108
+ # This source has extracted from Grack written by Scott Chacon.
109
+ IO.popen(command, File::RDWR) do |pipe|
110
+ pipe.write(input)
111
+ while !pipe.eof?
112
+ block = pipe.read(8192) # 8M at a time
113
+ response.write block # steam it to the client
114
+ end
115
+ end # IO
116
+ response.finish
117
+ end
118
+ end
119
+
120
+ end # HttpBackendUtils
121
+
122
+ module HttpBackendAuthentication #:nodoc:
123
+
124
+ def htpasswd
125
+ @htpasswd ||= Htpasswd.new(project_path_to("htpasswd"))
126
+ end
127
+
128
+ def authentication
129
+ @authentication ||= Rack::Auth::Basic::Request.new request.env
130
+ end
131
+
132
+ def authenticated?
133
+ request.env["REMOTE_USER"] && request.env["git.webby.authenticated"]
134
+ end
135
+
136
+ def authenticate(username, password)
137
+ checked = [ username, password ] == authentication.credentials
138
+ validated = authentication.provided? && authentication.basic?
139
+ granted = htpasswd.authenticated? username, password
140
+ if checked and validated and granted
141
+ request.env["git.webby.authenticated"] = true
142
+ request.env["REMOTE_USER"] = authentication.username
143
+ else
144
+ nil
145
+ end
146
+ end
147
+
148
+ def unauthorized!(realm = Git::Webby::info)
149
+ headers "WWW-Authenticate" => %(Basic realm="#{realm}")
150
+ throw :halt, [ 401, "Authorization Required" ]
151
+ end
152
+
153
+ def bad_request!
154
+ throw :halt, [ 400, "Bad Request" ]
155
+ end
156
+
157
+ def authenticate!
158
+ return if authenticated?
159
+ unauthorized! unless authentication.provided?
160
+ bad_request! unless authentication.basic?
161
+ unauthorized! unless authenticate(*authentication.credentials)
162
+ request.env["REMOTE_USER"] = authentication.username
163
+ end
164
+
165
+ def access_granted?(username, password)
166
+ authenticated? || authenticate(username, password)
167
+ end
168
+
169
+ end
170
+
171
+ require "sinatra/base"
172
+
173
+ # The Smart HTTP handler server. This is the main Web application which respond to following requests:
174
+ #
175
+ # <repo.git>/HEAD :: HEAD contents
176
+ # <repo.git>/info/refs :: Text file that contains references.
177
+ # <repo.git>/objects/info/* :: Text file that contains all list of packets, alternates or http-alternates.
178
+ # <repo.git>/objects/*/* :: Git objects, packets or indexes.
179
+ # <repo.git>/upload-pack :: Post an upload packets.
180
+ # <repo.git>/receive-pack :: Post a receive packets.
181
+ #
182
+ # See ::configure for more details.
183
+ class HttpBackend < Sinatra::Base
184
+
185
+ helpers HttpBackendUtils
186
+
187
+ set :project_root, File.expand_path("#{File.dirname(__FILE__)}/git")
188
+ set :git_path, "/usr/bin/git"
189
+ set :get_any_file, true
190
+ set :upload_pack, true
191
+ set :receive_pack, false
192
+ set :authenticate, false
193
+
194
+ def self.configure(*envs, &block)
195
+ super(*envs, &block)
196
+ self
197
+ end
198
+
199
+ before do
200
+ authenticate! if settings.authenticate
201
+ end
202
+
203
+ # implements the get_text_file function
204
+ get "/:repository/HEAD" do |repository|
205
+ read_text_file(repository, "HEAD")
206
+ end
207
+
208
+ # implements the get_info_refs function
209
+ get "/:repository/info/refs" do |repository|
210
+ if service_request? # by URL query parameters
211
+ run_advertisement repository, service
212
+ else
213
+ read_text_file(repository, :info, :refs)
214
+ end
215
+ end
216
+
217
+ # implements the get_text_file and get_info_packs functions
218
+ get %r{/(.*?)/objects/info/(packs|alternates|http-alternates)$} do |repository, file|
219
+ if file == "packs"
220
+ send_info_packs(repository)
221
+ else
222
+ read_text_file(repository, :objects, :info, file)
223
+ end
224
+ end
225
+
226
+ # implements the get_loose_object function
227
+ get %r{/(.*?)/objects/([0-9a-f]{2})/([0-9a-f]{38})$} do |repository, prefix, suffix|
228
+ send_loose_object(repository, prefix, suffix)
229
+ end
230
+
231
+ # implements the get_pack_file and get_idx_file functions
232
+ get %r{/(.*?)/objects/pack/(pack-[0-9a-f]{40}.(pack|idx))$} do |repository, pack, ext|
233
+ send_pack_idx_file(repository, pack, ext == "idx")
234
+ end
235
+
236
+ # implements the service_rpc function
237
+ post "/:repository/:service" do |repository, rpc|
238
+ run_process repository, service
239
+ end
240
+
241
+ private
242
+
243
+ helpers HttpBackendAuthentication
244
+
245
+ end # HttpBackend
246
+
247
+ end # Git::Webby
248
+