From e36902cb8af68b7a2026dbc527ba52d10f326b5f Mon Sep 17 00:00:00 2001 From: Roland Mas Date: Fri, 9 Nov 2012 16:45:45 +0100 Subject: [PATCH] Started rebasing multi-git-repository branch --- src/common/include/SCMPlugin.class.php | 14 +- src/common/include/utils.php | 19 ++ src/plugins/scmgit/bin/db-upgrade.pl | 26 ++ src/plugins/scmgit/common/GitPlugin.class.php | 370 ++++++++++++++++++++-- src/plugins/scmgit/db/20121019-multiple-repos.sql | 11 + src/www/scm/admin/index.php | 52 +-- tests/func/PluginsScmGit/gitTest.php | 26 +- 7 files changed, 467 insertions(+), 51 deletions(-) create mode 100644 src/plugins/scmgit/db/20121019-multiple-repos.sql diff --git a/src/common/include/SCMPlugin.class.php b/src/common/include/SCMPlugin.class.php index a24e464..7c26551 100644 --- a/src/common/include/SCMPlugin.class.php +++ b/src/common/include/SCMPlugin.class.php @@ -108,7 +108,9 @@ abstract class SCMPlugin extends Plugin { $this->activity($params); break; } - default: { // Forgot something + default: { + // Default is to use method named as the hook + $this->$hookname($params); } } } @@ -265,10 +267,12 @@ abstract class SCMPlugin extends Plugin { } if ($project->usesPlugin($this->name) ) { - if (isset($params['scm_enable_anonymous']) && $params['scm_enable_anonymous']) { - $project->SetUsesAnonSCM(true); - } else { - $project->SetUsesAnonSCM(false); + if (isset($params['scm_enable_anonymous'])) { + if ($params['scm_enable_anonymous']) { + $project->SetUsesAnonSCM(true); + } else { + $project->SetUsesAnonSCM(false); + } } } } diff --git a/src/common/include/utils.php b/src/common/include/utils.php index d3688ac..704d5d3 100644 --- a/src/common/include/utils.php +++ b/src/common/include/utils.php @@ -865,6 +865,25 @@ function util_is_valid_filename($file) { } } +/* util_is_valid_repository_name() - Verifies whether a repository name is valid + * + * @param string The name to verify + * @returns true on success/false on error + * + */ +function util_is_valid_repository_name ($file) { + //bad char test + $invalidchars = preg_replace("/[-A-Z0-9+_\.]/i","",$file); + + if (!empty($invalidchars)) { + return false; + } + if (strstr($file,'..')) { + return false; + } + return true; +} + /** * valid_hostname() - Validates a hostname string to make sure it doesn't contain invalid characters * diff --git a/src/plugins/scmgit/bin/db-upgrade.pl b/src/plugins/scmgit/bin/db-upgrade.pl index e460021..3cc18e8 100755 --- a/src/plugins/scmgit/bin/db-upgrade.pl +++ b/src/plugins/scmgit/bin/db-upgrade.pl @@ -71,6 +71,8 @@ eval { $dbh->commit () ; } + &update_with_sql("/usr/share/gforge/plugins/$pluginname/lib/20121019-multiple-repos.sql","0.2"); + debug "It seems your database install/upgrade went well and smoothly. That's cool." ; debug "Please enjoy using Debian GForge." ; @@ -274,3 +276,27 @@ sub bump_sequence_to ( $$ ) { $sth->finish () ; } until $array[0] >= $targetvalue ; } + +sub update_with_sql ( $$ ) { + my $sqlfile = shift or die "Not enough arguments" ; + my $target = shift or die "Not enough arguments" ; + $sqlfile =~ s/\.sql$//; + my $version = &get_db_version ; + if (&is_lesser ($version, $target)) { + &debug ("Upgrading database with $sqlfile.sql") ; + + @reqlist = @{ &parse_sql_file ("$sqlfile.sql") } ; + foreach my $s (@reqlist) { + my $query = $s ; + # debug $query ; + my $sth = $dbh->prepare ($query) ; + $sth->execute () ; + $sth->finish () ; + } + @reqlist = () ; + + &update_db_version ($target) ; + &debug ("...OK.") ; + $dbh->commit () ; + } +} diff --git a/src/plugins/scmgit/common/GitPlugin.class.php b/src/plugins/scmgit/common/GitPlugin.class.php index d80ad5d..0360c1c 100644 --- a/src/plugins/scmgit/common/GitPlugin.class.php +++ b/src/plugins/scmgit/common/GitPlugin.class.php @@ -36,6 +36,9 @@ class GitPlugin extends SCMPlugin { $this->_addHook('scm_update_repolist'); $this->_addHook('scm_generate_snapshots'); $this->_addHook('scm_gather_stats'); + $this->_addHook('scm_admin_form'); + $this->_addHook('scm_add_repo'); + $this->_addHook('scm_delete_repo'); $this->_addHook('widget_instance', 'myPageBox', false); $this->_addHook('widgets', 'widgets', false); $this->_addHook('activity'); @@ -73,15 +76,31 @@ class GitPlugin extends SCMPlugin { } function getInstructionsForAnon($project) { - $b = '

' . _('Anonymous Git Access') . '

'; + $repo_list = array($project->getUnixName()); + $result = db_query_params ('SELECT repo_name FROM plugin_scmgit_secondary_repos WHERE group_id=$1 AND next_action = 0 ORDER BY repo_name', + array ($project->getID())) ; + $rows = db_numrows ($result) ; + for ($i=0; $i<$rows; $i++) { + $repo_list[] = db_result($result,$i,'repo_name'); + } + + $b = '

' . ngettext('Anonymous Access to the Git repository', + 'Anonymous Access to the Git repositories', + count($repo_list)) . '

'; + $b .= '

'; - $b .= _('This project\'s Git repository can be checked out through anonymous access with the following command.'); - $b .= '

