# HG changeset patch
# User Chris Cannam
# Date 1297171491 0
# Node ID ff5112f823e565ca3dde51c22ea671e7242051e8
# Parent 30203ffaa6122101593e338ab6af88d50f4b0cd4# Parent bb7cac2ac146e3a79bf15e16d789329fb44a324c
Merge from branch "feature_72", to provide just enough of the site support for external repos to permit testing while developing the conversion code
diff -r bb7cac2ac146 -r ff5112f823e5 app/controllers/projects_controller.rb
--- a/app/controllers/projects_controller.rb Tue Feb 08 13:15:47 2011 +0000
+++ b/app/controllers/projects_controller.rb Tue Feb 08 13:24:51 2011 +0000
@@ -46,19 +46,20 @@
include RepositoriesHelper
include ProjectsHelper
- # Lists visible projects
+ # Lists visible projects. Paginator is for top-level projects only
+ # (subprojects belong to them)
def index
respond_to do |format|
format.html {
- sort_init 'lft'
- sort_update %w(lft title created_on updated_on)
+ sort_init 'name'
+ sort_update %w(name lft created_on updated_on)
@limit = per_page_option
- @project_count = Project.visible.count
+ @project_count = Project.visible_roots.count
@project_pages = Paginator.new self, @project_count, @limit, params['page']
@offset ||= @project_pages.current.offset
- @projects = Project.visible.all(:offset => @offset, :limit => @limit, :order => sort_clause)
+ @projects = Project.visible_roots.all(:offset => @offset, :limit => @limit, :order => sort_clause)
if User.current.logged?
- @user_projects = User.current.projects.sort_by(&:lft)
+ @user_projects = User.current.projects.sort_by(&:name)
end
render :template => 'projects/index.rhtml', :layout => !request.xhr?
}
diff -r bb7cac2ac146 -r ff5112f823e5 app/helpers/projects_helper.rb
--- a/app/helpers/projects_helper.rb Tue Feb 08 13:15:47 2011 +0000
+++ b/app/helpers/projects_helper.rb Tue Feb 08 13:24:51 2011 +0000
@@ -163,70 +163,17 @@
s << "
"
s << ""
- s << sort_header_tag('lft', :caption => l("field_name"), :default_order => 'desc')
+ s << sort_header_tag('name', :caption => l("field_name"))
s << "" << l("label_managers") << " | "
s << sort_header_tag('created_on', :default_order => 'desc')
s << sort_header_tag('updated_on', :default_order => 'desc')
s << "
"
- ancestors = []
original_project = @project
- oddeven = 'even'
- level = 0
projects.each do |project|
-
- # set the project environment to please macros.
-
- @project = project
-
- if (ancestors.empty? || project.is_descendant_of?(ancestors.last))
- level = level + 1
- else
- level = 0
- oddeven = cycle('odd','even')
- ancestors.pop
- while (ancestors.any? && !project.is_descendant_of?(ancestors.last))
- ancestors.pop
- end
- end
-
- classes = (ancestors.empty? ? 'root' : 'child')
-
- s << ""
- s << "" << link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}") << " | "
- s << ""
-
- u = project.users_by_role
- if u
- u.keys.each do |r|
- if r.allowed_to?(:edit_project)
- mgrs = []
- u[r].sort.each do |m|
- mgrs << link_to_user(m)
- end
- if mgrs.size < 3
- s << '' << mgrs.join(', ') << ''
- else
- s << mgrs.join(', ')
- end
- end
- end
- end
-
- s << " | "
- s << "" << format_date(project.created_on) << " | "
- s << "" << format_date(project.updated_on) << " | "
-
- s << "
"
- s << ""
- s << ""
- s << textilizable(project.short_description, :project => project) unless project.description.blank?
- s << " | "
- s << "
"
-
- ancestors << project
+ s << render_project_in_table(project, cycle('odd', 'even'), 0)
end
s << "
"
@@ -237,6 +184,58 @@
end
+ def render_project_in_table(project, oddeven, level)
+
+ # set the project environment to please macros.
+ @project = project
+
+ classes = (level == 0 ? 'root' : 'child')
+
+ s = ""
+
+ s << ""
+ s << "" << link_to_project(project, {}, :class => "project #{User.current.member_of?(project) ? 'my-project' : nil}");
+ s << " "
+ unless project.description.blank?
+ s << ""
+ s << textilizable(project.short_description, :project => project)
+ s << " "
+ end
+
+ s << " | "
+
+ u = project.users_by_role
+ if u
+ u.keys.each do |r|
+ if r.allowed_to?(:edit_project)
+ mgrs = []
+ u[r].sort.each do |m|
+ mgrs << link_to_user(m)
+ end
+ if mgrs.size < 3
+ s << '' << mgrs.join(', ') << ''
+ else
+ s << mgrs.join(', ')
+ end
+ end
+ end
+ end
+
+ s << " | "
+ s << "" << format_date(project.created_on) << " | "
+ s << "" << format_date(project.updated_on) << " | "
+
+ s << "
"
+
+ project.children.each do |child|
+ s << render_project_in_table(child, oddeven, level + 1)
+ end
+
+ s
+ end
+
# Returns a set of options for a select field, grouped by project.
def version_options_for_select(versions, selected=nil)
diff -r bb7cac2ac146 -r ff5112f823e5 app/models/project.rb
--- a/app/models/project.rb Tue Feb 08 13:15:47 2011 +0000
+++ b/app/models/project.rb Tue Feb 08 13:24:51 2011 +0000
@@ -83,6 +83,7 @@
named_scope :active, { :conditions => "#{Project.table_name}.status = #{STATUS_ACTIVE}"}
named_scope :all_public, { :conditions => { :is_public => true } }
named_scope :visible, lambda { { :conditions => Project.visible_by(User.current) } }
+ named_scope :visible_roots, lambda { { :conditions => Project.root_visible_by(User.current) } }
def identifier=(identifier)
super unless identifier_frozen?
@@ -114,6 +115,10 @@
end
end
+ def self.root_visible_by(user=nil)
+ return "#{Project.table_name}.parent_id IS NULL AND " + visible_by(user)
+ end
+
def self.allowed_to_condition(user, permission, options={})
statements = []
base_statement = "#{Project.table_name}.status=#{Project::STATUS_ACTIVE}"
diff -r bb7cac2ac146 -r ff5112f823e5 app/views/layouts/base.rhtml
--- a/app/views/layouts/base.rhtml Tue Feb 08 13:15:47 2011 +0000
+++ b/app/views/layouts/base.rhtml Tue Feb 08 13:24:51 2011 +0000
@@ -3,8 +3,8 @@
<%=h html_title %>
-
-
+
+
<%= favicon %>
<%= stylesheet_link_tag 'application', :media => 'all' %>
<%= stylesheet_link_tag 'rtl', :media => 'all' if l(:direction) == 'rtl' %>
diff -r bb7cac2ac146 -r ff5112f823e5 extra/soundsoftware/SoundSoftware.pm
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/SoundSoftware.pm Tue Feb 08 13:24:51 2011 +0000
@@ -0,0 +1,433 @@
+package Apache::Authn::SoundSoftware;
+
+=head1 Apache::Authn::SoundSoftware
+
+SoundSoftware - a mod_perl module for Apache authentication against a
+Redmine database and optional LDAP implementing the access control
+rules required for the SoundSoftware.ac.uk repository site.
+
+=head1 SYNOPSIS
+
+This module is closely based on the Redmine.pm authentication module
+provided with Redmine. It is intended to be used for authentication
+in front of a repository service such as hgwebdir.
+
+Requirements:
+
+1. Clone/pull from repo for public project: Any user, no
+authentication required
+
+2. Clone/pull from repo for private project: Project members only
+
+3. Push to repo for public project: "Permitted" users only (this
+probably means project members who are also identified in the hgrc web
+section for the repository and so will be approved by hgwebdir?)
+
+4. Push to repo for private project: "Permitted" users only (as above)
+
+=head1 INSTALLATION
+
+Debian/ubuntu:
+
+ apt-get install libapache-dbi-perl libapache2-mod-perl2 \
+ libdbd-mysql-perl libauthen-simple-ldap-perl libio-socket-ssl-perl
+
+Note that LDAP support is hardcoded "on" in this script (it is
+optional in the original Redmine.pm).
+
+=head1 CONFIGURATION
+
+ ## This module has to be in your perl path
+ ## eg: /usr/local/lib/site_perl/Apache/Authn/SoundSoftware.pm
+ PerlLoadModule Apache::Authn::SoundSoftware
+
+ # Example when using hgwebdir
+ ScriptAlias / "/var/hg/hgwebdir.cgi/"
+
+
+ AuthName "Mercurial"
+ AuthType Basic
+ Require valid-user
+ PerlAccessHandler Apache::Authn::SoundSoftware::access_handler
+ PerlAuthenHandler Apache::Authn::SoundSoftware::authen_handler
+ SoundSoftwareDSN "DBI:mysql:database=redmine;host=localhost"
+ SoundSoftwareDbUser "redmine"
+ SoundSoftwareDbPass "password"
+ Options +ExecCGI
+ AddHandler cgi-script .cgi
+ ## Optional where clause (fulltext search would be slow and
+ ## database dependant).
+ # SoundSoftwareDbWhereClause "and members.role_id IN (1,2)"
+ ## Optional prefix for local repository URLs
+ # SoundSoftwareRepoPrefix "/var/hg/"
+
+
+See the original Redmine.pm for further configuration notes.
+
+=cut
+
+use strict;
+use warnings FATAL => 'all', NONFATAL => 'redefine';
+
+use DBI;
+use Digest::SHA1;
+use Authen::Simple::LDAP;
+use Apache2::Module;
+use Apache2::Access;
+use Apache2::ServerRec qw();
+use Apache2::RequestRec qw();
+use Apache2::RequestUtil qw();
+use Apache2::Const qw(:common :override :cmd_how);
+use APR::Pool ();
+use APR::Table ();
+
+my @directives = (
+ {
+ name => 'SoundSoftwareDSN',
+ req_override => OR_AUTHCFG,
+ args_how => TAKE1,
+ errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
+ },
+ {
+ name => 'SoundSoftwareDbUser',
+ req_override => OR_AUTHCFG,
+ args_how => TAKE1,
+ },
+ {
+ name => 'SoundSoftwareDbPass',
+ req_override => OR_AUTHCFG,
+ args_how => TAKE1,
+ },
+ {
+ name => 'SoundSoftwareDbWhereClause',
+ req_override => OR_AUTHCFG,
+ args_how => TAKE1,
+ },
+ {
+ name => 'SoundSoftwareRepoPrefix',
+ req_override => OR_AUTHCFG,
+ args_how => TAKE1,
+ },
+);
+
+sub SoundSoftwareDSN {
+ my ($self, $parms, $arg) = @_;
+ $self->{SoundSoftwareDSN} = $arg;
+ my $query = "SELECT
+ hashed_password, auth_source_id, permissions
+ FROM members, projects, users, roles, member_roles
+ WHERE
+ projects.id=members.project_id
+ AND member_roles.member_id=members.id
+ AND users.id=members.user_id
+ AND roles.id=member_roles.role_id
+ AND users.status=1
+ AND login=?
+ AND identifier=? ";
+ $self->{SoundSoftwareQuery} = trim($query);
+}
+
+sub SoundSoftwareDbUser { set_val('SoundSoftwareDbUser', @_); }
+sub SoundSoftwareDbPass { set_val('SoundSoftwareDbPass', @_); }
+sub SoundSoftwareDbWhereClause {
+ my ($self, $parms, $arg) = @_;
+ $self->{SoundSoftwareQuery} = trim($self->{SoundSoftwareQuery}.($arg ? $arg : "")." ");
+}
+
+sub SoundSoftwareRepoPrefix {
+ my ($self, $parms, $arg) = @_;
+ if ($arg) {
+ $self->{SoundSoftwareRepoPrefix} = $arg;
+ }
+}
+
+sub trim {
+ my $string = shift;
+ $string =~ s/\s{2,}/ /g;
+ return $string;
+}
+
+sub set_val {
+ my ($key, $self, $parms, $arg) = @_;
+ $self->{$key} = $arg;
+}
+
+Apache2::Module::add(__PACKAGE__, \@directives);
+
+
+my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
+
+sub access_handler {
+ my $r = shift;
+
+ print STDERR "SoundSoftware.pm: In access handler at " . scalar localtime() . "\n";
+
+ unless ($r->some_auth_required) {
+ $r->log_reason("No authentication has been configured");
+ return FORBIDDEN;
+ }
+
+ my $method = $r->method;
+
+ print STDERR "SoundSoftware.pm: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n";
+ print STDERR "SoundSoftware.pm: Accept: " . $r->headers_in->{Accept} . "\n";
+
+ if (!defined $read_only_methods{$method}) {
+ print STDERR "SoundSoftware.pm: Method is not read-only, authentication handler required\n";
+ return OK;
+ }
+
+ my $dbh = connect_database($r);
+ unless ($dbh) {
+ print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n";
+ return FORBIDDEN;
+ }
+
+
+print STDERR "Connected to db, dbh is " . $dbh . "\n";
+
+ my $project_id = get_project_identifier($dbh, $r);
+ my $status = get_project_status($dbh, $project_id, $r);
+
+ $dbh->disconnect();
+ undef $dbh;
+
+ if ($status == 0) { # nonexistent
+ print STDERR "SoundSoftware.pm: Project does not exist, refusing access\n";
+ return FORBIDDEN;
+ } elsif ($status == 1) { # public
+ print STDERR "SoundSoftware.pm: Project is public, no restriction here\n";
+ $r->set_handlers(PerlAuthenHandler => [\&OK])
+ } else { # private
+ print STDERR "SoundSoftware.pm: Project is private, authentication handler required\n";
+ }
+
+ return OK
+}
+
+sub authen_handler {
+ my $r = shift;
+
+ print STDERR "SoundSoftware.pm: In authentication handler at " . scalar localtime() . "\n";
+
+ my $dbh = connect_database($r);
+ unless ($dbh) {
+ print STDERR "SoundSoftware.pm: Database connection failed!: " . $DBI::errstr . "\n";
+ return AUTH_REQUIRED;
+ }
+
+ my $project_id = get_project_identifier($dbh, $r);
+ my $realm = get_realm($dbh, $project_id, $r);
+ $r->auth_name($realm);
+
+ my ($res, $redmine_pass) = $r->get_basic_auth_pw();
+ unless ($res == OK) {
+ $dbh->disconnect();
+ undef $dbh;
+ return $res;
+ }
+
+ print STDERR "SoundSoftware.pm: User is " . $r->user . ", got password\n";
+
+ my $permitted = is_permitted($dbh, $project_id, $r->user, $redmine_pass, $r);
+
+ $dbh->disconnect();
+ undef $dbh;
+
+ if ($permitted) {
+ return OK;
+ } else {
+ print STDERR "SoundSoftware.pm: Not permitted\n";
+ $r->note_auth_failure();
+ return AUTH_REQUIRED;
+ }
+}
+
+sub get_project_status {
+ my $dbh = shift;
+ my $project_id = shift;
+ my $r = shift;
+
+ if (!defined $project_id or $project_id eq '') {
+ return 0; # nonexistent
+ }
+
+ my $sth = $dbh->prepare(
+ "SELECT is_public FROM projects WHERE projects.identifier = ?;"
+ );
+
+ $sth->execute($project_id);
+ my $ret = 0; # nonexistent
+ if (my @row = $sth->fetchrow_array) {
+ if ($row[0] eq "1" || $row[0] eq "t") {
+ $ret = 1; # public
+ } else {
+ $ret = 2; # private
+ }
+ }
+ $sth->finish();
+ undef $sth;
+
+ $ret;
+}
+
+sub is_permitted {
+ my $dbh = shift;
+ my $project_id = shift;
+ my $redmine_user = shift;
+ my $redmine_pass = shift;
+ my $r = shift;
+
+ my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
+
+ my $cfg = Apache2::Module::get_config
+ (__PACKAGE__, $r->server, $r->per_dir_config);
+
+ my $query = $cfg->{SoundSoftwareQuery};
+ my $sth = $dbh->prepare($query);
+ $sth->execute($redmine_user, $project_id);
+
+ my $ret;
+ while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) {
+
+ # Test permissions for this user before we verify credentials
+ # -- if the user is not permitted this action anyway, there's
+ # not much point in e.g. contacting the LDAP
+
+ my $method = $r->method;
+
+ if ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/)
+ || $permissions =~ /:commit_access/) {
+
+ # User would be permitted this action, if their
+ # credentials checked out -- test those now
+
+ print STDERR "SoundSoftware.pm: User $redmine_user has required role, checking credentials\n";
+
+ unless ($auth_source_id) {
+ if ($hashed_password eq $pass_digest) {
+ print STDERR "SoundSoftware.pm: User $redmine_user authenticated via password\n";
+ $ret = 1;
+ last;
+ }
+ } else {
+ my $sthldap = $dbh->prepare(
+ "SELECT host,port,tls,account,account_password,base_dn,attr_login FROM auth_sources WHERE id = ?;"
+ );
+ $sthldap->execute($auth_source_id);
+ while (my @rowldap = $sthldap->fetchrow_array) {
+ my $ldap = Authen::Simple::LDAP->new(
+ host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
+ port => $rowldap[1],
+ basedn => $rowldap[5],
+ binddn => $rowldap[3] ? $rowldap[3] : "",
+ bindpw => $rowldap[4] ? $rowldap[4] : "",
+ filter => "(".$rowldap[6]."=%s)"
+ );
+ if ($ldap->authenticate($redmine_user, $redmine_pass)) {
+ print STDERR "SoundSoftware.pm: User $redmine_user authenticated via LDAP\n";
+ $ret = 1;
+ }
+ }
+ $sthldap->finish();
+ undef $sthldap;
+ }
+ } else {
+ print STDERR "SoundSoftware.pm: User $redmine_user lacks required role for this project\n";
+ }
+ }
+
+ $sth->finish();
+ undef $sth;
+
+ $ret;
+}
+
+sub get_project_identifier {
+ my $dbh = shift;
+ my $r = shift;
+
+ my $location = $r->location;
+ my ($repo) = $r->uri =~ m{$location/*([^/]+)};
+
+ return $repo if (!$repo);
+
+ $repo =~ s/[^a-zA-Z0-9\._-]//g;
+
+ # The original Redmine.pm returns the string just calculated as
+ # the project identifier. That won't do for us -- we may have
+ # (and in fact already do have, in our test instance) projects
+ # whose repository names differ from the project identifiers.
+
+ # This is a rather fundamental change because it means that almost
+ # every request needs more than one database query -- which
+ # prompts us to start passing around $dbh instead of connecting
+ # locally within each function as is done in Redmine.pm.
+
+ my $sth = $dbh->prepare(
+ "SELECT projects.identifier FROM projects, repositories WHERE repositories.project_id = projects.id AND repositories.url LIKE ?;"
+ );
+
+ my $cfg = Apache2::Module::get_config
+ (__PACKAGE__, $r->server, $r->per_dir_config);
+
+ my $prefix = $cfg->{SoundSoftwareRepoPrefix};
+ if (!defined $prefix) { $prefix = '%/'; }
+
+ my $identifier = '';
+
+ $sth->execute($prefix . $repo);
+ my $ret = 0;
+ if (my @row = $sth->fetchrow_array) {
+ $identifier = $row[0];
+ }
+ $sth->finish();
+ undef $sth;
+
+ print STDERR "SoundSoftware.pm: Repository '$repo' belongs to project '$identifier'\n";
+
+ $identifier;
+}
+
+sub get_realm {
+ my $dbh = shift;
+ my $project_id = shift;
+ my $r = shift;
+
+ my $sth = $dbh->prepare(
+ "SELECT projects.name FROM projects WHERE projects.identifier = ?;"
+ );
+
+ my $name = $project_id;
+
+ $sth->execute($project_id);
+ my $ret = 0;
+ if (my @row = $sth->fetchrow_array) {
+ $name = $row[0];
+ }
+ $sth->finish();
+ undef $sth;
+
+ # be timid about characters not permitted in auth realm and revert
+ # to project identifier if any are found
+ if ($name =~ m/[^\w\d\s\._-]/) {
+ $name = $project_id;
+ }
+
+ my $realm = '"Mercurial repository for ' . "'$name'" . '"';
+
+ $realm;
+}
+
+sub connect_database {
+ my $r = shift;
+
+ my $cfg = Apache2::Module::get_config
+ (__PACKAGE__, $r->server, $r->per_dir_config);
+
+ return DBI->connect($cfg->{SoundSoftwareDSN},
+ $cfg->{SoundSoftwareDbUser},
+ $cfg->{SoundSoftwareDbPass});
+}
+
+1;
diff -r bb7cac2ac146 -r ff5112f823e5 extra/soundsoftware/reposman-soundsoftware.rb
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/extra/soundsoftware/reposman-soundsoftware.rb Tue Feb 08 13:24:51 2011 +0000
@@ -0,0 +1,350 @@
+#!/usr/bin/env ruby
+
+# == Synopsis
+#
+# reposman: manages your repositories with Redmine
+#
+# == Usage
+#
+# reposman [OPTIONS...] -s [DIR] -r [HOST]
+#
+# Examples:
+# reposman --svn-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
+# reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git
+#
+# == Arguments (mandatory)
+#
+# -s, --svn-dir=DIR use DIR as base directory for svn repositories
+# -r, --redmine-host=HOST assume Redmine is hosted on HOST. Examples:
+# -r redmine.example.net
+# -r http://redmine.example.net
+# -r https://example.net/redmine
+# -k, --key=KEY use KEY as the Redmine API key
+#
+# == Options
+#
+# -o, --owner=OWNER owner of the repository. using the rails login
+# allow user to browse the repository within
+# Redmine even for private project. If you want to
+# share repositories through Redmine.pm, you need
+# to use the apache owner.
+# -g, --group=GROUP group of the repository. (default: root)
+# --scm=SCM the kind of SCM repository you want to create (and
+# register) in Redmine (default: Subversion).
+# reposman is able to create Git and Subversion
+# repositories. For all other kind, you must specify
+# a --command option
+# -u, --url=URL the base url Redmine will use to access your
+# repositories. This option is used to automatically
+# register the repositories in Redmine. The project
+# identifier will be appended to this url. Examples:
+# -u https://example.net/svn
+# -u file:///var/svn/
+# if this option isn't set, reposman will register
+# the repositories with local file paths in Redmine
+# -c, --command=COMMAND use this command instead of "svnadmin create" to
+# create a repository. This option can be used to
+# create repositories other than subversion and git
+# kind.
+# This command override the default creation for git
+# and subversion.
+# --http-user=USER User for HTTP Basic authentication with Redmine WS
+# --http-pass=PASSWORD Password for Basic authentication with Redmine WS
+# -t, --test only show what should be done
+# -h, --help show help and exit
+# -v, --verbose verbose
+# -V, --version print version and exit
+# -q, --quiet no log
+#
+# == References
+#
+# You can find more information on the redmine's wiki : http://www.redmine.org/wiki/redmine/HowTos
+
+
+require 'getoptlong'
+require 'rdoc/usage'
+require 'find'
+require 'etc'
+
+Version = "1.3"
+SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
+
+opts = GetoptLong.new(
+ ['--svn-dir', '-s', GetoptLong::REQUIRED_ARGUMENT],
+ ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
+ ['--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
+ ['--owner', '-o', GetoptLong::REQUIRED_ARGUMENT],
+ ['--group', '-g', GetoptLong::REQUIRED_ARGUMENT],
+ ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
+ ['--command' , '-c', GetoptLong::REQUIRED_ARGUMENT],
+ ['--scm', GetoptLong::REQUIRED_ARGUMENT],
+ ['--http-user', GetoptLong::REQUIRED_ARGUMENT],
+ ['--http-pass', GetoptLong::REQUIRED_ARGUMENT],
+ ['--test', '-t', GetoptLong::NO_ARGUMENT],
+ ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
+ ['--version', '-V', GetoptLong::NO_ARGUMENT],
+ ['--help' , '-h', GetoptLong::NO_ARGUMENT],
+ ['--quiet' , '-q', GetoptLong::NO_ARGUMENT]
+ )
+
+$verbose = 0
+$quiet = false
+$redmine_host = ''
+$repos_base = ''
+$http_user = ''
+$http_pass = ''
+$svn_owner = 'root'
+$svn_group = 'root'
+$use_groupid = true
+$svn_url = false
+$test = false
+$scm = 'Subversion'
+
+def log(text, options={})
+ level = options[:level] || 0
+ puts text unless $quiet or level > $verbose
+ exit 1 if options[:exit]
+end
+
+def system_or_raise(command)
+ raise "\"#{command}\" failed" unless system command
+end
+
+module SCM
+
+ module Subversion
+ def self.create(path)
+ system_or_raise "svnadmin create #{path}"
+ end
+ end
+
+ module Git
+ def self.create(path)
+ Dir.mkdir path
+ Dir.chdir(path) do
+ system_or_raise "git --bare init --shared"
+ system_or_raise "git update-server-info"
+ end
+ end
+ end
+
+end
+
+begin
+ opts.each do |opt, arg|
+ case opt
+ when '--svn-dir'; $repos_base = arg.dup
+ when '--redmine-host'; $redmine_host = arg.dup
+ when '--key'; $api_key = arg.dup
+ when '--owner'; $svn_owner = arg.dup; $use_groupid = false;
+ when '--group'; $svn_group = arg.dup; $use_groupid = false;
+ when '--url'; $svn_url = arg.dup
+ when '--scm'; $scm = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm)
+ when '--http-user'; $http_user = arg.dup
+ when '--http-pass'; $http_pass = arg.dup
+ when '--command'; $command = arg.dup
+ when '--verbose'; $verbose += 1
+ when '--test'; $test = true
+ when '--version'; puts Version; exit
+ when '--help'; RDoc::usage
+ when '--quiet'; $quiet = true
+ end
+ end
+rescue
+ exit 1
+end
+
+if $test
+ log("running in test mode")
+end
+
+# Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
+if $command.nil?
+ begin
+ scm_module = SCM.const_get($scm)
+ rescue
+ log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
+ end
+end
+
+$svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
+
+if ($redmine_host.empty? or $repos_base.empty?)
+ RDoc::usage
+end
+
+unless File.directory?($repos_base)
+ log("directory '#{$repos_base}' doesn't exists", :exit => true)
+end
+
+begin
+ require 'active_resource'
+rescue LoadError
+ log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
+end
+
+class Project < ActiveResource::Base
+ self.headers["User-agent"] = "Redmine repository manager/#{Version}"
+end
+
+log("querying Redmine for projects...", :level => 1);
+
+$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
+$redmine_host.gsub!(/\/$/, '')
+
+Project.site = "#{$redmine_host}/sys";
+Project.user = $http_user;
+Project.password = $http_pass;
+
+begin
+ # Get all active projects that have the Repository module enabled
+ projects = Project.find(:all, :params => {:key => $api_key})
+rescue => e
+ log("Unable to connect to #{Project.site}: #{e}", :exit => true)
+end
+
+if projects.nil?
+ log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
+end
+
+log("retrieved #{projects.size} projects", :level => 1)
+
+def set_owner_and_rights(project, repos_path, &block)
+ if RUBY_PLATFORM =~ /mswin/
+ yield if block_given?
+ else
+ uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
+ right = project.is_public ? 02775 : 02770
+ yield if block_given?
+ Find.find(repos_path) do |f|
+ File.chmod right, f
+ File.chown uid, gid, f
+ end
+ end
+end
+
+def other_read_right?(file)
+ (File.stat(file).mode & 0007).zero? ? false : true
+end
+
+def owner_name(file)
+ mswin? ?
+ $svn_owner :
+ Etc.getpwuid( File.stat(file).uid ).name
+end
+
+def mswin?
+ (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
+end
+
+projects.each do |project|
+ log("treating project #{project.name}", :level => 1)
+
+ if project.identifier.empty?
+ log("\tno identifier for project #{project.name}")
+ next
+ elsif not project.identifier.match(/^[a-z0-9\-]+$/)
+ log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
+ next;
+ end
+
+ repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
+
+ create_repos = false
+
+ # Logic required for SoundSoftware.ac.uk repositories:
+ #
+ # * If the project has a repository path declared already,
+ # - if it's a local path,
+ # - if it does not exist
+ # - if it has the right root
+ # - create it
+ # - else
+ # - leave alone (remote repository)
+ # * else
+ # - create repository with same name as project
+ # - set to project
+
+ if project.respond_to?(:repository)
+
+ repos_url = project.repository.url;
+ log("\texisting url for project #{project.identifier} is #{repos_url}");
+
+ if repos_url.match(/^file:\//) || repos_url.match(/^\//)
+
+ repos_url = repos_url.gsub(/^file:\/*/, "/");
+ log("\tthis is a local file path, at #{repos_url}");
+
+ if repos_url.slice(0, $repos_base.length) != $repos_base
+ log("\tit is in the wrong place: replacing it");
+ # leave repos_path set to our original suggestion
+ create_repos = true
+ else
+ if !File.directory?(repos_url)
+ log("\tit doesn't exist; we should create it");
+ repos_path = repos_url
+ create_repos = true
+ else
+ log("\tit exists and is in the right place");
+ end
+ end
+ else
+ log("\tthis is a remote path, leaving alone");
+ end
+ else
+ log("\tproject #{project.identifier} has no repository registered")
+# if File.directory?(repos_path)
+# log("\trepository path #{repos_path} already exists, not creating")
+# else
+ create_repos = true
+# end
+ end
+
+ if create_repos
+
+ registration_url = repos_path
+ if $svn_url
+ registration_url = "#{$svn_url}#{project.identifier}"
+ end
+
+ if $test
+ log("\tproposal: create repository #{repos_path}")
+ log("\tproposal: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}")
+ next
+ end
+
+# No -- we need "other" users to be able to read it. Access control
+# is not handled through Unix user id anyway
+# project.is_public ? File.umask(0002) : File.umask(0007)
+ File.umask(0002)
+
+ log("\taction: create repository #{repos_path}")
+
+ begin
+ if !File.directory?(repos_path)
+ set_owner_and_rights(project, repos_path) do
+ if scm_module.nil?
+ log("\trunning command: #{$command} #{repos_path}")
+ system_or_raise "#{$command} #{repos_path}"
+ else
+ scm_module.create(repos_path)
+ end
+ end
+ end
+ rescue => e
+ log("\tunable to create #{repos_path} : #{e}\n")
+ next
+ end
+
+ begin
+ log("\taction: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}");
+ project.post(:repository, :vendor => $scm, :repository => {:url => "#{registration_url}"}, :key => $api_key)
+ rescue => e
+ log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
+ end
+
+ log("\trepository #{repos_path} created");
+ end
+
+end
+
diff -r bb7cac2ac146 -r ff5112f823e5 extra/svn/SoundSoftware.pm
--- a/extra/svn/SoundSoftware.pm Tue Feb 08 13:15:47 2011 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,422 +0,0 @@
-package Apache::Authn::SoundSoftware;
-
-=head1 Apache::Authn::SoundSoftware
-
-SoundSoftware - a mod_perl module for Apache authentication against a
-Redmine database and optional LDAP implementing the access control
-rules required for the SoundSoftware.ac.uk repository site.
-
-=head1 SYNOPSIS
-
-This module is closely based on the Redmine.pm authentication module
-provided with Redmine. It is intended to be used for authentication
-in front of a repository service such as hgwebdir.
-
-Requirements:
-
-1. Clone/pull from repo for public project: Any user, no
-authentication required
-
-2. Clone/pull from repo for private project: Project members only
-
-3. Push to repo for public project: "Permitted" users only (this
-probably means project members who are also identified in the hgrc web
-section for the repository and so will be approved by hgwebdir?)
-
-4. Push to repo for private project: "Permitted" users only (as above)
-
-=head1 INSTALLATION
-
-Debian/ubuntu:
-
- apt-get install libapache-dbi-perl libapache2-mod-perl2 \
- libdbd-mysql-perl libauthen-simple-ldap-perl libio-socket-ssl-perl
-
-Note that LDAP support is hardcoded "on" in this script (it is
-optional in the original Redmine.pm).
-
-=head1 CONFIGURATION
-
- ## This module has to be in your perl path
- ## eg: /usr/local/lib/site_perl/Apache/Authn/SoundSoftware.pm
- PerlLoadModule Apache::Authn::SoundSoftware
-
- # Example when using hgwebdir
- ScriptAlias / "/var/hg/hgwebdir.cgi/"
-
-
- AuthName "Mercurial"
- AuthType Basic
- Require valid-user
- PerlAccessHandler Apache::Authn::SoundSoftware::access_handler
- PerlAuthenHandler Apache::Authn::SoundSoftware::authen_handler
- SoundSoftwareDSN "DBI:mysql:database=redmine;host=localhost"
- SoundSoftwareDbUser "redmine"
- SoundSoftwareDbPass "password"
- Options +ExecCGI
- AddHandler cgi-script .cgi
- ## Optional where clause (fulltext search would be slow and
- ## database dependant).
- # SoundSoftwareDbWhereClause "and members.role_id IN (1,2)"
- ## Optional prefix for local repository URLs
- # SoundSoftwareRepoPrefix "/var/hg/"
-
-
-See the original Redmine.pm for further configuration notes.
-
-=cut
-
-use strict;
-use warnings FATAL => 'all', NONFATAL => 'redefine';
-
-use DBI;
-use Digest::SHA1;
-use Authen::Simple::LDAP;
-use Apache2::Module;
-use Apache2::Access;
-use Apache2::ServerRec qw();
-use Apache2::RequestRec qw();
-use Apache2::RequestUtil qw();
-use Apache2::Const qw(:common :override :cmd_how);
-use APR::Pool ();
-use APR::Table ();
-
-my @directives = (
- {
- name => 'SoundSoftwareDSN',
- req_override => OR_AUTHCFG,
- args_how => TAKE1,
- errmsg => 'Dsn in format used by Perl DBI. eg: "DBI:Pg:dbname=databasename;host=my.db.server"',
- },
- {
- name => 'SoundSoftwareDbUser',
- req_override => OR_AUTHCFG,
- args_how => TAKE1,
- },
- {
- name => 'SoundSoftwareDbPass',
- req_override => OR_AUTHCFG,
- args_how => TAKE1,
- },
- {
- name => 'SoundSoftwareDbWhereClause',
- req_override => OR_AUTHCFG,
- args_how => TAKE1,
- },
- {
- name => 'SoundSoftwareRepoPrefix',
- req_override => OR_AUTHCFG,
- args_how => TAKE1,
- },
-);
-
-sub SoundSoftwareDSN {
- my ($self, $parms, $arg) = @_;
- $self->{SoundSoftwareDSN} = $arg;
- my $query = "SELECT
- hashed_password, auth_source_id, permissions
- FROM members, projects, users, roles, member_roles
- WHERE
- projects.id=members.project_id
- AND member_roles.member_id=members.id
- AND users.id=members.user_id
- AND roles.id=member_roles.role_id
- AND users.status=1
- AND login=?
- AND identifier=? ";
- $self->{SoundSoftwareQuery} = trim($query);
-}
-
-sub SoundSoftwareDbUser { set_val('SoundSoftwareDbUser', @_); }
-sub SoundSoftwareDbPass { set_val('SoundSoftwareDbPass', @_); }
-sub SoundSoftwareDbWhereClause {
- my ($self, $parms, $arg) = @_;
- $self->{SoundSoftwareQuery} = trim($self->{SoundSoftwareQuery}.($arg ? $arg : "")." ");
-}
-
-sub SoundSoftwareRepoPrefix {
- my ($self, $parms, $arg) = @_;
- if ($arg) {
- $self->{SoundSoftwareRepoPrefix} = $arg;
- }
-}
-
-sub trim {
- my $string = shift;
- $string =~ s/\s{2,}/ /g;
- return $string;
-}
-
-sub set_val {
- my ($key, $self, $parms, $arg) = @_;
- $self->{$key} = $arg;
-}
-
-Apache2::Module::add(__PACKAGE__, \@directives);
-
-
-my %read_only_methods = map { $_ => 1 } qw/GET PROPFIND REPORT OPTIONS/;
-
-sub access_handler {
- my $r = shift;
-
- print STDERR "SoundSoftware.pm: In access handler\n";
-
- unless ($r->some_auth_required) {
- $r->log_reason("No authentication has been configured");
- return FORBIDDEN;
- }
-
- my $method = $r->method;
-
- print STDERR "SoundSoftware.pm: Method: $method, uri " . $r->uri . ", location " . $r->location . "\n";
- print STDERR "SoundSoftware.pm: Accept: " . $r->headers_in->{Accept} . "\n";
-
- if (!defined $read_only_methods{$method}) {
- print STDERR "SoundSoftware.pm: Method is not read-only, authentication handler required\n";
- return OK;
- }
-
- my $dbh = connect_database($r);
-
- my $project_id = get_project_identifier($dbh, $r);
- my $status = get_project_status($dbh, $project_id, $r);
-
- $dbh->disconnect();
- undef $dbh;
-
- if ($status == 0) { # nonexistent
- print STDERR "SoundSoftware.pm: Project does not exist, refusing access\n";
- return FORBIDDEN;
- } elsif ($status == 1) { # public
- print STDERR "SoundSoftware.pm: Project is public, no restriction here\n";
- $r->set_handlers(PerlAuthenHandler => [\&OK])
- } else { # private
- print STDERR "SoundSoftware.pm: Project is private, authentication handler required\n";
- }
-
- return OK
-}
-
-sub authen_handler {
- my $r = shift;
-
- print STDERR "SoundSoftware.pm: In authentication handler\n";
-
- my $dbh = connect_database($r);
-
- my $project_id = get_project_identifier($dbh, $r);
- my $realm = get_realm($dbh, $project_id, $r);
- $r->auth_name($realm);
-
- my ($res, $redmine_pass) = $r->get_basic_auth_pw();
- unless ($res == OK) {
- $dbh->disconnect();
- undef $dbh;
- return $res;
- }
-
- print STDERR "SoundSoftware.pm: User is " . $r->user . ", got password\n";
-
- my $permitted = is_permitted($dbh, $project_id, $r->user, $redmine_pass, $r);
-
- $dbh->disconnect();
- undef $dbh;
-
- if ($permitted) {
- return OK;
- } else {
- print STDERR "SoundSoftware.pm: Not permitted\n";
- $r->note_auth_failure();
- return AUTH_REQUIRED;
- }
-}
-
-sub get_project_status {
- my $dbh = shift;
- my $project_id = shift;
- my $r = shift;
-
- if (!defined $project_id or $project_id eq '') {
- return 0; # nonexistent
- }
-
- my $sth = $dbh->prepare(
- "SELECT is_public FROM projects WHERE projects.identifier = ?;"
- );
-
- $sth->execute($project_id);
- my $ret = 0; # nonexistent
- if (my @row = $sth->fetchrow_array) {
- if ($row[0] eq "1" || $row[0] eq "t") {
- $ret = 1; # public
- } else {
- $ret = 2; # private
- }
- }
- $sth->finish();
- undef $sth;
-
- $ret;
-}
-
-sub is_permitted {
- my $dbh = shift;
- my $project_id = shift;
- my $redmine_user = shift;
- my $redmine_pass = shift;
- my $r = shift;
-
- my $pass_digest = Digest::SHA1::sha1_hex($redmine_pass);
-
- my $cfg = Apache2::Module::get_config
- (__PACKAGE__, $r->server, $r->per_dir_config);
-
- my $query = $cfg->{SoundSoftwareQuery};
- my $sth = $dbh->prepare($query);
- $sth->execute($redmine_user, $project_id);
-
- my $ret;
- while (my ($hashed_password, $auth_source_id, $permissions) = $sth->fetchrow_array) {
-
- # Test permissions for this user before we verify credentials
- # -- if the user is not permitted this action anyway, there's
- # not much point in e.g. contacting the LDAP
-
- my $method = $r->method;
-
- if ((defined $read_only_methods{$method} && $permissions =~ /:browse_repository/)
- || $permissions =~ /:commit_access/) {
-
- # User would be permitted this action, if their
- # credentials checked out -- test those now
-
- print STDERR "SoundSoftware.pm: User $redmine_user has required role, checking credentials\n";
-
- unless ($auth_source_id) {
- if ($hashed_password eq $pass_digest) {
- print STDERR "SoundSoftware.pm: User $redmine_user authenticated via password\n";
- $ret = 1;
- last;
- }
- } else {
- my $sthldap = $dbh->prepare(
- "SELECT host,port,tls,account,account_password,base_dn,attr_login FROM auth_sources WHERE id = ?;"
- );
- $sthldap->execute($auth_source_id);
- while (my @rowldap = $sthldap->fetchrow_array) {
- my $ldap = Authen::Simple::LDAP->new(
- host => ($rowldap[2] eq "1" || $rowldap[2] eq "t") ? "ldaps://$rowldap[0]" : $rowldap[0],
- port => $rowldap[1],
- basedn => $rowldap[5],
- binddn => $rowldap[3] ? $rowldap[3] : "",
- bindpw => $rowldap[4] ? $rowldap[4] : "",
- filter => "(".$rowldap[6]."=%s)"
- );
- if ($ldap->authenticate($redmine_user, $redmine_pass)) {
- print STDERR "SoundSoftware.pm: User $redmine_user authenticated via LDAP\n";
- $ret = 1;
- }
- }
- $sthldap->finish();
- undef $sthldap;
- }
- } else {
- print STDERR "SoundSoftware.pm: User $redmine_user lacks required role for this project\n";
- }
- }
-
- $sth->finish();
- undef $sth;
-
- $ret;
-}
-
-sub get_project_identifier {
- my $dbh = shift;
- my $r = shift;
-
- my $location = $r->location;
- my ($repo) = $r->uri =~ m{$location/*([^/]+)};
-
- return $repo if (!$repo);
-
- $repo =~ s/[^a-zA-Z0-9\._-]//g;
-
- # The original Redmine.pm returns the string just calculated as
- # the project identifier. That won't do for us -- we may have
- # (and in fact already do have, in our test instance) projects
- # whose repository names differ from the project identifiers.
-
- # This is a rather fundamental change because it means that almost
- # every request needs more than one database query -- which
- # prompts us to start passing around $dbh instead of connecting
- # locally within each function as is done in Redmine.pm.
-
- my $sth = $dbh->prepare(
- "SELECT projects.identifier FROM projects, repositories WHERE repositories.project_id = projects.id AND repositories.url LIKE ?;"
- );
-
- my $cfg = Apache2::Module::get_config
- (__PACKAGE__, $r->server, $r->per_dir_config);
-
- my $prefix = $cfg->{SoundSoftwareRepoPrefix};
- if (!defined $prefix) { $prefix = '%/'; }
-
- my $identifier = '';
-
- $sth->execute($prefix . $repo);
- my $ret = 0;
- if (my @row = $sth->fetchrow_array) {
- $identifier = $row[0];
- }
- $sth->finish();
- undef $sth;
-
- print STDERR "SoundSoftware.pm: Repository '$repo' belongs to project '$identifier'\n";
-
- $identifier;
-}
-
-sub get_realm {
- my $dbh = shift;
- my $project_id = shift;
- my $r = shift;
-
- my $sth = $dbh->prepare(
- "SELECT projects.name FROM projects WHERE projects.identifier = ?;"
- );
-
- my $name = $project_id;
-
- $sth->execute($project_id);
- my $ret = 0;
- if (my @row = $sth->fetchrow_array) {
- $name = $row[0];
- }
- $sth->finish();
- undef $sth;
-
- # be timid about characters not permitted in auth realm and revert
- # to project identifier if any are found
- if ($name =~ m/[^\w\d\s\._-]/) {
- $name = $project_id;
- }
-
- my $realm = '"Mercurial repository for ' . "'$name'" . '"';
-
- $realm;
-}
-
-sub connect_database {
- my $r = shift;
-
- my $cfg = Apache2::Module::get_config
- (__PACKAGE__, $r->server, $r->per_dir_config);
-
- return DBI->connect($cfg->{SoundSoftwareDSN},
- $cfg->{SoundSoftwareDbUser},
- $cfg->{SoundSoftwareDbPass});
-}
-
-1;
diff -r bb7cac2ac146 -r ff5112f823e5 extra/svn/reposman-soundsoftware.rb
--- a/extra/svn/reposman-soundsoftware.rb Tue Feb 08 13:15:47 2011 +0000
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,350 +0,0 @@
-#!/usr/bin/env ruby
-
-# == Synopsis
-#
-# reposman: manages your repositories with Redmine
-#
-# == Usage
-#
-# reposman [OPTIONS...] -s [DIR] -r [HOST]
-#
-# Examples:
-# reposman --svn-dir=/var/svn --redmine-host=redmine.example.net --scm subversion
-# reposman -s /var/git -r redmine.example.net -u http://svn.example.net --scm git
-#
-# == Arguments (mandatory)
-#
-# -s, --svn-dir=DIR use DIR as base directory for svn repositories
-# -r, --redmine-host=HOST assume Redmine is hosted on HOST. Examples:
-# -r redmine.example.net
-# -r http://redmine.example.net
-# -r https://example.net/redmine
-# -k, --key=KEY use KEY as the Redmine API key
-#
-# == Options
-#
-# -o, --owner=OWNER owner of the repository. using the rails login
-# allow user to browse the repository within
-# Redmine even for private project. If you want to
-# share repositories through Redmine.pm, you need
-# to use the apache owner.
-# -g, --group=GROUP group of the repository. (default: root)
-# --scm=SCM the kind of SCM repository you want to create (and
-# register) in Redmine (default: Subversion).
-# reposman is able to create Git and Subversion
-# repositories. For all other kind, you must specify
-# a --command option
-# -u, --url=URL the base url Redmine will use to access your
-# repositories. This option is used to automatically
-# register the repositories in Redmine. The project
-# identifier will be appended to this url. Examples:
-# -u https://example.net/svn
-# -u file:///var/svn/
-# if this option isn't set, reposman will register
-# the repositories with local file paths in Redmine
-# -c, --command=COMMAND use this command instead of "svnadmin create" to
-# create a repository. This option can be used to
-# create repositories other than subversion and git
-# kind.
-# This command override the default creation for git
-# and subversion.
-# --http-user=USER User for HTTP Basic authentication with Redmine WS
-# --http-pass=PASSWORD Password for Basic authentication with Redmine WS
-# -t, --test only show what should be done
-# -h, --help show help and exit
-# -v, --verbose verbose
-# -V, --version print version and exit
-# -q, --quiet no log
-#
-# == References
-#
-# You can find more information on the redmine's wiki : http://www.redmine.org/wiki/redmine/HowTos
-
-
-require 'getoptlong'
-require 'rdoc/usage'
-require 'find'
-require 'etc'
-
-Version = "1.3"
-SUPPORTED_SCM = %w( Subversion Darcs Mercurial Bazaar Git Filesystem )
-
-opts = GetoptLong.new(
- ['--svn-dir', '-s', GetoptLong::REQUIRED_ARGUMENT],
- ['--redmine-host', '-r', GetoptLong::REQUIRED_ARGUMENT],
- ['--key', '-k', GetoptLong::REQUIRED_ARGUMENT],
- ['--owner', '-o', GetoptLong::REQUIRED_ARGUMENT],
- ['--group', '-g', GetoptLong::REQUIRED_ARGUMENT],
- ['--url', '-u', GetoptLong::REQUIRED_ARGUMENT],
- ['--command' , '-c', GetoptLong::REQUIRED_ARGUMENT],
- ['--scm', GetoptLong::REQUIRED_ARGUMENT],
- ['--http-user', GetoptLong::REQUIRED_ARGUMENT],
- ['--http-pass', GetoptLong::REQUIRED_ARGUMENT],
- ['--test', '-t', GetoptLong::NO_ARGUMENT],
- ['--verbose', '-v', GetoptLong::NO_ARGUMENT],
- ['--version', '-V', GetoptLong::NO_ARGUMENT],
- ['--help' , '-h', GetoptLong::NO_ARGUMENT],
- ['--quiet' , '-q', GetoptLong::NO_ARGUMENT]
- )
-
-$verbose = 0
-$quiet = false
-$redmine_host = ''
-$repos_base = ''
-$http_user = ''
-$http_pass = ''
-$svn_owner = 'root'
-$svn_group = 'root'
-$use_groupid = true
-$svn_url = false
-$test = false
-$scm = 'Subversion'
-
-def log(text, options={})
- level = options[:level] || 0
- puts text unless $quiet or level > $verbose
- exit 1 if options[:exit]
-end
-
-def system_or_raise(command)
- raise "\"#{command}\" failed" unless system command
-end
-
-module SCM
-
- module Subversion
- def self.create(path)
- system_or_raise "svnadmin create #{path}"
- end
- end
-
- module Git
- def self.create(path)
- Dir.mkdir path
- Dir.chdir(path) do
- system_or_raise "git --bare init --shared"
- system_or_raise "git update-server-info"
- end
- end
- end
-
-end
-
-begin
- opts.each do |opt, arg|
- case opt
- when '--svn-dir'; $repos_base = arg.dup
- when '--redmine-host'; $redmine_host = arg.dup
- when '--key'; $api_key = arg.dup
- when '--owner'; $svn_owner = arg.dup; $use_groupid = false;
- when '--group'; $svn_group = arg.dup; $use_groupid = false;
- when '--url'; $svn_url = arg.dup
- when '--scm'; $scm = arg.dup.capitalize; log("Invalid SCM: #{$scm}", :exit => true) unless SUPPORTED_SCM.include?($scm)
- when '--http-user'; $http_user = arg.dup
- when '--http-pass'; $http_pass = arg.dup
- when '--command'; $command = arg.dup
- when '--verbose'; $verbose += 1
- when '--test'; $test = true
- when '--version'; puts Version; exit
- when '--help'; RDoc::usage
- when '--quiet'; $quiet = true
- end
- end
-rescue
- exit 1
-end
-
-if $test
- log("running in test mode")
-end
-
-# Make sure command is overridden if SCM vendor is not handled internally (for the moment Subversion and Git)
-if $command.nil?
- begin
- scm_module = SCM.const_get($scm)
- rescue
- log("Please use --command option to specify how to create a #{$scm} repository.", :exit => true)
- end
-end
-
-$svn_url += "/" if $svn_url and not $svn_url.match(/\/$/)
-
-if ($redmine_host.empty? or $repos_base.empty?)
- RDoc::usage
-end
-
-unless File.directory?($repos_base)
- log("directory '#{$repos_base}' doesn't exists", :exit => true)
-end
-
-begin
- require 'active_resource'
-rescue LoadError
- log("This script requires activeresource.\nRun 'gem install activeresource' to install it.", :exit => true)
-end
-
-class Project < ActiveResource::Base
- self.headers["User-agent"] = "Redmine repository manager/#{Version}"
-end
-
-log("querying Redmine for projects...", :level => 1);
-
-$redmine_host.gsub!(/^/, "http://") unless $redmine_host.match("^https?://")
-$redmine_host.gsub!(/\/$/, '')
-
-Project.site = "#{$redmine_host}/sys";
-Project.user = $http_user;
-Project.password = $http_pass;
-
-begin
- # Get all active projects that have the Repository module enabled
- projects = Project.find(:all, :params => {:key => $api_key})
-rescue => e
- log("Unable to connect to #{Project.site}: #{e}", :exit => true)
-end
-
-if projects.nil?
- log('no project found, perhaps you forgot to "Enable WS for repository management"', :exit => true)
-end
-
-log("retrieved #{projects.size} projects", :level => 1)
-
-def set_owner_and_rights(project, repos_path, &block)
- if RUBY_PLATFORM =~ /mswin/
- yield if block_given?
- else
- uid, gid = Etc.getpwnam($svn_owner).uid, ($use_groupid ? Etc.getgrnam(project.identifier).gid : Etc.getgrnam($svn_group).gid)
- right = project.is_public ? 02775 : 02770
- yield if block_given?
- Find.find(repos_path) do |f|
- File.chmod right, f
- File.chown uid, gid, f
- end
- end
-end
-
-def other_read_right?(file)
- (File.stat(file).mode & 0007).zero? ? false : true
-end
-
-def owner_name(file)
- mswin? ?
- $svn_owner :
- Etc.getpwuid( File.stat(file).uid ).name
-end
-
-def mswin?
- (RUBY_PLATFORM =~ /(:?mswin|mingw)/) || (RUBY_PLATFORM == 'java' && (ENV['OS'] || ENV['os']) =~ /windows/i)
-end
-
-projects.each do |project|
- log("treating project #{project.name}", :level => 1)
-
- if project.identifier.empty?
- log("\tno identifier for project #{project.name}")
- next
- elsif not project.identifier.match(/^[a-z0-9\-]+$/)
- log("\tinvalid identifier for project #{project.name} : #{project.identifier}");
- next;
- end
-
- repos_path = File.join($repos_base, project.identifier).gsub(File::SEPARATOR, File::ALT_SEPARATOR || File::SEPARATOR)
-
- create_repos = false
-
- # Logic required for SoundSoftware.ac.uk repositories:
- #
- # * If the project has a repository path declared already,
- # - if it's a local path,
- # - if it does not exist
- # - if it has the right root
- # - create it
- # - else
- # - leave alone (remote repository)
- # * else
- # - create repository with same name as project
- # - set to project
-
- if project.respond_to?(:repository)
-
- repos_url = project.repository.url;
- log("\texisting url for project #{project.identifier} is #{repos_url}");
-
- if repos_url.match(/^file:\//) || repos_url.match(/^\//)
-
- repos_url = repos_url.gsub(/^file:\/*/, "/");
- log("\tthis is a local file path, at #{repos_url}");
-
- if repos_url.slice(0, $repos_base.length) != $repos_base
- log("\tit is in the wrong place: replacing it");
- # leave repos_path set to our original suggestion
- create_repos = true
- else
- if !File.directory?(repos_url)
- log("\tit doesn't exist; we should create it");
- repos_path = repos_url
- create_repos = true
- else
- log("\tit exists and is in the right place");
- end
- end
- else
- log("\tthis is a remote path, leaving alone");
- end
- else
- log("\tproject #{project.identifier} has no repository registered")
-# if File.directory?(repos_path)
-# log("\trepository path #{repos_path} already exists, not creating")
-# else
- create_repos = true
-# end
- end
-
- if create_repos
-
- registration_url = repos_path
- if $svn_url
- registration_url = "#{$svn_url}#{project.identifier}"
- end
-
- if $test
- log("\tproposal: create repository #{repos_path}")
- log("\tproposal: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}")
- next
- end
-
-# No -- we need "other" users to be able to read it. Access control
-# is not handled through Unix user id anyway
-# project.is_public ? File.umask(0002) : File.umask(0007)
- File.umask(0002)
-
- log("\taction: create repository #{repos_path}")
-
- begin
- if !File.directory?(repos_path)
- set_owner_and_rights(project, repos_path) do
- if scm_module.nil?
- log("\trunning command: #{$command} #{repos_path}")
- system_or_raise "#{$command} #{repos_path}"
- else
- scm_module.create(repos_path)
- end
- end
- end
- rescue => e
- log("\tunable to create #{repos_path} : #{e}\n")
- next
- end
-
- begin
- log("\taction: register repository #{repos_path} in Redmine with vendor #{$scm}, url #{registration_url}");
- project.post(:repository, :vendor => $scm, :repository => {:url => "#{registration_url}"}, :key => $api_key)
- rescue => e
- log("\trepository #{repos_path} not registered in Redmine: #{e.message}");
- end
-
- log("\trepository #{repos_path} created");
- end
-
-end
-
diff -r bb7cac2ac146 -r ff5112f823e5 public/themes/soundsoftware/stylesheets/application.css
--- a/public/themes/soundsoftware/stylesheets/application.css Tue Feb 08 13:15:47 2011 +0000
+++ b/public/themes/soundsoftware/stylesheets/application.css Tue Feb 08 13:24:51 2011 +0000
@@ -26,7 +26,7 @@
color: #000;
margin: 0;
margin-bottom: 40px;
-/* font-size: 95%; */
+ min-width: 620px;
}
h1 {
@@ -69,8 +69,9 @@
table.projects th { text-align: left; }
table.projects th.managers { color: #3e442c; }
-table.projects .root .name { font-size: 1.2em; }
-table.projects .root .description { padding-bottom: 0.5em; }
+table.projects .root .name { font-size: 1.2em; padding-top: 0.3em; }
+table.projects .description { padding-bottom: 0.5em; }
+table.projects .no_description { padding-bottom: 0.5em; }
table.projects .hosted_here { font-weight: bold; }
table.projects .child .name { font-weight: normal; }
table.projects .child .description { font-size: 0.95em; }