| |   |
| 9 | 9 | :order => "status, id desc", :dependent => :destroy |
| 10 | 10 | has_many :proposed_merge_requests, :foreign_key => 'source_repository_id', |
| 11 | 11 | :class_name => 'MergeRequest', :order => "id desc", :dependent => :destroy |
| 12 | has_many :cloners, :dependent => :destroy |
| 12 | 13 | |
| 13 | 14 | validates_presence_of :user_id, :project_id, :name |
| 14 | 15 | validates_format_of :name, :with => /^[a-z0-9_\-]+$/i, |
| … | … | |
| 29 | 29 | find_by_name(name) || raise(ActiveRecord::RecordNotFound) |
| 30 | 30 | end |
| 31 | 31 | |
| 32 | def self.find_by_path(path) |
| 33 | base_path = path.gsub(/^#{Regexp.escape(GitoriousConfig['repository_base_path'])}/, "") |
| 34 | repo_name, project_name = base_path.split("/").reverse |
| 35 | |
| 36 | project = Project.find_by_slug!(project_name) |
| 37 | project.repositories.find_by_name(repo_name.sub(/\.git/, "")) |
| 38 | end |
| 39 | |
| 32 | 40 | def self.create_git_repository(path) |
| 33 | 41 | git_backend.create(full_path_from_partial_path(path)) |
| 34 | 42 | end |
| … | … | |
| 230 | 230 | users_by_email = users.inject({}){|hash, user| hash[user.email] = user; hash } |
| 231 | 231 | users_by_email |
| 232 | 232 | end |
| 233 | |
| 234 | def cloned_from(ip, country_code = "--", country_name = nil) |
| 235 | cloners.create(:ip => ip, :date => Time.now.utc, :country_code => country_code, :country => country_name) |
| 236 | end |
| 233 | 237 | |
| 234 | 238 | protected |
| 235 | 239 | def set_as_mainline_if_first |
| toggle raw diff |
--- 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 |
| |   |
| 14 | 14 | <% else -%> |
| 15 | 15 | <td class="node file"><%= link_to h(node.basename), blob_path(params[:id], node.name) -%></td> |
| 16 | 16 | <% end -%> |
| 17 | | <% last_commit = commit_for_tree_path(@repository, node.name) -%> |
| 18 | | <td class="meta"><%= last_commit.committed_date.to_s(:short) -%></td> |
| 19 | | <td class="meta commit_message"> |
| 20 | | <%= link_to truncate(h(last_commit.message), 75, "…"), commit_path(last_commit.id) -%> |
| 21 | | </td> |
| 17 | <% if last_commit = commit_for_tree_path(@repository, node.name) -%> |
| 18 | <td class="meta"><%= last_commit.committed_date.to_s(:short) -%></td> |
| 19 | <td class="meta commit_message"> |
| 20 | <%= link_to truncate(h(last_commit.message), 75, "…"), commit_path(last_commit.id) -%> |
| 21 | </td> |
| 22 | <% else -%> |
| 23 | <td class="meta"></td> |
| 24 | <td class="meta commit_message"></td> |
| 25 | <% end -%> |
| 22 | 26 | </tr> |
| 23 | 27 | <% end -%> |
| 24 | 28 | </table> |
| toggle raw diff |
--- a/app/views/trees/show.html.erb
+++ b/app/views/trees/show.html.erb
@@ -14,11 +14,15 @@
<% else -%>
<td class="node file"><%= link_to h(node.basename), blob_path(params[:id], node.name) -%></td>
<% end -%>
- <% last_commit = commit_for_tree_path(@repository, node.name) -%>
- <td class="meta"><%= last_commit.committed_date.to_s(:short) -%></td>
- <td class="meta commit_message">
- <%= link_to truncate(h(last_commit.message), 75, "…"), commit_path(last_commit.id) -%>
- </td>
+ <% if last_commit = commit_for_tree_path(@repository, node.name) -%>
+ <td class="meta"><%= last_commit.committed_date.to_s(:short) -%></td>
+ <td class="meta commit_message">
+ <%= link_to truncate(h(last_commit.message), 75, "…"), commit_path(last_commit.id) -%>
+ </td>
+ <% else -%>
+ <td class="meta"></td>
+ <td class="meta commit_message"></td>
+ <% end -%>
</tr>
<% end -%>
</table> |
| |   |
| 1 | #!/usr/bin/env ruby |
| 2 | |
| 3 | require 'rubygems' |
| 4 | require 'daemons' |
| 5 | require 'geoip' |
| 6 | require 'socket' |
| 7 | require "optparse" |
| 8 | |
| 9 | ENV["RAILS_ENV"] ||= "production" |
| 10 | require File.dirname(__FILE__)+'/../config/environment' |
| 11 | |
| 12 | Rails.configuration.log_level = :info # Disable debug |
| 13 | ActiveRecord::Base.allow_concurrency = true |
| 14 | |
| 15 | BASE_PATH = File.expand_path(GitoriousConfig['repository_base_path']) |
| 16 | |
| 17 | TIMEOUT = 30 |
| 18 | MAX_CHILDREN = 30 |
| 19 | $children_reaped = 0 |
| 20 | $children_active = 0 |
| 21 | |
| 22 | module Git |
| 23 | class Daemon |
| 24 | include Daemonize |
| 25 | |
| 26 | SERVICE_REGEXP = /(\w{4})(git\-upload\-pack)\s(.+)\x0host=([\w\.\-]+)/.freeze |
| 27 | |
| 28 | def initialize(options) |
| 29 | @options = options |
| 30 | end |
| 31 | |
| 32 | def start |
| 33 | if @options[:daemonize] |
| 34 | daemonize(@options[:logfile]) |
| 35 | File.open(@options[:pidfile], "w") do |f| |
| 36 | f.write(Process.pid) |
| 37 | end |
| 38 | end |
| 39 | @socket = TCPServer.new(@options[:host], @options[:port]) |
| 40 | log(Process.pid, "Listening on #{@options[:host]}:#{@options[:port]}...") |
| 41 | run |
| 42 | end |
| 43 | |
| 44 | def run |
| 45 | while session = @socket.accept |
| 46 | connections = $children_active - $children_reaped |
| 47 | if connections > MAX_CHILDREN |
| 48 | log(Process.pid, "too many active children #{connections}/#{MAX_CHILDREN}") |
| 49 | session.close |
| 50 | next |
| 51 | end |
| 52 | |
| 53 | run_service(session) |
| 54 | end |
| 55 | end |
| 56 | |
| 57 | def run_service(session) |
| 58 | $children_active += 1 |
| 59 | ip_family, port, name, ip = session.peeraddr |
| 60 | |
| 61 | line = session.recv(1000) |
| 62 | |
| 63 | if line =~ SERVICE_REGEXP |
| 64 | start_time = Time.now |
| 65 | code = $1 |
| 66 | service = $2 |
| 67 | base_path = $3 |
| 68 | host = $4 |
| 69 | |
| 70 | path = File.expand_path("#{BASE_PATH}/#{base_path}") |
| 71 | if !path.index(BASE_PATH) == 0 || !File.directory?(path) |
| 72 | log(Process.pid, "Invalid path: #{base_path}") |
| 73 | session.close |
| 74 | $children_active -= 1 |
| 75 | return |
| 76 | end |
| 77 | |
| 78 | if !File.exist?(File.join(path, "git-daemon-export-ok")) |
| 79 | session.close |
| 80 | $children_active -= 1 |
| 81 | return |
| 82 | end |
| 83 | |
| 84 | Dir.chdir(path) do |
| 85 | cmd = "git-upload-pack --strict --timeout=#{TIMEOUT} ." |
| 86 | |
| 87 | fork do |
| 88 | repository = nil |
| 89 | |
| 90 | begin |
| 91 | repository = ::Repository.find_by_path(path) |
| 92 | rescue => e |
| 93 | log(Process.pid, "AR error: #{e.class.name} #{e.message}:\n #{e.backtrace.join("\n ")}") |
| 94 | end |
| 95 | |
| 96 | pid = Process.pid |
| 97 | log(pid, "Connection from #{ip} for #{path.inspect}") |
| 98 | |
| 99 | $stdout.reopen(session) |
| 100 | $stdin.reopen(session) |
| 101 | session.close |
| 102 | |
| 103 | if repository |
| 104 | if ip_family == "AF_INET6" |
| 105 | repository.cloned_from(ip) |
| 106 | else |
| 107 | geoip = GeoIP.new(File.join(RAILS_ROOT, "data", "GeoIP.dat")) |
| 108 | localization = geoip.country(ip) |
| 109 | repository.cloned_from(ip, localization[3], localization[5]) |
| 110 | end |
| 111 | else |
| 112 | log(pid, "Cannot find repository: #{path}") |
| 113 | end |
| 114 | log(Process.pid, "Deferred in #{'%0.5f' % (Time.now - start_time)}s") |
| 115 | |
| 116 | exec(cmd) |
| 117 | # FIXME; we don't ever get here since we exec(), so reaped count may be incorrect |
| 118 | $children_reaped += 1 |
| 119 | exit! |
| 120 | end |
| 121 | end rescue Errno::EAGAIN |
| 122 | else |
| 123 | $stderr.puts "Invalid request from #{ip}: #{line}" |
| 124 | session.close |
| 125 | $children_active -= 1 |
| 126 | end |
| 127 | end |
| 128 | |
| 129 | def handle_stop(signal) |
| 130 | log(Process.pid, "Received #{signal}, exiting..") |
| 131 | exit 0 |
| 132 | end |
| 133 | |
| 134 | def handle_cld |
| 135 | loop do |
| 136 | pid = nil |
| 137 | begin |
| 138 | pid = Process.wait(-1, Process::WNOHANG) |
| 139 | rescue Errno::ECHILD |
| 140 | break |
| 141 | end |
| 142 | |
| 143 | if pid && $? |
| 144 | $children_reaped += 1 |
| 145 | log(pid, "Disconnected. (status=#{$?.exitstatus})") if pid > 0 |
| 146 | if $children_reaped == $children_active |
| 147 | $children_reaped = 0 |
| 148 | $children_active = 0 |
| 149 | end |
| 150 | |
| 151 | next |
| 152 | end |
| 153 | break |
| 154 | end |
| 155 | end |
| 156 | |
| 157 | def log(pid, msg) |
| 158 | $stderr.puts "#{Time.now.strftime("%Y-%m-%d %H:%M:%S")} [#{pid}] #{msg}" |
| 159 | end |
| 160 | |
| 161 | end |
| 162 | end |
| 163 | |
| 164 | options = { |
| 165 | :port => 9418, |
| 166 | :host => "0.0.0.0", |
| 167 | :logfile => File.join(RAILS_ROOT, "log", "git-daemon.log"), |
| 168 | :pidfile => File.join(RAILS_ROOT, "log", "git-daemon.pid"), |
| 169 | :daemonize => false |
| 170 | |
| 171 | } |
| 172 | |
| 173 | OptionParser.new do |opts| |
| 174 | opts.banner = "Usage: #{$0} [options]" |
| 175 | |
| 176 | opts.on("-p", "--port=[port]", Integer, "Port to listen on", "Default: #{options[:port]}") do |o| |
| 177 | options[:port] = o |
| 178 | end |
| 179 | |
| 180 | opts.on("-a", "--address=[host]", "Host to listen on", "Default: #{options[:host]}") do |o| |
| 181 | options[:host] = o |
| 182 | end |
| 183 | |
| 184 | opts.on("-l", "--logfile=[file]", "File to log to", "Default: #{options[:logfile]}") do |o| |
| 185 | options[:logfile] = o |
| 186 | end |
| 187 | |
| 188 | opts.on("-P", "--pidfile=[file]", "PID file to use (if daemonized)", "Default: #{options[:pidfile]}") do |o| |
| 189 | options[:pidfile] = o |
| 190 | end |
| 191 | |
| 192 | opts.on("-d", "--daemonize", "Daemonize or run in foreground", "Default: #{options[:daemonize]}") do |o| |
| 193 | options[:daemonize] = o |
| 194 | end |
| 195 | |
| 196 | opts.on_tail("-h", "--help", "Show this help message.") do |
| 197 | puts opts |
| 198 | exit |
| 199 | end |
| 200 | |
| 201 | # opts.on("-e", "--environment", "RAILS_ENV to use") do |o| |
| 202 | # options[:port] = o |
| 203 | # end |
| 204 | end.parse! |
| 205 | |
| 206 | @git_daemon = Git::Daemon.new(options) |
| 207 | |
| 208 | trap("SIGKILL") { @git_daemon.handle_stop("SIGKILL") } |
| 209 | trap("TERM") { @git_daemon.handle_stop("TERM") } |
| 210 | trap("SIGINT") { @git_daemon.handle_stop("SIGINT") } |
| 211 | trap("CLD") { @git_daemon.handle_cld } |
| 212 | |
| 213 | @git_daemon.start |
| 214 | |
| toggle raw diff |
--- /dev/null
+++ b/script/git-daemon
@@ -0,0 +1,214 @@
+#!/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
+ 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
+ ip_family, port, name, ip = session.peeraddr
+
+ 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
+ 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
+ geoip = GeoIP.new(File.join(RAILS_ROOT, "data", "GeoIP.dat"))
+ 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 from #{ip}: #{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} [options]"
+
+ opts.on("-p", "--port=[port]", Integer, "Port to listen on", "Default: #{options[:port]}") do |o|
+ options[:port] = o
+ end
+
+ opts.on("-a", "--address=[host]", "Host to listen on", "Default: #{options[:host]}") do |o|
+ options[:host] = o
+ end
+
+ opts.on("-l", "--logfile=[file]", "File to log to", "Default: #{options[:logfile]}") do |o|
+ options[:logfile] = o
+ end
+
+ opts.on("-P", "--pidfile=[file]", "PID file to use (if daemonized)", "Default: #{options[:pidfile]}") do |o|
+ options[:pidfile] = o
+ end
+
+ opts.on("-d", "--daemonize", "Daemonize or run in foreground", "Default: #{options[:daemonize]}") do |o|
+ options[:daemonize] = o
+ end
+
+ opts.on_tail("-h", "--help", "Show this help message.") do
+ puts opts
+ exit
+ 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
+ |