summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohan Sørensen <johan@johansorensen.com>2008-04-19 00:40:42 +0200
committerJohan Sørensen <johan@johansorensen.com>2008-04-19 00:40:42 +0200
commit37e1c41b4f693c787096e3fb7858f8b8c68c966b (patch)
tree955acfba731ad0823cc9777bc0afeeab9d3295b5
parent68b75354f51f222d689ca22b623ff89bcefe7b6f (diff)
parent7d56991920c9e56a6991f41324b1d73835f3eb0f (diff)
downloadgitorious-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.rb3
-rw-r--r--app/models/repository.rb13
-rw-r--r--app/views/repositories/_infobox.html.erb1
-rw-r--r--data/GeoIP.datbin0 -> 1104406 bytes
-rw-r--r--db/migrate/023_create_cloners.rb16
-rwxr-xr-xscript/git-daemon209
-rw-r--r--spec/fixtures/cloners.yml13
-rw-r--r--spec/models/cloner_spec.rb18
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
new file mode 100644
index 0000000..e05bf09
--- /dev/null
+++ b/data/GeoIP.dat
Binary files differ
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