git-webby 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+