System notice: In light of the Debian OpenSSL security issue we've regenerated the server keys. See this thread for instructions and the new key fingerprints.

Commit 1015c9e63e990cc98199b5b301cac03db41dfcc9

Merge branch 'master' of git://gitorious.org/gitorious/mainline

* 'master' of git://gitorious.org/gitorious/mainline: (30 commits)
beware of the geoip io stream when forking
back out of non-blocking Socket#accept
actually do the accept nonblockingly
accept nonblocking
Fixed git-daemon optparsing
Log how long it takes to defer a request to git-upload-pack
Write a PID file if daemonized
only allow git-upload-pack the the command
updated AUTHORS
Fix spelling errors
because I'm too lazy to sort that list
move cloners migration into place
Don't prepend http:// to empty project urls
Additional fixes to the add committer autocompleter issues. This should now resolve all issues with firefox3 and firefox2
Don't break tree browser table when there's no last_commit
Workaround edgecase where we can't get the last_commit for a given treenode due to weird encodings
added method to handle the incoming connection
Tim needs a spot in the AUTHORS file as well
Fixed name and email in AUTHORS
Avoid zombie processes, catch exception if cannot fork, use 0.0.0.0 instead of localhost
...

Commit diff

AUTHORS

 
1212-------------
1313
1414A number of people have contributed directly to Gitorious by writing documentation
15or developing code and/or graphics. A list of these people in alphabetical order is included
16below:
15or developing code and/or graphics that the project wishes to thank.
16A list of these people in order of appearance:
1717
1818 * David A. Cuadrado <krawek@gmail.com>
1919 * August Lilleaas <augustlilleaas@gmail.com>
2020 * Simon Bohlin <simon.bohlin@zondera.com>
21 * Will <will@gina.alaska. edu>
22
21 * Tim Dysinger <tim@dysinger.net>
22 * Dag Odenhall <dag.odenhall@gmail.com>
23 * Will Fisher <will@gina.alaska.edu>
24 * Jonas Fonseca <fonseca@diku.dk>
25
toggle raw diff

TODO.txt

 
11(in no particular order)
22
3two from Yurii:
41) show commits actually merged for merge request after actual merge (I see them on open request, but do not on merged one)
52) provide clone url on every repo browsing page, so I can get url to pull from even if I am reading a commit
6
7* tree browser: deal better with funny characters: http://gitorious.org/projects/avara/repos/mainline/trees/master
38* if you comment on a specific commit, you should get redirected back there
49* graphs should show username (if we have it) for avoiding confusion and for consistency
510** Repository#commit_graph_data_by_author should just use git-shortlog summary instead of jumping through hoops about it
toggle raw diff

app/models/cloner.rb

 
1class Cloner < ActiveRecord::Base
2 belongs_to :repository
3end
toggle raw diff

app/models/project.rb

 
144144
145145 # Try our best to guess the url
146146 def clean_url(url)
147 return if url.blank?
147148 begin
148149 url = "http://#{url}" if URI.parse(url).class == URI::Generic
149150 rescue
toggle raw diff

app/models/repository.rb

 
99 :order => "status, id desc", :dependent => :destroy
1010 has_many :proposed_merge_requests, :foreign_key => 'source_repository_id',
1111 :class_name => 'MergeRequest', :order => "id desc", :dependent => :destroy
12 has_many :cloners, :dependent => :destroy
1213
1314 validates_presence_of :user_id, :project_id, :name
1415 validates_format_of :name, :with => /^[a-z0-9_\-]+$/i,
2929 find_by_name(name) || raise(ActiveRecord::RecordNotFound)
3030 end
3131
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
3240 def self.create_git_repository(path)
3341 git_backend.create(full_path_from_partial_path(path))
3442 end
230230 users_by_email = users.inject({}){|hash, user| hash[user.email] = user; hash }
231231 users_by_email
232232 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
233237
234238 protected
235239 def set_as_mainline_if_first
toggle raw diff

app/views/committers/auto_complete_for_user_login.js.erb

 
11<ul>
22<% @users.each do |user| -%>
33<li class="committer">
4<div class="image"><%= gravatar user.email, :size => 32 -%></div>
5<div class="login"><%=h user.login -%></div>
6<div class="email"><span class="informal"><%=h user.email -%></span></div>
4 <div class="image"><%= gravatar user.email, :size => 32 -%></div>
5 <div class="login"><%=h user.login -%></div>
6 <div class="email"><span class="informal"><%=h user.email -%></span></div>
77</li>
88<% end -%>
99</ul>
toggle raw diff

app/views/committers/new.html.erb

 
2828 :url => {:controller => "committers", :action => "create"}, :method => :post do |f| -%>
2929 <p>
3030 <%= f.label :login, "Existing username <small>(search-as-you-type)</small>" -%><br />
31 <%= text_field_with_auto_complete :user, :login, {}, :skip_style => true -%>
31 <%= text_field_with_auto_complete :user, :login, {}, :skip_style => true, :select => :login -%>
3232 </p>
3333 <p>
3434 <%= f.submit "Add as committer" -%> or <%= link_to "cancel", [@repository.project, @repository] -%>
toggle raw diff