'; - - $b .= '

' ; - $b .= 'git clone '.util_make_url ('/anonscm/git/'.$project->getUnixName().'/'.$project->getUnixName().'.git').'
'; + $b .= ngettext('This project\'s Git repository can be checked out through anonymous access with the following command.', + 'This project\'s Git repositories can be checked out through anonymous access with the following commands.', + count($repo_list)); + $b .= '

'; - + + foreach ($repo_list as $repo_name) { + $b .= '

' ; + $b .= 'git clone '.util_make_url ('/anonscm/git/'.$project->getUnixName().'/'.$repo_name.'.git').'
'; + $b .= '

'; + } + $result = db_query_params('SELECT u.user_id, u.user_name, u.realname FROM plugin_scmgit_personal_repos p, users u WHERE p.group_id=$1 AND u.user_id=p.user_id AND u.unix_status=$2', array ($project->getID(), 'A')); @@ -89,7 +108,9 @@ class GitPlugin extends SCMPlugin { if ($rows > 0) { $b .= '

'; - $b .= _('Developer\'s repository'); + $b .= ngettext('Developer\'s repository', + 'Developer\'s repositories', + $rows); $b .= '

'."\n"; $b .= '

'; $b .= ngettext('One of this project\'s members also has a personal Git repository that can be checked out anonymously.', @@ -110,7 +131,15 @@ class GitPlugin extends SCMPlugin { } function getInstructionsForRW($project) { - + $repo_list = array($project->getUnixName()); + + $result = db_query_params ('SELECT repo_name FROM plugin_scmgit_secondary_repos WHERE group_id=$1 AND next_action = 0 ORDER BY repo_name', + array ($project->getID())) ; + $rows = db_numrows ($result) ; + for ($i=0; $i<$rows; $i++) { + $repo_list[] = db_result($result,$i,'repo_name'); + } + if (session_loggedin()) { $u = user_get_object(user_getid()); $d = $u->getUnixName(); @@ -118,23 +147,41 @@ class GitPlugin extends SCMPlugin { $b = ''; if (forge_get_config('use_ssh', 'scmgit')) { $b .= '

'; - $b .= _('Developer Git Access via SSH'); + $b = '

' . ngettext('Developer Access to the Git repository via SSH', + 'Developer Access to the Git repositories via SSH', + count($repo_list)) . '

'; $b .= ''; $b .= '

'; - $b .= _('Only project developers can access the Git tree via this method. SSH must be installed on your client machine. Enter your site password when prompted.'); + $b .= ngettext('Only project developers can access the GIT repository via this method. SSH must be installed on your client machine. Enter your site password when prompted.', + 'Only project developers can access the GIT repositories via this method. SSH must be installed on your client machine. Enter your site password when prompted.', + count($repo_list)); + $b .= '

'; - $b .= '

git clone git+ssh://'.$d.'@' . $this->getBoxForProject($project) . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git

' ; + foreach ($repo_list as $repo_name) { + $b .= '

git clone git+ssh://'.$d.'@' . $project->getSCMBox() . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git

' ; + } + $validSetup = 1; } if (forge_get_config('use_dav', 'scmgit')) { $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http'; $b .= '

'; - $b .= _('Developer Git Access via HTTP'); + $b = '

' . ngettext('Developer Access to the Git repository via HTTP', + 'Developer Access to the Git repositories via HTTP', + count($repo_list)) . '

'; + $b .= ''; $b .= '

'; - $b .= _('Only project developers can access the Git tree via this method. Enter your site password when prompted.'); + $b .= ngettext('Only project developers can access the GIT repository via this method. Enter your site password when prompted.', + 'Only project developers can access the GIT repositories via this method. Enter your site password when prompted.', + count($repo_list)); + $b .= '

'; - $b .= '

git clone '.$protocol.'://'.$d.'@' . $this->getBoxForProject($project) . '/'. forge_get_config('scm_root', 'scmgit') .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git

' ; + foreach ($repo_list as $repo_name) { + $b .= '

git clone '.$protocol.'://'.$d.'@' . $project->getSCMBox() . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git

' ; + } + + $validSetup = 1; } if ($validSetup == 0) { @@ -143,22 +190,39 @@ class GitPlugin extends SCMPlugin { } else { if (forge_get_config('use_ssh', 'scmgit')) { $b = '

'; - $b .= _('Developer Git Access via SSH'); + $b = '

' . ngettext('Developer Access to the Git repository via SSH', + 'Developer Access to the Git repositories via SSH', + count($repo_list)) . '

'; + $b .= ''; $b .= '

'; - $b .= _('Only project developers can access the Git tree via this method. SSH must be installed on your client machine. Substitute developername with the proper value. Enter your site password when prompted.'); + $b .= ngettext('Only project developers can access the GIT repository via this method. SSH must be installed on your client machine. Substitute developername with the proper value. Enter your site password when prompted.', + 'Only project developers can access the GIT repositories via this method. SSH must be installed on your client machine. Substitute developername with the proper value. Enter your site password when prompted.', + count($repo_list)); + $b .= '

'; - $b .= '

git clone git+ssh://'._('developername').'@' . $this->getBoxForProject($project) . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git

' ; + foreach ($repo_list as $repo_name) { + $b .= '

git clone git+ssh://'._('developername').'@' . $project->getSCMBox() . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git

' ; + } + } if (forge_get_config('use_dav', 'scmgit')) { $protocol = forge_get_config('use_ssl', 'scmgit')? 'https' : 'http'; $b = '

'; - $b .= _('Developer Git Access via HTTP'); + $b = '

' . ngettext('Developer Access to the Git repository via HTTP', + 'Developer Access to the Git repositories via HTTP', + count($repo_list)) . '

'; $b .= ''; $b .= '

'; - $b .= _('Only project developers can access the Git tree via this method. Enter your site password when prompted.'); + $b .= ngettext('Only project developers can access the GIT repository via this method. Enter your site password when prompted.', + 'Only project developers can access the GIT repositories via this method. Enter your site password when prompted.', + count($repo_list)); + $b .= '

'; - $b .= '

git clone '.$protocol.'://'._('developername').'@' . $this->getBoxForProject($project) . '/'. forge_get_config('scm_root', 'scmgit') .'/'. $project->getUnixName() .'/'. $project->getUnixName() .'.git

' ; + foreach ($repo_list as $repo_name) { + $b .= '

git clone '.$protocol.'://'._('developername').'@' . $project->getSCMBox() . '/'. forge_get_config('repos_path', 'scmgit') .'/'. $project->getUnixName() .'/'. $repo_name .'.git

' ; + } + } } @@ -166,8 +230,8 @@ class GitPlugin extends SCMPlugin { $u =& user_get_object(user_getid()) ; if ($u->getUnixStatus() == 'A') { $result = db_query_params('SELECT * FROM plugin_scmgit_personal_repos p WHERE p.group_id=$1 AND p.user_id=$2', - array ($project->getID(), - $u->getID())); + array ($project->getID(), + $u->getID())); if ($result && db_numrows ($result) > 0) { $b .= '

'; $b .= _('Access to your personal repository'); @@ -301,6 +365,7 @@ class GitPlugin extends SCMPlugin { } $output = ''; + // Create main repository $main_repo = $root . '/' . $project_name . '.git' ; if (!is_file("$main_repo/HEAD") && !is_dir("$main_repo/objects") && !is_dir("$main_repo/refs")) { exec("GIT_DIR=\"$main_repo\" git init --bare --shared=group", $result) ; @@ -341,6 +406,48 @@ class GitPlugin extends SCMPlugin { system ("chmod -R g-rwx,o-rwx $main_repo") ; } + // Create project-wide secondary repositories + $result = db_query_params ('SELECT repo_name, description, clone_url FROM plugin_scmgit_secondary_repos WHERE group_id=$1 AND next_action = 0', + array ($project->getID())) ; + $rows = db_numrows ($result) ; + for ($i=0; $i<$rows; $i++) { + $repo_name = db_result($result,$i,'repo_name'); + $description = db_result($result,$i,'description'); + $clone_url = db_result($result,$i,'clone_url'); + $repodir = $root . '/' . $repo_name . '.git' ; + if (!is_file ("$repodir/HEAD") && !is_dir("$repodir/objects") && !is_dir("$repodir/refs")) { + if ($clone_url != '') { + system ("cd $root;git clone --bare $clone_url $repodir") ; + } else { + system ("GIT_DIR=\"$repodir\" git init --bare --shared=group") ; + } + system ("GIT_DIR=\"$repodir\" git update-server-info") ; + if (is_file ("$repodir/hooks/post-update.sample")) { + rename ("$repodir/hooks/post-update.sample", + "$repodir/hooks/post-update") ; + } + if (!is_file ("$repodir/hooks/post-update")) { + $f = fopen ("$repodir/hooks/post-update") ; + fwrite ($f, "exec git-update-server-info\n") ; + fclose ($f) ; + } + if (is_file ("$repodir/hooks/post-update")) { + system ("chmod +x $repodir/hooks/post-update") ; + } + $f = fopen("$repodir/description", "w"); + fwrite($f, $description."\n"); + fclose($f); + system ("chgrp -R $unix_group $repodir") ; + system ("chmod g+s $root") ; + if ($project->enableAnonSCM()) { + system ("chmod -R g+wX,o+rX-w $main_repo") ; + } else { + system ("chmod -R g+wX,o-rwx $main_repo") ; + } + } + } + + // Create users' personal repositories $result = db_query_params ('SELECT u.user_name FROM plugin_scmgit_personal_repos p, users u WHERE p.group_id=$1 AND u.user_id=p.user_id AND u.unix_status=$2', array ($project->getID(), 'A')) ; @@ -694,6 +801,21 @@ class GitPlugin extends SCMPlugin { $params['output'] .= $project_name.': '.`git gc --quiet 2>&1`; } } + + // Delete project-wide secondary repositories + $result = db_query_params ('SELECT repo_name FROM plugin_scmgit_secondary_repos WHERE group_id=$1 AND next_action = 1', + array ($project->getID())) ; + $rows = db_numrows ($result) ; + for ($i=0; $i<$rows; $i++) { + $repo_name = db_result($result,$i,'repo_name'); + $repodir = $root . '/' . $repo_name . '.git' ; + if (util_is_valid_repository_name($repo_name)) { + system ("rm -rf $repodir"); + } + db_query_params ('DELETE FROM plugin_scmgit_secondary_repos WHERE group_id=$1 AND repo_name=$2 AND next_action = 1', + array ($project->getID(), + $repo_name)) ; + } } function activity($params) { @@ -728,6 +850,208 @@ class GitPlugin extends SCMPlugin { $params['texts'][] = _('Git Commits'); return true; } + + function scm_add_repo(&$params) { + $project = $this->checkParams($params); + if (!$project) { + return false ; + } + if (! $project->usesPlugin ($this->name)) { + return false; + } + + if (!isset($params['repo_name'])) { + return false; + } + + if ($params['repo_name'] == $project->getUnixName()) { + $params['error_msg'] = _('Cannot create a secondary repository with the same name as the primary'); + return false; + } + + if (! util_is_valid_repository_name($params['repo_name'])) { + $params['error_msg'] = _('This repository name is not valid'); + return false; + } + + $result = db_query_params('SELECT count(*) AS count FROM plugin_scmgit_secondary_repos WHERE group_id=$1 AND repo_name = $2', + array ($params['group_id'], $params['repo_name'])); + if (! $result) { + $params['error_msg'] = db_error(); + return false; + } + if (db_result($result, 0, 'count')) { + $params['error_msg'] = sprintf(_('A repository %s already exists'), $params['repo_name']); + return false; + } + + $description = ''; + $clone = ''; + if (isset($params['clone'])) { + $url = $params['clone']; + if ($url == '') { + // Start from empty + $clone = $url; + } elseif (preg_match('|^git://|', $url) || preg_match('|^https?://|', $url)) { + // External URLs: OK + $clone = $url; + } elseif ($url == $project->getUnixName()) { + $clone = $url; + } elseif (($result = db_query_params('SELECT count(*) AS count FROM plugin_scmgit_secondary_repos WHERE group_id=$1 AND repo_name = $2', array ($project->getID(), $url))) + && db_result($result, 0, 'count')) { + // Local repo: try to clone from an existing repo in same project + // Repository found + $clone = $url; + } else { + $params['error_msg'] = _('Invalid URL from which to clone'); + $clone = ''; + return false; + } + } + if (isset($params['description'])) { + $description = $params['description']; + } + if ($clone && !$description) { + $description = sprintf(_('Clone of %s'), $params['clone']); + } + if (!$description) { + $description = "Git repository $params[repo_name] for project ".$project->getUnixName(); + } + + $result = db_query_params ('INSERT INTO plugin_scmgit_secondary_repos (group_id, repo_name, description, clone_url) VALUES ($1, $2, $3, $4)', + array ($params['group_id'], $params['repo_name'], $description, $clone)); + if (! $result) { + $params['error_msg'] = db_error(); + return false; + } + + plugin_hook ("scm_admin_update", $params); + return true; + } + + function scm_delete_repo(&$params) { + $project = $this->checkParams($params); + if (!$project) { + return false ; + } + if (! $project->usesPlugin ($this->name)) { + return false; + } + + if (!isset($params['repo_name'])) { + return false; + } + + $result = db_query_params('SELECT count(*) AS count FROM plugin_scmgit_secondary_repos WHERE group_id=$1 AND repo_name = $2', + array ($params['group_id'], $params['repo_name'])); + if (! $result) { + $params['error_msg'] = db_error(); + return false; + } + if (db_result($result, 0, 'count') == 0) { + $params['error_msg'] = sprintf(_('No repository %s exists'), $params['repo_name']); + return false; + } + + $result = db_query_params ('UPDATE plugin_scmgit_secondary_repos SET next_action = 1 WHERE group_id=$1 AND repo_name=$2', + array ($params['group_id'], $params['repo_name'])); + if (! $result) { + $params['error_msg'] = db_error(); + return false; + } + + plugin_hook ("scm_admin_update", $params); + return true; + } + + function scm_admin_buttons(&$params) { + $project = $this->checkParams($params); + if (!$project) { + return false ; + } + if (! $project->usesPlugin ($this->name)) { + return false; + } + + global $HTML; + + $HTML->addButtons( + '/scm/admin/?group_id='.$params['group_id'].'&form_create_repo=1', + _("Add Repository"), + array('icon' => html_image('ic/scm_repo_add.png')) + ); + } + + function scm_admin_form(&$params) { + $project = $this->checkParams($params); + if (!$project) { + return false ; + } + if (! $project->usesPlugin ($this->name)) { + return false; + } + + session_require_perm('project_admin', $params['group_id']); + + $project_name = $project->getUnixName(); + + $select_repo = ' + + + + +\n"; + } + print ''; + } + + printf('

'._('Create new Git repository for project %1$s').'

', $project_name); + + ?> +
+ + +


+

+


+

+


+

+ + +
+ + _('SCM Repository'),'group'=>$group_id)); click("//input[@name='scmradio' and @value='scmgit']"); $this->clickAndWait("submit"); + $this->type("//input[@name='repo_name']", "other-repo"); + $this->type("//input[@name='description']", "Description for second repository"); + $this->clickAndWait("//input[@value='Submit']"); + $this->assertTextPresent("New repository other-repo registered"); + // Run the cronjob to create repositories - $this->cron("create_scm_repos.php"); + $this->cron("cronjobs/create_scm_repos.php"); + + $this->open(ROOT); + $this->clickAndWait("link=ProjectA"); + $this->clickAndWait("link=SCM"); + $this->assertTextPresent("other-repo"); + + $this->open(ROOT); + $this->clickAndWait("link=ProjectA"); + $this->clickAndWait("link=Admin"); + $this->clickAndWait("link=Tools"); + $this->clickAndWait("link=Source Code Admin"); + $this->clickAndWait("//form[@name='form_delete_repo_other-repo']/input[@value='Delete']"); + $this->assertTextPresent("Repository other-repo is marked for deletion"); + + // Run the cronjob to create repositories + $this->cron("cronjobs/create_scm_repos.php"); $this->open(ROOT); $this->clickAndWait("link=ProjectA"); $this->clickAndWait("link=SCM"); + $this->assertTextNotPresent("other-repo"); - $this->assertTextPresent("Anonymous Git Access"); + $this->assertTextPresent("Anonymous Access to the Git"); $this->clickAndWait("link=Request a personal repository"); $this->assertTextPresent("You have now requested a personal Git repository"); -- 2.1.4