use strict; use warnings; use Carp; package CommitBit::Model::Repository; use Jifty::DBI::Schema; use File::Spec; use File::Path; use Cwd 'abs_path'; use CommitBit::Record schema { column name => type is 'text', is distinct; is mandatory; column local_path => type is 'text'; column description => type is 'text', render_as 'textarea'; column member_url => type is 'text'; column anon_url => type is 'text'; }; # Your model-specific methods go here. sub projects { my $self = shift; my $projects = CommitBit::Model::ProjectCollection->new(); $projects->limit(column => 'repository', value => $self->id); return ($projects); } sub associated_users { my $self = shift; my $users = CommitBit::Model::UserCollection->new(); my $members = $users->join( alias1 => 'main', column1 => 'id', table2 => 'project_members', column2 => 'person' ); my $projects = $users->join( alias1 => $members, column1 => 'project', table2 => 'projects', column2 => 'id' ); my $repositories = $users->join( alias1 => $projects, column1 => 'repository', table2 => 'repositories', column2 => 'id' ); $users->limit( alias => $repositories, column => 'id', value => $self->id ); return $users; } # XXX: make sure svn/svnadmin is in path during compile time. sub _svnadmin { my $self = shift; $self->run_cmd("svnadmin", @_); } sub _svn { my $self = shift; $self->run_cmd("svn", @_); } sub with_write_lock (&) { my $sub = shift; eval { obtain_write_lock() || Carp::croak "Couldn't get write lock :("; &$sub(); release_write_lock() || Carp::croak "Couldn't release write lock :("; return 1; }; warn $@ if $@; return 0; } sub create { my $self = shift; my $args = { @_ }; my $prefix = abs_path( Jifty->config->app("repository_prefix") ); if (exists $args->{local_path} and length $args->{local_path}) { if ( ! File::Spec->file_name_is_absolute( $args->{'local_path'} )) { $args->{local_path} = File::Spec->catdir($prefix, $args->{local_path}); } } else { $args->{local_path} = File::Spec->catdir($prefix, $args->{name}); } my ($val) = $self->SUPER::create(%$args); if ($self->id) { # XXX: when failed rollback the create? warn "my id is ".$self->id; $self->init_repository; } return ($val); } sub init_repository { my $self = shift; if ( -d $self->local_path ) { } else { $self->mkdir_parent( $self->local_path ); $self->_svnadmin( 'create', $self->local_path ); $self->write_svnserv_config; $self->write_httpd_config; } } sub mkdir_parent { my $self = shift; my $path = shift; my @parts = File::Spec->splitdir($path); pop @parts; File::Path::mkpath([File::Spec->catdir(@parts)]); } sub add_project { my $self = shift; my $project = shift; my @project_structure = ( '', 'trunk', 'branches', 'tags' ); my @paths = map { "file://" . File::Spec::Unix->catdir( $self->local_path, $project->root_path, $_ ) } @project_structure; foreach my $path (@paths) { $self->_svn( 'ls', $path ); if ($?) { # If the path isn't there $self->_svn( 'mkdir', -m => 'Project ' . $project->name . ' init by CommitBit ' . $CommitBit::VERSION, $path ); } } $self->write_authz_file(); } sub remove_project {} sub write_password_files { my $self = shift; $self->write_htpasswd_file(); $self->write_svnserve_passwd_file(); } sub write_htpasswd_file { my $self = shift; with_write_lock { open my $fh, '>', $self->svnconf_file_path('htpasswd') or die $@; print $fh $self->autogenerated_file_warning; my $users = $self->associated_users; while (my $user = $users->next) { my $line = `htpasswd -nmb @{[$user->email]} @{[$user->__value('password')]}`; chomp $line; print $fh $line; } }; } sub write_svnserve_passwd_file { my $self = shift; with_write_lock { open my $fh, '>', $self->svnconf_file_path('passwd') or die $@; print $fh $self->autogenerated_file_warning; print $fh "[users]\n"; my $users = $self->associated_users; while (my $user = $users->next) { print $fh $user->email. ' = '. $user->__value('password'). "\n"; } # all users that involves in any project in the repository }; } sub write_authz_file { my $self = shift; my $authz = {}; my $projects = CommitBit::Model::ProjectCollection->new(); $projects->limit(column => 'repository', value => $self->id); with_write_lock { open( my $file, ">", $self->svnconf_file_path('authz') ) || die $@; print $file $self->autogenerated_file_warning; while ( my $project = $projects->next ) { print $file "[/" . $project->root_path . "]\n" || die $@; foreach my $user ( @{ $project->members->items_array_ref, $project->administrators->items_array_ref } ) { print $file $user->email . " = rw\n" || die $@; } if ($project->publicly_visible) { print $file "* = r\n"; } } close($file) || die $@; }; } sub svnconf_file_path { my $self = shift; return File::Spec->catdir($self->local_path, 'conf', @_); } sub write_svnserv_config { my $self = shift; with_write_lock { open my $file, ">", $self->svnconf_file_path('svnserve.conf') or die $@; print $file $self->autogenerated_file_warning; print $file <<"EOF"; [general] anon-access = read auth-access = write password-db = passwd authz-db = authz realm = @{[$self->name]} EOF close $file or die $@; }; } sub write_httpd_config { my $self = shift; my $msg = $self->autogenerated_file_warning; $msg .= <<"EOF"; name]}> # Uncomment this to enable the repository, DAV svn # Set this to the path to your repository SVNPath @{[$self->local_path]} # The following allows for basic http authentication. Basic authentication # should not be considered secure for any particularly rigorous definition of # secure. # Uncomment the following 3 lines to enable Basic Authentication AuthType Basic AuthName "@{[$self->name]} Subversion Repository" AuthUserFile @{[$self->svnconf_file_path('htpasswd')]} # Uncomment the following line to enable Authz Authentication AuthzSVNAccessFile @{[$self->svnconf_file_path('authz')]} # The following three lines allow anonymous read, but make # committers authenticate themselves. Require valid-user EOF with_write_lock { open my $fh, '>', $self->svnconf_file_path('httpd.conf') or die $@; print $fh $msg or die $@; close $fh or die $@; }; } sub obtain_write_lock { return 1; } sub release_write_lock { return 1; } sub autogenerated_file_warning { my $self = shift; my $msg = <log->debug("Running: ".join(' ', @_)); system(@_); } sub current_user_can { my $self = shift; my $right = shift; if ($right eq 'read') { return 1 } elsif (($right eq 'create' or $right eq 'update' or $right eq 'delete') and ($self->current_user->user_object && $self->current_user->user_object->admin)) { return 1; } return 0 } 1;