app/views/repositories/_infobox.html.erb

 
4141 </div>
4242 </li>
4343 <% end -%>
44 <li>The repository has been cloned <%= @repository.cloners.size %> times.</li>
4445</ul>
toggle raw diff

app/views/site/about.html.erb

 
2828 <li>Making it easier for maintainers to accept contributions</li>
2929 </ul>
3030
31 <p>Gitorious still under development and there's many many more things I'd like
31 <p>Gitorious is still under development and there's many many more things I'd like
3232 to do with it. If you have ideas for improvements or technical issues please
3333 do stop by the <a href="http://groups.google.com/group/gitorious">discussion group</a>.</p>
3434
toggle raw diff

app/views/site/faq.html.erb

 
44<p>
55 Most likely the repository you're trying to clone is empty, and the error
66 message is git's friendly way of telling you that. See below (or by clicking
7 "more info" next to repositorys "push url") for info on how t push to it.
7 "more info" next to the repository's "push url") for info on how to push to it.
88</p>
99
1010<hr />
5252<h3>Why do I need to upload my public SSH key?</h3>
5353<p>
5454 When you push to a Git repository, your public key is how we authenticate
55 you and if have the permissions required to do a commit to a given repository
55 you and check if have the permissions required to do a commit to a given repository
5656</p>
5757
5858<hr />
toggle raw diff

app/views/trees/show.html.erb

 
1414 <% else -%>
1515 <td class="node file"><%= link_to h(node.basename), blob_path(params[:id], node.name) -%></td>
1616 <% 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, "&hellip;"), 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, "&hellip;"), commit_path(last_commit.id) -%>
21 </td>
22 <% else -%>
23 <td class="meta"></td>
24 <td class="meta commit_message"></td>
25 <% end -%>
2226 </tr>
2327 <% end -%>
2428</table>
toggle raw diff

data/GeoIP.dat

 
toggle raw diff

db/migrate/023_create_cloners.rb

 
1class CreateCloners < ActiveRecord::Migration
2 def self.up
3 create_table :cloners do |t|
4 t.string :ip
5 t.string :country_code, :length => 2
6 t.string :country
7 t.datetime :date
8 t.integer :repository_id
9 end
10 end
11
12 def self.down
13 drop_table :cloners
14 end
15end
16
toggle raw diff

log/.gitignore

 
1*.log
1*.log
2*.pid
toggle raw diff

script/git-daemon

 
1#!/usr/bin/env ruby
2
3require 'rubygems'
4require 'daemons'
5require 'geoip'
6require 'socket'
7require "optparse"
8
9ENV["RAILS_ENV"] ||= "production"
10require File.dirname(__FILE__)+'/../config/environment'
11
12Rails.configuration.log_level = :info # Disable debug
13ActiveRecord::Base.allow_concurrency = true
14
15BASE_PATH = File.expand_path(GitoriousConfig['repository_base_path'])
16
17TIMEOUT = 30
18MAX_CHILDREN = 30
19$children_reaped = 0
20$children_active = 0
21
22module 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
162end
163
164options = {
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
173OptionParser.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
204end.parse!
205
206@git_daemon = Git::Daemon.new(options)
207
208trap("SIGKILL") { @git_daemon.handle_stop("SIGKILL") }
209trap("TERM") { @git_daemon.handle_stop("TERM") }
210trap("SIGINT") { @git_daemon.handle_stop("SIGINT") }
211trap("CLD") { @git_daemon.handle_cld }
212
213@git_daemon.start
214
toggle raw diff

spec/fixtures/cloners.yml

 
1# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html
2
3argentina:
4 ip: 200.45.34.21
5 country: Argentina
6 country_code: AR
7 date: 2008-04-13 23:40:30
8
9mexico:
10 ip: 200.53.12.21
11 country: Mexico
12 country_code: MX
13 date: 2008-04-13 23:40:30
toggle raw diff

spec/models/cloner_spec.rb

 
1require File.dirname(__FILE__) + '/../spec_helper'
2require 'geoip'
3
4describe Cloner do
5 before(:all) do
6 @geoip = GeoIP.new(File.join(RAILS_ROOT, "data", "GeoIP.dat"))
7 end
8
9 before(:each) do
10 @cloner = Cloner.new
11 end
12
13 it "should has a valid country" do
14 localization = @geoip.country(cloners(:argentina).ip)
15 localization[3].should == cloners(:argentina).country_code
16 localization[5].should == cloners(:argentina).country
17 end
18end
toggle raw diff

spec/models/project_spec.rb

 
103103 project.send(attr).should == 'http://blah.com'
104104 end
105105 end
106
107 it "should not prepend http:// to empty urls" do
108 project = projects(:johans)
109 [ :home_url, :mailinglist_url, :bugtracker_url ].each do |attr|
110 project.send("#{attr}=", '')
111 project.send(attr).should be_blank
112 project.send("#{attr}=", nil)
113 project.send(attr).should be_blank
114 end
115 end
106116
107117end
toggle raw diff