diff options
author | Johan Sørensen <johan@johansorensen.com> | 2008-04-19 00:40:42 +0200 |
---|---|---|
committer | Johan Sørensen <johan@johansorensen.com> | 2008-04-19 00:40:42 +0200 |
commit | 37e1c41b4f693c787096e3fb7858f8b8c68c966b (patch) | |
tree | 955acfba731ad0823cc9777bc0afeeab9d3295b5 | |
parent | 68b75354f51f222d689ca22b623ff89bcefe7b6f (diff) | |
parent | 7d56991920c9e56a6991f41324b1d73835f3eb0f (diff) | |
download | gitorious-mainline-outdated-37e1c41b4f693c787096e3fb7858f8b8c68c966b.zip gitorious-mainline-outdated-37e1c41b4f693c787096e3fb7858f8b8c68c966b.tar.gz gitorious-mainline-outdated-37e1c41b4f693c787096e3fb7858f8b8c68c966b.tar.bz2 |
Merge branch 'ruby-git-daemon'
-rw-r--r-- | app/models/cloner.rb | 3 | ||||
-rw-r--r-- | app/models/repository.rb | 13 | ||||
-rw-r--r-- | app/views/repositories/_infobox.html.erb | 1 | ||||
-rw-r--r-- | data/GeoIP.dat | bin | 0 -> 1104406 bytes | |||
-rw-r--r-- | db/migrate/023_create_cloners.rb | 16 | ||||
-rwxr-xr-x | script/git-daemon | 209 | ||||
-rw-r--r-- | spec/fixtures/cloners.yml | 13 | ||||
-rw-r--r-- | spec/models/cloner_spec.rb | 18 |
8 files changed, 273 insertions, 0 deletions
diff --git a/app/models/cloner.rb b/app/models/cloner.rb new file mode 100644 index 0000000..8f7d37d --- /dev/null +++ b/app/models/cloner.rb @@ -0,0 +1,3 @@ +class Cloner < ActiveRecord::Base + belongs_to :repository +end diff --git a/app/models/repository.rb b/app/models/repository.rb index 235359a..3403677 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -9,6 +9,7 @@ class Repository < ActiveRecord::Base :order => "status, id desc", :dependent => :destroy has_many :proposed_merge_requests, :foreign_key => 'source_repository_id', :class_name => 'MergeRequest', :order => "id desc", :dependent => :destroy + has_many :cloners, :dependent => :destroy validates_presence_of :user_id, :project_id, :name validates_format_of :name, :with => /^[a-z0-9_\-]+$/i, @@ -28,6 +29,14 @@ class Repository < ActiveRecord::Base find_by_name(name) || raise(ActiveRecord::RecordNotFound) end + def self.find_by_path(path) + base_path = path.gsub(/^#{Regexp.escape(GitoriousConfig['repository_base_path'])}/, "") + repo_name, project_name = base_path.split("/").reverse + + project = Project.find_by_slug!(project_name) + project.repositories.find_by_name(repo_name.sub(/\.git/, "")) + end + def self.create_git_repository(path) git_backend.create(full_path_from_partial_path(path)) end @@ -221,6 +230,10 @@ class Repository < ActiveRecord::Base users_by_email = users.inject({}){|hash, user| hash[user.email] = user; hash } users_by_email end + + def cloned_from(ip, country_code = "--", country_name = nil) + cloners.create(:ip => ip, :date => Time.now.utc, :country_code => country_code, :country => country_name) + end protected def set_as_mainline_if_first diff --git a/app/views/repositories/_infobox.html.erb b/app/views/repositories/_infobox.html.erb index a3cc628..230acf3 100644 --- a/app/views/repositories/_infobox.html.erb +++ b/app/views/repositories/_infobox.html.erb @@ -41,4 +41,5 @@ git push </div> </li> <% end -%> + <li>The repository has been cloned <%= @repository.cloners.size %> times.</li> </ul>
\ No newline at end of file diff --git a/data/GeoIP.dat b/data/GeoIP.dat Binary files differnew file mode 100644 index 0000000..e05bf09 --- /dev/null +++ b/data/GeoIP.dat diff --git a/db/migrate/023_create_cloners.rb b/db/migrate/023_create_cloners.rb new file mode 100644 index 0000000..a77cf4f --- /dev/null +++ b/db/migrate/023_create_cloners.rb @@ -0,0 +1,16 @@ +class CreateCloners < ActiveRecord::Migration + def self.up + create_table :cloners do |t| + t.string :ip + t.string :country_code, :length => 2 + t.string :country + t.datetime :date + t.integer :repository_id + end + end + + def self.down + drop_table :cloners + end +end + diff --git a/script/git-daemon b/script/git-daemon new file mode 100755 index 0000000..0064342 --- /dev/null +++ b/script/git-daemon @@ -0,0 +1,209 @@ +#!/usr/bin/env ruby + +require 'rubygems' +require 'daemons' +require 'geoip' +require 'socket' +require "optparse" + +ENV["RAILS_ENV"] ||= "production" +require File.dirname(__FILE__)+'/../config/environment' + +Rails.configuration.log_level = :info # Disable debug +ActiveRecord::Base.allow_concurrency = true + +BASE_PATH = File.expand_path(GitoriousConfig['repository_base_path']) + +TIMEOUT = 30 +MAX_CHILDREN = 30 +$children_reaped = 0 +$children_active = 0 + +module Git + class Daemon + include Daemonize + + SERVICE_REGEXP = /(\w{4})(git\-upload\-pack)\s(.+)\x0host=([\w\.\-]+)/.freeze + + def initialize(options) + @options = options + @geoip = GeoIP.new(File.join(RAILS_ROOT, "data", "GeoIP.dat")) + end + + def start + if @options[:daemonize] + daemonize(@options[:logfile]) + File.open(@options[:pidfile], "w") do |f| + f.write(Process.pid) + end + end + @socket = TCPServer.new(@options[:host], @options[:port]) + log(Process.pid, "Listening on #{@options[:host]}:#{@options[:port]}...") + run + end + + def run + while session = @socket.accept + connections = $children_active - $children_reaped + if connections > MAX_CHILDREN + log(Process.pid, "too many active children #{connections}/#{MAX_CHILDREN}") + session.close + next + end + + run_service(session) + end + end + + def run_service(session) + $children_active += 1 + + line = session.recv(1000) + + if line =~ SERVICE_REGEXP + start_time = Time.now + code = $1 + service = $2 + base_path = $3 + host = $4 + + path = File.expand_path("#{BASE_PATH}/#{base_path}") + if !path.index(BASE_PATH) == 0 || !File.directory?(path) + log(Process.pid, "Invalid path: #{base_path}") + session.close + $children_active -= 1 + return + end + + if !File.exist?(File.join(path, "git-daemon-export-ok")) + session.close + $children_active -= 1 + return + end + + Dir.chdir(path) do + cmd = "git-upload-pack --strict --timeout=#{TIMEOUT} ." + + fork do + repository = nil + + begin + repository = ::Repository.find_by_path(path) + rescue => e + log(Process.pid, "AR error: #{e.class.name} #{e.message}:\n #{e.backtrace.join("\n ")}") + end + + pid = Process.pid + ip_family, port, name, ip = session.peeraddr + log(pid, "Connection from #{ip} for #{path.inspect}") + + $stdout.reopen(session) + $stdin.reopen(session) + session.close + + if repository + if ip_family == "AF_INET6" + repository.cloned_from(ip) + else + localization = @geoip.country(ip) + repository.cloned_from(ip, localization[3], localization[5]) + end + else + log(pid, "Cannot find repository: #{path}") + end + log(Process.pid, "Deferred in #{'%0.5f' % (Time.now - start_time)}s") + + exec(cmd) + # FIXME; we don't ever get here since we exec(), so reaped count may be incorrect + $children_reaped += 1 + exit! + end + end rescue Errno::EAGAIN + else + $stderr.puts "Invalid request: #{line}" + session.close + $children_active -= 1 + end + end + + def handle_stop(signal) + log(Process.pid, "Received #{signal}, exiting..") + exit 0 + end + + def handle_cld + loop do + pid = nil + begin + pid = Process.wait(-1, Process::WNOHANG) + rescue Errno::ECHILD + break + end + + if pid && $? + $children_reaped += 1 + log(pid, "Disconnected. (status=#{$?.exitstatus})") if pid > 0 + if $children_reaped == $children_active + $children_reaped = 0 + $children_active = 0 + end + + next + end + break + end + end + + def log(pid, msg) + $stderr.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} [#{pid}] #{msg}" + end + + end +end + +options = { + :port => 9418, + :host => "0.0.0.0", + :logfile => File.join(RAILS_ROOT, "log", "git-daemon.log"), + :pidfile => File.join(RAILS_ROOT, "log", "git-daemon.pid"), + :daemonize => false + +} + +OptionParser.new do |opts| + opts.banner = "Usage: #{$0} start|stop [options]" + + opts.on("-p", "--port", Integer, "Port to listen on") do |o| + options[:port] = o + end + + opts.on("-h", "--host", "Host to listen on") do |o| + options[:host] = o + end + + opts.on("-l", "--logfile", "File to log to") do |o| + options[:logfile] = o + end + + opts.on("-l", "--logfile", "PID file to use (if daemonized)") do |o| + options[:pidfile] = o + end + + opts.on("-d", "--daemonize", "Daemonize or run in foreground (default)") do |o| + options[:daemonize] = o + end + + # opts.on("-e", "--environment", "RAILS_ENV to use") do |o| + # options[:port] = o + # end +end.parse! + +@git_daemon = Git::Daemon.new(options) + +trap("SIGKILL") { @git_daemon.handle_stop("SIGKILL") } +trap("TERM") { @git_daemon.handle_stop("TERM") } +trap("SIGINT") { @git_daemon.handle_stop("SIGINT") } +trap("CLD") { @git_daemon.handle_cld } + +@git_daemon.start + diff --git a/spec/fixtures/cloners.yml b/spec/fixtures/cloners.yml new file mode 100644 index 0000000..3e7d18f --- /dev/null +++ b/spec/fixtures/cloners.yml @@ -0,0 +1,13 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +argentina: + ip: 200.45.34.21 + country: Argentina + country_code: AR + date: 2008-04-13 23:40:30 + +mexico: + ip: 200.53.12.21 + country: Mexico + country_code: MX + date: 2008-04-13 23:40:30 diff --git a/spec/models/cloner_spec.rb b/spec/models/cloner_spec.rb new file mode 100644 index 0000000..a6c9402 --- /dev/null +++ b/spec/models/cloner_spec.rb @@ -0,0 +1,18 @@ +require File.dirname(__FILE__) + '/../spec_helper' +require 'geoip' + +describe Cloner do + before(:all) do + @geoip = GeoIP.new(File.join(RAILS_ROOT, "data", "GeoIP.dat")) + end + + before(:each) do + @cloner = Cloner.new + end + + it "should has a valid country" do + localization = @geoip.country(cloners(:argentina).ip) + localization[3].should == cloners(:argentina).country_code + localization[5].should == cloners(:argentina).country + end +end |