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;