From b3561717af8ae3f294879e9c139f4cf6288ddd38 Mon Sep 17 00:00:00 2001
From: Petr Baudis
Date: Tue, 24 Oct 2006 03:37:25 +0200
Subject: [PATCH] Support for creating forks
You can get a fork of an existing project using project name
forkee/fork.git, and alternates will be set up properly.
---
cgi/Git/RepoCGI.pm | 62 +++++++++++++++++++++++++++++++++++++++++++++---------
cgi/p/delproj.cgi | 9 ++++++--
cgi/p/editproj.cgi | 2 +-
cgi/regproj.cgi | 29 ++++++++++++++++++++++---
cgi/reguser.cgi | 2 +-
5 files changed, 87 insertions(+), 17 deletions(-)
diff --git a/cgi/Git/RepoCGI.pm b/cgi/Git/RepoCGI.pm
index a827afb..b300f4f 100644
--- a/cgi/Git/RepoCGI.pm
+++ b/cgi/Git/RepoCGI.pm
@@ -12,7 +12,8 @@ BEGIN {
our @EXPORT = qw(scrypt html_esc jailed_file
lock_file unlock_file
filedb_atomic_append filedb_atomic_edit
- valid_name valid_email valid_repo_url valid_web_url);
+ proj_get_forkee_name proj_get_forkee_path
+ valid_proj_name valid_user_name valid_email valid_repo_url valid_web_url);
use CGI qw(:standard :escapeHTML -nosticky);
use CGI::Util qw(unescape);
@@ -190,8 +191,21 @@ sub filedb_atomic_edit {
unlock_file($file);
}
-# BOTH user AND project name!
-sub valid_name {
+sub proj_get_forkee_name {
+ $_ = $_[0];
+ (m#^(.*)/.*?$#)[0];
+}
+sub proj_get_forkee_path {
+ my $forkee = '/srv/git/'.proj_get_forkee_name($_[0]).'.git';
+ -d $forkee ? $forkee : '';
+}
+sub valid_proj_name {
+ $_ = $_[0];
+ (not m#/# or -d proj_get_forkee_path($_)) # will also catch ^/
+ and (not m#\./#)
+ and m#^[a-zA-Z0-9+./_-]+$#;
+}
+sub valid_user_name {
$_ = $_[0];
/^[a-zA-Z0-9+._-]+$/;
}
@@ -216,6 +230,17 @@ package Git::RepoCGI::Project;
BEGIN { use Git::RepoCGI; }
+sub _mkdir_forkees {
+ my $self = shift;
+ my @pelems = split('/', $self->{name});
+ pop @pelems; # do not create dir for the project itself
+ my $path = "/srv/git";
+ foreach my $pelem (@pelems) {
+ (-d "$path/$pelem" ) or mkdir "$path/$pelem" or die "mkdir $path/$pelem: $!";
+ $path .= "/$pelem";
+ }
+}
+
our %propmap = (
url => 'base_url',
email => 'owner',
@@ -283,6 +308,19 @@ sub _nofetch {
}
}
+sub _alternates_setup {
+ my $self = shift;
+ return unless $self->{name} =~ m#/#;
+ my $forkee_name = proj_get_forkee_name($self->{name});
+ my $forkee_path = proj_get_forkee_path($self->{name});
+ return unless -d $forkee_path;
+ open X, '>'.$self->path.'/objects/info/alternates' or die "alternates failed: $!";
+ # We use relative URL here so that http-fetch groks it automagically.
+ $forkee_name =~ s#^.*/##; # Only the last element of the path matters, again.
+ print X "../../$forkee_name.git/objects\n";
+ close X;
+}
+
sub _group_add {
my $self = shift;
my ($xtra) = @_;
@@ -346,7 +384,7 @@ sub _hooks_install {
sub _new {
my $class = shift;
my ($name, $path) = @_;
- valid_name($name) or die "refusing to create project with invalid name ($name)!";
+ valid_proj_name($name) or die "refusing to create project with invalid name ($name)!";
my $proj = { name => $name, path => $path };
bless $proj, $class;
@@ -427,7 +465,7 @@ sub cgi_fill {
}
# FIXME: Permit only existing users
- $self->{users} = [grep { valid_name($_) } $cgi->param('user')];
+ $self->{users} = [grep { valid_user_name($_) } $cgi->param('user')];
not $repo->err_check;
}
@@ -448,19 +486,23 @@ sub form_defaults {
sub premirror {
my $self = shift;
+ $self->_mkdir_forkees;
mkdir $self->{path} or die "mkdir failed: $!";
chmod 0775, $self->{path} or die "chmod failed: $!";
$self->_properties_save;
+ $self->_alternates_setup;
$self->_group_add(':');
}
sub conjure {
my $self = shift;
+ $self->_mkdir_forkees;
system('cg-admin-setuprepo', '-g', 'repo', $self->{path}) == 0
or die "cg-admin-setuprepo failed: $?";
$self->_nofetch(1);
$self->_properties_save;
+ $self->_alternates_setup;
$self->_group_add;
$self->_hooks_install;
}
@@ -486,14 +528,14 @@ sub delete {
# static method
sub does_exist {
my ($name) = @_;
- valid_name($name) or die "tried to query for project with invalid name $name!";
+ valid_proj_name($name) or die "tried to query for project with invalid name $name!";
(available($name)
or -d "/home/repo/repodata/cloning/$name"
or -d "/home/repo/repodata/to-clone/$name");
}
sub available {
my ($name) = @_;
- valid_name($name) or die "tried to query for project with invalid name $name!";
+ valid_proj_name($name) or die "tried to query for project with invalid name $name!";
(-d "/srv/git/$name.git");
}
@@ -527,7 +569,7 @@ sub _sshkey_save {
sub _new {
my $class = shift;
my ($name) = @_;
- valid_name($name) or die "refusing to create user with invalid name ($name)!";
+ valid_user_name($name) or die "refusing to create user with invalid name ($name)!";
my $proj = { name => $name };
bless $proj, $class;
@@ -550,7 +592,7 @@ sub cgi_fill {
my $cgi = $repo->cgi;
$self->{name} = $repo->wparam('name');
- valid_name($self->{name})
+ valid_user_name($self->{name})
or $repo->err("Name contains invalid characters.");
$self->{email} = $repo->wparam('email');
@@ -574,7 +616,7 @@ sub conjure {
# static method
sub does_exist {
my ($name) = @_;
- valid_name($name) or die "tried to query for user with invalid name $name!";
+ valid_user_name($name) or die "tried to query for user with invalid name $name!";
(-e jailed_file("/etc/sshkeys/$name"));
}
sub available {
diff --git a/cgi/p/delproj.cgi b/cgi/p/delproj.cgi
index 753e773..4d1ca8b 100755
--- a/cgi/p/delproj.cgi
+++ b/cgi/p/delproj.cgi
@@ -13,7 +13,7 @@ my $cgi = $repo->cgi;
my $name = $cgi->remote_user();
-if (!valid_name($name)) {
+if (!valid_proj_name($name)) {
print "Invalid project name. Go away, sorcerer.
\n";
exit;
}
@@ -32,7 +32,12 @@ my $proj = Git::RepoCGI::Project->load($name);
$proj or die "not found project $name, that's really weird!";
if (!$proj->{mirror}) {
- print "Sorry but you can remove only mirrored projects. Pushed projects cannot be removed.
\n";
+ print "Sorry but you can remove only mirrored projects. Pushed projects cannot be removed. Please tell the administrator if you really want to.
\n";
+ exit;
+}
+
+if (glob('/srv/git/'.($name =~ /^(.*)\.git$/)[0].'/*')) {
+ print "Sorry but this project has forks associated. Such projects cannot be removed. Please tell the administrator if you really want to.
\n";
exit;
}
diff --git a/cgi/p/editproj.cgi b/cgi/p/editproj.cgi
index 2a93fc6..81532be 100755
--- a/cgi/p/editproj.cgi
+++ b/cgi/p/editproj.cgi
@@ -13,7 +13,7 @@ my $cgi = $repo->cgi;
my $name = $cgi->remote_user();
-if (!valid_name($name)) {
+if (!valid_proj_name($name)) {
print "Invalid project name. Go away, sorcerer.
\n";
exit;
}
diff --git a/cgi/regproj.cgi b/cgi/regproj.cgi
index 631b966..6836877 100755
--- a/cgi/regproj.cgi
+++ b/cgi/regproj.cgi
@@ -11,11 +11,14 @@ use Git::RepoCGI;
my $repo = Git::RepoCGI->new('Project Registration');
my $cgi = $repo->cgi;
-if ($cgi->param('name')) {
+my $name = $cgi->param('name');
+$name ||= '';
+
+if ($cgi->param('mode')) {
# submitted, let's see
# FIXME: racy, do a lock
my $name = $repo->wparam('name');
- valid_name($name)
+ valid_proj_name($name)
and Git::RepoCGI::Project::does_exist($name)
and $repo->err("Project with that name already exists.");
@@ -55,12 +58,32 @@ the project. If you want to switch the other way, please contact the administrat
You will need the admin password to adjust project settings later
(mirroring URL, list of users allowed to push, project description, ...).
Use the project name as the username when asked by the browser.
+EOT
+unless ($name =~ m#/#) {
+ print <Note that if your project is a fork of an existing project
+(this does not mean anything socially bad), please instead go to the project's
+summary view and click the 'fork' link in the top bar. This way, both you and
+we will save bandwidth and your project will be properly categorized. If your
+project is a fork but the existing project is not registered here yet, please
+consider registering it first; you do not have to be involved in the project
+in order to register it here as a mirror.
+EOT
+} else {
+ print <Great, your project will be created as a subproject of the '$name' project.
+This means that it will be properly categorized and you will need to push only
+the data you created, not the whole project. (That will be done automagically,
+you do not need to specify any extra arguments during the push.)
+EOT
+}
+print <By submitting this form, you are confirming that the repository contains only free software
and redistributing it does not violate any law of Czech Republic. Read more details
about the hosting and terms and conditions.
Have fun!