<?php
require_once($INC . "/util.inc.php");
require_once($INC . "/log.inc.php");
require_once($INC . "/sql.inc.php");
require_once($INC . "/user.inc.php");
require_once($INC . "/git.inc.php");
require_once($INC . "/rights.inc.php");
require_once($INC . "/prof.inc.php");
require_once($INC . "/mail.inc.php");
require_once($INC . "/events.inc.php");
require_once($INC . "/webhooks.inc.php");
$rg_repo_refs_rights = array(
"F" => "Fetch",
"P" => "Push",
"H" => "Anonymous push",
"S" => "Create annotated tag",
"n" => "Delete annotated tag",
"Y" => "Create un-annotated tag",
"U" => "Modify un-annotated tag",
"u" => "Delete un-annotated tag",
"C" => "Create branch",
"D" => "Delete branch",
"O" => "Non fast-forwards",
"M" => "Merge commits",
"W" => "Bad whitespace"
);
$rg_repo_path_rights = array(
"P" => "Push",
"W" => "Bad whitespace"
);
$rg_repo_rights = array(
"A" => "Access repo",
"E" => "Create/edit repo",
"D" => "Delete repo",
'K' => 'Lock repo',
"G" => "Grant rights",
"a" => "Access bug tracker",
"B" => "Add bugs",
"r" => "Reopen bugs",
"d" => "Delete bugs",
"C" => "Close bugs",
'W' => 'Manage hooks'
);
// TODO: default rights should go into conf file?
// TODO: better move all config to database (modulo db conn info)?
rg_rights_register("repo_refs", $rg_repo_refs_rights, "FMH",
"rg_repo_compare_refs", "rg_repo_rights_inject");
rg_rights_register("repo_path", $rg_repo_path_rights, "P",
"rg_repo_compare_paths", "rg_repo_rights_inject");
rg_rights_register("repo", $rg_repo_rights, "AaB",
FALSE, "rg_repo_rights_inject");
/*
* Helper to obtain the next id (used for bugs and mr)
*/
function rg_repo_next_id($db, $field, $repo_id)
{
rg_prof_start('repo_next_id');
rg_log_enter('repo_next_id: field=' . $field . ' repo_id=' . $repo_id);
$next_id = FALSE;
while (1) {
$params = array('repo_id' => $repo_id);
$sql = 'UPDATE repos SET last_' . $field . '_id'
. ' = last_' . $field . '_id + 1'
. ' WHERE repo_id = @@repo_id@@'
. ' RETURNING last_' . $field . '_id AS next_id';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error('cannot get/update max (' . rg_sql_error() . ')');
break;
}
$rows = rg_sql_num_rows($res);
if ($rows > 0)
$row = rg_sql_fetch_array($res);
rg_sql_free_result($res);
if ($rows == 0) {
rg_repo_set_error('seems that repo does not exists!');
break;
}
$next_id = $row['next_id'];
$key = 'repo_by_id' . '::' . $repo_id . '::'
. 'last_' . $field . '_id';
rg_cache_set($key, $next_id, RG_SOCKET_NO_WAIT);
break;
}
rg_log('DEBUG: next_id=' . $next_id);
rg_log_exit();
rg_prof_end('repo_next_id');
return $next_id;
}
/*
* Improve repo info
*/
function rg_repo_cosmetic($db, &$row)
{
global $rg_ssh_port, $rg_git_port;
// Because this field was added later and the cache may not have it
if (!isset($row['template']))
$row['template'] = '';
if (strlen(substr($row['description'], 0, 1)) == 1) {
$_a = rg_xss_safe($row['description']);
$row['HTML:description_nlbr'] = nl2br($_a);
} else {
$row['HTML:description_nlbr'] = 'n/a';
}
if (isset($row['itime']))
$row['HTML:itime_nice'] = gmdate('Y-m-d', $row['itime']);
$_ui = rg_user_info($db, $row['uid'], '', '');
if ($_ui['exists'] == 1) {
$row['owner'] = $_ui['username'];
$row['url_user'] = rg_base_url() . rg_re_userpage($_ui);
$row['url_repo'] = rg_base_url() . rg_re_repopage($_ui, $row['name']);
$row['clone_url_ssh'] = rg_re_repo_ssh($_ui['organization'],
$_ui['username'], $row['name']);
$row['clone_url_git'] = rg_re_repo_git($_ui['organization'],
$_ui['username'], $row['name']);
$row['clone_url_http'] = rg_re_repo_http($_ui['organization'],
$_ui['username'], $row['name']);
}
$row['master_name'] = '-';
if ($row['master'] > 0) {
$_mi = rg_repo_info($db, $row['master'], 0, '');
if ($_mi['exists'] = 1)
$row['master_name'] = $_mi['name'];
}
if (isset($row['disk_used_mb']))
$row['disk_used'] = rg_1024($row['disk_used_mb'] * 1024 * 1024);
}
/*
* Returns info about a repo
* If you want to lookup by repo_id or uid/repo_name
*/
function rg_repo_info($db, $repo_id, $uid, $repo_name)
{
rg_prof_start("repo_info");
rg_log_enter("repo_info: repo_id=$repo_id uid=$uid repo_name=$repo_name.");
$ret['ok'] = 0;
$ret['exists'] = 0;
while(1) {
$params = array("uid" => $uid,
"repo_id" => $repo_id,
"repo_name" => $repo_name);
if ($repo_id > 0) {
$c = rg_cache_get('repo_by_id' . '::' . $repo_id);
if (($c !== FALSE) && isset($c['repo_id'])) {
$ret = $c;
rg_repo_cosmetic($db, $ret);
break;
}
$sql = "SELECT * FROM repos WHERE repo_id = @@repo_id@@";
} else if (!empty($repo_name)) {
$x_repo_id = rg_cache_get("repo_by_name::$uid::$repo_name");
if ($x_repo_id !== FALSE) {
$ret = rg_repo_info($db, $x_repo_id, $uid, "");
break;
}
$sql = "SELECT * FROM repos WHERE uid = @@uid@@"
. " AND name = @@repo_name@@";
} else {
rg_repo_set_error("no repo_id or user/repo specified!");
break;
}
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error("cannot query (" . rg_sql_error() . ")");
break;
}
$rows = rg_sql_num_rows($res);
if ($rows > 0)
$ret = rg_sql_fetch_array($res);
rg_sql_free_result($res);
if (($rows == 0) && ($repo_id == 0)) {
// Repo not found, maybe it was renamed
$_repo_id = rg_repo_lookup_by_old_name($db, $uid, $repo_name);
if ($_repo_id === FALSE)
break;
if ($_repo_id > 0) {
$ret = rg_repo_info($db, $_repo_id, 0, "");
break;
}
}
$ret['ok'] = 1;
if ($rows > 0) {
$ret['exists'] = 1;
} else {
$ret['exists'] = 0;
}
//rg_log_ml("CHECK: ret=" . print_r($ret, TRUE));
if ($ret['exists'] == 1) {
rg_cache_set('repo_by_id' . '::' . $ret['repo_id'],
$ret, RG_SOCKET_NO_WAIT);
rg_cache_set("repo_by_name::$uid::" . $ret['name'],
$ret['repo_id'], RG_SOCKET_NO_WAIT);
}
if ($rows > 0)
rg_repo_cosmetic($db, $ret);
break;
}
rg_log_exit();
rg_prof_end("repo_info");
return $ret;
}
/*
* Function used to inject rights for a obj_id/type combination
* It will be called from rg_rights_get
* @out - the output array where we want to put rights
*/
function rg_repo_rights_inject($db, $obj_id, $type, $owner, $uid)
{
rg_log_enter("repo_rights_inject: obj_id=$obj_id type=$type"
. " owner=$owner uid=$uid");
$ret = array();
while (1) {
$a = array();
$a['type'] = $type;
$a['obj_id'] = $obj_id;
$a['uid'] = 0; // TODO: not clear here what to put!
$a['itime'] = 0;
$a['misc'] = "";
$a['prio'] = 0;
$a['who'] = $owner; // TODO: not clear if correct/good
$a['right_id'] = 0;
$a['ip'] = "";
$a['can_be_deleted'] = 0;
$a['description'] = "Autogenerated";
if ($uid > 0) {
$ui = rg_user_info($db, $uid, "", "");
if ($ui['exists'] != 1)
break;
if ($ui['is_admin'] == 1) {
$a['uid'] = $owner;
$a['rights'] = rg_rights_all($type);
$a['description'] = 'Autogenerated (you are admin)';
$ret[] = $a;
}
}
$ri = rg_repo_info($db, $obj_id, 0, "");
if ($ri['exists'] != 1) {
rg_log("repo $obj_id does not exists");
break;
}
// Here, rights to inject, even if repo is not public
if (strcmp($type, "repo_path") == 0) {
$a['uid'] = 0;
$a['prio'] = 30001;
$a['rights'] = "PW"; // push + whitespace
$a['description'] = 'Autogenerated (sane default)';
$ret[] = $a;
}
// No rights to inject if is not public
if ($ri['public'] != 1)
break;
if (strcmp($type, "repo") == 0) {
$a['uid'] = 0;
$a['rights'] = "AaB"; // access repo + access bug tracker + add bugs
$a['rights'] = "Aa"; // access repo + access bug tracker + add bugs
$a['description'] = 'Autogenerated (repo is public)';
$ret[] = $a;
} else if (strcmp($type, "repo_refs") == 0) {
$a['uid'] = 0;
$a['prio'] = 1;
$a['rights'] = "F"; // Fetch
$a['description'] = 'Autogenerated (repo is public)';
$ret[] = $a;
$a['uid'] = 0;
$a['prio'] = 30001;
$a['rights'] = "H"; // push + whitespace
$a['description'] = 'Autogenerated (sane default)';
$ret[] = $a;
} else if (strcmp($type, "repo_path") == 0) {
} else {
rg_log("type $type not used. bug?");
break;
}
break;
}
rg_log_exit();
return $ret;
}
/*
* Returns TRUE if the login user has @rights rights
*/
function rg_repo_has_rights($db, $rg, $rights)
{
$x = array();
$x['obj_id'] = $rg['ri']['repo_id'];
$x['type'] = 'repo';
$x['owner'] = $rg['ri']['uid'];
$x['uid'] = $rg['login_ui']['uid'];
$x['username'] = $rg['login_ui']['username'];
$x['needed_rights'] = $rights;
$x['ip'] = $rg['ip'];
$x['misc'] = '';
return rg_rights_allow($db, $x);
}
/*
* Bring a ref to a canonical format (refs/x/name)
* TODO: move to git.inc?
*/
function rg_repo_ref_canon($ref)
{
if (strncmp($ref, "refs/", 5) == 0) {
// do nothing
} else if (strncmp($ref, "/refs/", 6) == 0) {
$ref = substr($ref, 1);
} else {
$ref = "refs/heads/" . $ref;
}
return $ref;
}
/*
* Compare function for refs
*/
function rg_repo_compare_refs($misc, $ref)
{
rg_prof_start("repo_compare_refs");
if (empty($misc))
return TRUE;
$misc = rg_repo_ref_canon($misc);
$ref = rg_repo_ref_canon($ref);
// seems that by doing escape, nothing will match!
//$qmisc = preg_quote($misc, '/');
$qmisc = str_replace('`', '', $misc);
$ret = preg_match('`^' . $qmisc . '`uD', $ref);
rg_log("repo_compare_refs: ret=$ret misc=$misc ref=$ref [qmisc=$qmisc] => "
. ($ret === 1 ? "match" : "no match"));
rg_prof_end("repo_compare_refs");
return $ret === 1;
}
/*
* Compare function for paths
*/
function rg_repo_compare_paths($misc, $path)
{
rg_prof_start("repo_compare_paths");
$qmisc = preg_quote($misc, '/');
$ret = preg_match('/' . $qmisc . '/uD', $path);
rg_log("repo_compare_paths: misc=$misc path=$path => " . ($ret === 1 ? "T" : "F"));
rg_prof_end("repo_compare_paths");
return $ret === 1;
}
/*
* Remove "refs/heads/" when showing rights on web
* TODO: don't know where to call it. Maybe in rg_rights_load?
* TODO: also, register this kind of function.
*/
function rg_repo_ref_nice($ref)
{
if (strncmp($ref, "refs/heads/", 11) == 0)
$ref = substr($ref, 11);
return $ref;
}
// Repo history categories
define('REPO_CAT_CREATE', 1);
define('REPO_CAT_CLONED', 2);
define('REPO_CAT_PUSH', 3);
define('REPO_CAT_RENAME', 4);
define('REPO_CAT_UPDATE', 5);
define('REPO_CAT_BUG_ADDED', 10);
define('REPO_CAT_BUG_CLOSED', 11);
define('REPO_CAT_GIT_ATAG_CREATE', 20);
define('REPO_CAT_GIT_ATAG_DELETE', 21);
define('REPO_CAT_GIT_ATAG_UPDATE', 22);
define('REPO_CAT_GIT_UTAG_CREATE', 30);
define('REPO_CAT_GIT_UTAG_DELETE', 31);
define('REPO_CAT_GIT_UTAG_UPDATE', 32);
define('REPO_CAT_GIT_BRANCH_DELETE', 40);
define('REPO_CAT_GIT_BRANCH_UPDATE', 41);
define('REPO_CAT_GIT_BRANCH_CREATE', 42);
define('REPO_CAT_GIT_BRANCH_ANON_PUSH', 43);
define('REPO_CAT_LOCK', 50);
define('REPO_CAT_UNLOCK', 51);
$rg_repo_error = "";
function rg_repo_set_error($str)
{
global $rg_repo_error;
$rg_repo_error = $str;
rg_log($str);
}
function rg_repo_error()
{
global $rg_repo_error;
return $rg_repo_error;
}
/*
* Events functions
*/
$rg_repo_functions = array(
3000 => "rg_repo_event_new",
3001 => "rg_repo_event_del",
3002 => "rg_repo_event_update",
3003 => "rg_repo_event_notify_user",
3004 => "rg_repo_event_symlink_by_name",
3005 => "rg_repo_event_storage_create",
3006 => "rg_repo_history_insert",
3007 => 'rg_repo_event_push'
);
rg_event_register_functions($rg_repo_functions);
/*
* Event for adding a new repo
*/
function rg_repo_event_new($db, $event)
{
$ret = array();
$event['op'] = "new";
// Create git dir
$x = $event;
$x['category'] = 3005;
$x['prio'] = 20;
$x['notification'] .= "-git";
$ret[] = $x;
// make symlink by name
$x = $event;
$x['category'] = 3004;
$x['prio'] = 200;
$x['notification'] .= "-symlink";
$ret[] = $x;
// notify user
$x = $event;
$x['category'] = 3003;
$x['prio'] = 100;
$x['notification'] .= "-notify";
$ret[] = $x;
// webhook
$x = $event;
$x['category'] = 'wh_send';
$x['prio'] = 50;
$x['wh_event'] = 'C'; // see rg_wh_events array
$ret[] = $x;
// add a history entry
$x = $event;
$x['category'] = 3006;
$x['prio'] = 50;
$ret[] = $x;
// TODO: notify watchers of the user
return $ret;
}
/*
* Event for deleting repo
*/
function rg_repo_event_del($db, $event)
{
$ret = array();
$event['op'] = "del";
// notify user
$ret[] = array_merge($event, array("category" => 3003, "prio" => 100));
// TODO: notify watchers
return $ret;
}
/*
* Make a symlink by name (by_name/name -> ../by_id/xx/xx/xx/xx/xxxxxxxx.git)
* TODO: why return may be an array?!
*/
function rg_repo_event_symlink_by_name($db, $e)
{
global $php_errormsg;
rg_prof_start("repo_event_symlink_by_name");
$id_path = rg_repo_path_by_id($e['ui']['uid'], $e['ri']['repo_id']);
$id_path_rel = rg_repo_path_by_id_rel($e['ui']['uid'], $e['ri']['repo_id']);
$new_path = rg_repo_path_by_name($e['ui']['uid'], $e['ri']['name']);
$ret = FALSE;
while (1) {
// Check if we already did the rename
if (file_exists($new_path)) {
if (!is_link($new_path)) {
rg_internal_error("$new_path is not a link");
break;
}
$v = readlink($new_path);
if ($v === FALSE) {
rg_internal_error("cannot read link $new_path");
break;
}
rg_log("new_path points to [$v]");
if (strcmp($id_path_rel, $v) == 0) {
// Link already done
$ret = array();
break;
}
// Seems that new_path points to other place
$r = rename($new_path, $new_path . ".BOGUS." . time());
if ($r !== TRUE) {
rg_internal_error("cannot rename bogus");
break;
}
}
// Create links' parent
$new_path_parent = dirname($new_path);
if (!is_dir($new_path_parent)) {
$r = mkdir($new_path_parent, 0700, TRUE);
if ($r === FALSE) {
rg_repo_set_error("cannot create links' parent");
break;
}
}
// Now, the new name is free, do the link
$r = symlink($id_path_rel, $new_path);
if ($r !== TRUE) {
rg_internal_error("cannot symlink $id_path -> $new_path ($php_errormsg)!");
break;
}
$ret = array();
break;
}
rg_prof_end("repo_event_symlink_by_name");
return $ret;
}
/*
* Creates git dir storage
*/
function rg_repo_event_storage_create($db, $e)
{
rg_prof_start("repo_event_storage_create");
rg_log_enter("repo_event_storage_create: e=" . rg_array2string($e));
$ret = FALSE;
while (1) {
$by_id_path = rg_repo_path_by_id($e['ui']['uid'], $e['ri']['repo_id']);
if (!is_dir($by_id_path)) {
if (mkdir($by_id_path, 0700, TRUE) === FALSE) {
rg_repo_set_error("could not create folder $dst");
break;
}
}
if ($e['ri']['master'] == 0) {
$r = rg_git_init($by_id_path);
if ($r === FALSE) {
rg_repo_set_error("cannot init master"
. " (" . rg_git_error() . ")");
break;
}
} else {
$mi = rg_repo_info($db, $e['ri']['master'], 0, "");
if ($mi['exists'] != 1) {
rg_repo_set_error("cannot find master"
. " (" . rg_repo_error() . ")");
break;
}
$master_by_id_path = rg_repo_path_by_id($mi['uid'],
$mi['repo_id']);
$r = rg_git_clone($master_by_id_path, $by_id_path);
if ($r === FALSE) {
rg_repo_set_error("could not create repo"
. " (" . rg_git_error() . ")");
break;
}
}
$r = rg_repo_event_symlink_by_name($db, $e);
if ($r === FALSE)
break;
$r = rg_repo_git_done($db, $e['ri']['repo_id']);
if ($r !== TRUE)
break;
$ret = array();
break;
}
rg_log_exit();
rg_prof_end("repo_event_storage_create");
return $ret;
}
/*
* Event for renaming a repo
*/
function rg_repo_event_update($db, $event)
{
$ret = array();
$event['op'] = "update";
// make symlink by name
$ret[] = array_merge($event, array("category" => 3004, "prio" => 200));
// notify user
$ret[] = array_merge($event, array("category" => 3003, "prio" => 100));
// TODO: notify watchers
return $ret;
}
/*
* User notification
*/
function rg_repo_event_notify_user($db, $event)
{
rg_prof_start("repo_event_notify_user");
$ret = FALSE;
while (1) {
$r = rg_mail_template("mail/user/repo/" . $event['op'], $event);
if ($r === FALSE)
break;
$ret = array();
break;
}
rg_prof_end("repo_event_notify_user");
return $ret;
}
/*
* Inserts an event into repo_history table
*/
function rg_repo_history_insert($db, $event)
{
rg_prof_start("repo_history_insert");
rg_log_enter("repo_history_insert: event=" . rg_array2string($event));
if (!isset($event['ui']['uid']))
$event['ui']['uid'] = 0;
$ret = FALSE;
while (1) {
$now = time();
$params = array("now" => $now,
"repo_id" => $event['ri']['repo_id'],
"uid" => $event['ui']['uid'],
"cat" => $event['history_category'],
"mess" => $event['history_message']);
$sql = "INSERT INTO repo_history_" . gmdate("Y_m", $now)
. " (itime, uid, repo_id, category, message)"
. " VALUES (@@now@@, @@uid@@, @@repo_id@@, @@cat@@, @@mess@@)";
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE)
break;
rg_sql_free_result($res);
$ret = array();
break;
}
rg_log_exit();
rg_prof_end("repo_history_insert");
return $ret;
}
/*
* Event for pushing into a repo
*/
function rg_repo_event_push($db, $event)
{
$ret = array();
// notify user
if (0) { // TODO
$x = $event;
$x['category'] = 3003;
$x['prio'] = 100;
$x['notification'] .= "-notify";
$ret[] = $x;
}
// webhook
$x = $event;
$x['category'] = 'wh_send';
$x['wh_event'] = 'P'; // push
$ret[] = $x;
// TODO: notify watchers of the user
return $ret;
}
/*
* Returns last events from repo_history
* @category: 0 for any
* @number: number of entries
* @max_seconds: limit the search to less than max_seconds in the past
*/
function rg_repo_history_load($db, $repo_id, $category, $number, $max_seconds)
{
rg_prof_start("repo_history_load");
rg_log_enter("repo_history_load: repo_id=$repo_id, category=$category"
. ", number=$number max_seconds=$max_seconds");
$ret = FALSE;
while (1) {
$now = time();
$category_sql = "";
if ($category > 0)
$category_sql = " AND category = " . $category;
$limit_sql = "";
if ($number > 0)
$limit_sql = " LIMIT " . $number;
else
$limit_sql = " LIMIT 50";
$time_sql = "";
if ($max_seconds > 0)
$time_sql = " AND itime >= " . ($now - $max_seconds);
$sql = "SELECT * FROM repo_history"
. " WHERE repo_id = $repo_id"
. $category_sql
. $time_sql
. " ORDER BY itime DESC"
. $limit_sql;
$res = rg_sql_query($db, $sql);
if ($res === FALSE)
break;
$ret = array();
while (($row = rg_sql_fetch_array($res))) {
$row['username'] = 'n/a';
if ($row['uid'] > 0) {
$ui = rg_user_info($db, $row['uid'], '', '');
if ($ui['exists'] == 1)
$row['username'] = $ui['username'];
}
if ($row['itime'] == 0)
$row['itime_text'] = "N/A";
else
$row['itime_text'] = gmdate("Y-m-d H:i", $row['itime']);
$ret[] = $row;
}
rg_sql_free_result($res);
break;
}
rg_log_exit();
rg_prof_end("repo_history_load");
return $ret;
}
/*
* Enforce name
*/
function rg_repo_ok($repo)
{
global $rg_repo_allow;
global $rg_repo_min_len;
global $rg_repo_max_len;
if (empty($repo)) {
rg_repo_set_error("invalid repository name (empty)");
return FALSE;
}
if (rg_chars_allow($repo, $rg_repo_allow, $invalid) !== TRUE) {
rg_repo_set_error("invalid repository name"
. " (invalid chars: '$invalid')");
return FALSE;
}
if (preg_match('/\.\./', $repo) !== 0) {
rg_repo_set_error("invalid repository name (..)");
return FALSE;
}
$len = strlen($repo);
if ($len < $rg_repo_min_len) {
rg_repo_set_error("repository name is too short"
. " (minimum $rg_repo_min_len < $len)");
return FALSE;
}
if ($len > $rg_repo_max_len) {
rg_repo_set_error("repository name is too long"
. " (maximum $rg_repo_max_len > $len)");
return FALSE;
}
return TRUE;
}
/*
* Returns the relative path to a repository based on id
*/
function rg_repo_path_by_id_rel($uid, $repo_id)
{
return "../by_id/" . $repo_id . ".git";
}
/*
* Returns the path to a repository based on id
*/
function rg_repo_path_by_id($uid, $repo_id)
{
return rg_user_path_by_id($uid) . "/repos/by_id/" . $repo_id . ".git";
}
/*
* Returns the path to a repository based on name
*/
function rg_repo_path_by_name($uid, $repo_name)
{
return rg_user_path_by_id($uid) . "/repos/by_name/" . $repo_name . ".git";
}
/*
* Delete a repo
*/
function rg_repo_delete($db, $repo_id, $ui)
{
rg_prof_start("repo_delete");
rg_log_enter("repo_delete: uid=" . $ui['uid'] . ", repo_id=$repo_id");
$ret = FALSE;
while (1) {
// TODO: Transaction?
$ri = rg_repo_info($db, $repo_id, 0, "");
if ($ri['ok'] != 1)
break;
if ($ri['exists'] != 1) {
rg_repo_set_error("Repository does not exists.");
break;
}
// Only mark it as such, deletion will happen in background
$params = array("repo_id" => $repo_id);
$sql = "UPDATE repos SET deleted = 1"
. " WHERE repo_id = @@repo_id@@";
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error("Cannot delete (" . rg_sql_error() . ")");
break;
}
rg_sql_free_result($res);
$event = array(
'category' => 3001,
'prio' => 50,
'ui' => $ui,
'ri' => array(
'name' => $ri['name'],
'repo_id' => $repo_id
)
);
$r = rg_event_add($db, $event);
if ($r !== TRUE) {
rg_repo_set_error("cannot add event"
. " (" . rg_event_error() . ")");
break;
}
rg_event_signal_daemon("", 0);
rg_cache_unset('repo_by_id::' . $repo_id, RG_SOCKET_NO_WAIT);
rg_cache_unset('repo_by_name::' . $ui['uid']
. '::' . $ri['name'], RG_SOCKET_NO_WAIT);
rg_cache_unset('user' . '::' . $ui['uid'] . '::' . 'has_repos',
RG_SOCKET_NO_WAIT);
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("repo_delete");
return $ret;
}
/*
* Lookup in db the old name, so we can redirect the user to the new name
*/
function rg_repo_lookup_by_old_name($db, $uid, $old_name)
{
rg_prof_start("repo_lookup_by_old_name");
rg_log_enter("repo_lookup_by_old_name: uid=$uid old_name=$old_name");
$ret = FALSE;
while (1) {
$c = rg_cache_get("repo_by_old_name::$uid::$old_name");
if ($c !== FALSE) {
$ret = $c;
break;
}
$params = array("uid" => $uid, "old_name" => $old_name);
$sql = "SELECT repo_id FROM repos_renames"
. " WHERE uid = @@uid@@"
. " AND old_name = @@old_name@@";
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error("cannot lookup old name ("
. rg_sql_error() . ")");
break;
}
$rows = rg_sql_num_rows($res);
if ($rows > 0)
$row = rg_sql_fetch_array($res);
rg_sql_free_result($res);
if ($rows == 0)
$ret = 0;
else
$ret = $row['repo_id'];
rg_cache_set("repo_by_old_name::$uid::$old_name", $ret, RG_SOCKET_NO_WAIT);
break;
}
rg_log_exit();
rg_prof_end("repo_lookup_by_old_name");
return $ret;
}
/*
* Add a rename to the database
*/
function rg_repo_insert_rename($db, $uid, $repo_id, $old_name)
{
rg_prof_start("repo_insert_rename");
rg_log_enter("repo_insert_rename: uid=$uid repo_id=$repo_id old_name=$old_name");
$ret = FALSE;
while (1) {
// Check if already exists in the database
$r = rg_repo_lookup_by_old_name($db, $uid, $old_name);
if ($r === FALSE)
break;
$params = array("repo_id" => $repo_id,
"uid" => $uid,
"old_name" => $old_name,
"now" => time());
if ($r > 0) {
$sql = "UPDATE repos_renames"
. " SET repo_id = @@repo_id@@"
. " WHERE uid = @@uid@@"
. " AND old_name = @@old_name@@";
} else {
$sql = "INSERT INTO repos_renames (uid, old_name"
. ", repo_id, itime)"
. " VALUES (@@uid@@, @@old_name@@, @@repo_id@@"
. ", @@now@@)";
}
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error("cannot link with old_name in db"
. " (" . rg_sql_error() . ")");
break;
}
rg_cache_set("repo_by_name::$uid::$old_name", $repo_id, RG_SOCKET_NO_WAIT);
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("repo_insert_rename");
return $ret;
}
/*
* Creates/updates a repository
* @login_ui - info of the user doing the update.
* TODO: Warning, it may not be the owner.
* TODO: where do we validate if the user has enough public/private slots?
*/
function rg_repo_edit($db, $login_ui, &$new)
{
rg_prof_start("repo_edit");
rg_log_enter("repo_edit: login_uid=" . $login_ui['uid']
. " new=" . rg_array2string($new));
// TODO: test if user is allowed to add a repository
// TODO: test if user did not cross the limit for number of repos
$ret = FALSE;
while (1) {
if (rg_repo_ok($new['name']) !== TRUE)
break;
if ($new['repo_id'] == 0) {
// Check if name is already taken
$ri = rg_repo_info($db, 0, $login_ui['uid'],
$new['name']);
if ($ri['ok'] != 1)
break;
if ($ri['exists'] == 1) {
rg_repo_set_error('name already taken;'
. ' choose a different one');
break;
}
} else {
// Test if repo_id is valid
// TODO: also here we must test if we have a dup name!
$ri = rg_repo_info($db, $new['repo_id'],
$login_ui['uid'], "");
if ($ri['ok'] != 1)
break;
if ($ri['exists'] != 1) {
rg_repo_set_error('repo ' . $new['repo_id']
. ' does not exists.');
break;
}
}
$renamed = 0;
if ($new['repo_id'] > 0) {
// Check if the user renamed the repo
if (strcmp($new['name'], $ri['name']) != 0) {
$renamed = 1;
$r = rg_repo_insert_rename($db, $login_ui['uid'],
$new['repo_id'], $ri['name']);
if ($r !== TRUE)
break;
}
}
//TODO: master may be not accessible to this user. check.
// Small fixes
$new['itime'] = time();
$new['uid'] = $login_ui['uid'];
if ($new['repo_id'] == 0) {
$new['deleted'] = 0;
$new['disk_used_mb'] = 0;
$new['git_dir_done'] = 0;
$new['last_bug_id'] = 0;
$new['last_mr_id'] = 0;
$sql = "INSERT INTO repos (uid, master, name"
. ", itime, max_commit_size, description"
. ", git_dir_done, public, license, template)"
. " VALUES (@@uid@@, @@master@@, @@name@@"
. ", @@itime@@, @@max_commit_size@@"
. ", @@description@@, 0, @@public@@"
. ", @@license@@, @@template@@)"
. " RETURNING repo_id";
} else {
$sql = "UPDATE repos SET name = @@name@@"
. ", max_commit_size = @@max_commit_size@@"
. ", description = @@description@@"
. ", public = @@public@@"
. ", license = @@license@@"
. ", template = @@template@@"
. " WHERE repo_id = @@repo_id@@";
}
$res = rg_sql_query_params($db, $sql, $new);
if ($res === FALSE) {
rg_repo_set_error("cannot update: " . rg_sql_error());
break;
}
if ($new['repo_id'] == 0) {
$row = rg_sql_fetch_array($res);
if ($row === FALSE) {
rg_repo_set_error('cannot fetch row');
break;
}
}
rg_sql_free_result($res);
if ($new['repo_id'] == 0) {
$cat = 3000;
$hcat = REPO_CAT_CREATE;
$hmess = "Repository has been created";
$notification = "repo_create-" . $login_ui['uid']
. "-" . $row['repo_id'];
$old_description = "";
$new['repo_id'] = $row['repo_id'];
} else {
$cat = 3002;
$hcat = REPO_CAT_UPDATE;
$hmess = "Repository has been updated";
$notification = "";
$old_description = $ri['description'];
// TODO: we should log what changed
}
$event = array(
'category' => $cat,
'prio' => 50,
'notification' => $notification,
'ui' => $login_ui,
'history_category' => $hcat,
'history_message' => $hmess);
$event = rg_array_merge($event, 'ri_old', $ri);
$new['url'] = rg_base_url()
. rg_re_repopage($login_ui, $new['name']);
$event = rg_array_merge($event, 'ri', $new);
$event['ri_old']['description_md5'] = md5($old_description);
$event['ri']['description_md5'] = md5($new['description']);
$r = rg_event_add($db, $event);
if ($r !== TRUE) {
rg_repo_set_error("cannot add event"
. " (" . rg_event_error() . ")");
break;
}
$new['ok'] = 1;
$new['exists'] = 1;
rg_cache_merge("repo_by_id::" . $new['repo_id'], $new,
RG_SOCKET_NO_WAIT);
rg_cache_set("repo_by_name::" . $login_ui['uid'] . "::"
. $new['name'], $new['repo_id'], RG_SOCKET_NO_WAIT);
rg_event_signal_daemon('', 0);
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("repo_edit");
return $ret;
}
/*
* Returns 1 if user has at least one repo
* Used to go directly to 'create' page instead to 'list'
*/
function rg_repo_have($db, $uid)
{
rg_prof_start('repo_have');
$ret = 1;
while (1) {
$key = 'user' . '::' . $uid . '::' . 'has_repos';
$c = rg_cache_get($key);
if ($c !== FALSE) {
$ret = $c;
break;
}
$params = array('uid' => $uid);
$sql = 'SELECT 1 FROM repos WHERE uid = @@uid@@ LIMIT 1';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE)
break;
$rows = rg_sql_num_rows($res);
rg_sql_free_result($res);
$ret = $rows;
rg_cache_set($key, $ret, RG_SOCKET_NO_WAIT);
break;
}
rg_prof_end('repo_have');
return $ret;
}
/*
* List repositories
*/
function rg_repo_list_query($db, $url, $sql, $params)
{
rg_prof_start("repo_list_query");
rg_log("repo_list_query: url=$url, sql=$sql...");
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error("cannot list by query (" . rg_sql_error() . ")");
return FALSE;
}
$d = array();
while (($row = rg_sql_fetch_array($res))) {
$_line = array();
foreach ($row as $k => $v)
$_line[$k] = $v;
$_ui = rg_user_info($db, $row['uid'], "", "");
if ($_ui['exists'] != 1) {
rg_log("uid " . $row['uid']
. " associated with this repo not found");
continue;
}
rg_repo_cosmetic($db, $_line);
$d[] = $_line;
}
rg_sql_free_result($res);
rg_prof_end("repo_list_query");
return rg_template_table("repo/list", $d, array());
}
/*
* List repos of page user 'ui'.
* @uid - owner of the to be listed repos
*/
function rg_repo_list($db, $rg, $url, $uid)
{
rg_log("repo_list: url=$url uid=" . $uid
. " login_uid=" . $rg['login_ui']['uid']);
$params = array(
"uid" => $uid,
"login_uid" => $rg['login_ui']['uid']);
if ($uid > 0)
$add = ' AND uid = @@uid@@';
else
$add = ' AND uid != @@login_uid@@';
// TODO: also admin must be able to see them?
if (($rg['login_ui']['uid'] == 0)
|| ($rg['login_ui']['uid'] != $uid))
$add .= " AND public = 1";
$sql = "SELECT * FROM repos"
. " WHERE deleted = 0"
. $add
. " ORDER BY name";
return rg_repo_list_query($db, $url, $sql, $params);
}
/*
* Search in all repositories owned by 'login_ui' or public
* We need to exclude private repositories.
*/
function rg_repo_search($db, $login_ui, $q)
{
rg_prof_start("repo_search");
rg_log("repo_search: q=[$q]");
$admin = 0;
if (isset($login_ui['admin']) && ($login_ui['admin'] == 1))
$admin = 1;
$params = array("q" => "%" . $q . "%",
"uid" => $login_ui['uid']);
$sql = "SELECT * FROM repos"
. " WHERE deleted = 0"
. " AND (uid = @@uid@@ OR public = 1 OR " . $admin . " = 1)"
. " AND (name ILIKE @@q@@ OR description ILIKE @@q@@)"
. " ORDER BY master, name"
. " LIMIT 50";
$r = rg_repo_list_query($db, "", $sql, $params);
rg_prof_end("repo_search");
return $r;
}
/*
* Computes the size of a repository
* @all - if TRUE, take in account all files. If FALSE, ignore the files with
* many links (a clone).
*/
function rg_repo_size($path, $all)
{
//rg_log("repo_disk_mb: path=$path");
$ret = FALSE;
while (1) {
$dir = @opendir($path);
if ($dir === FALSE) {
rg_repo_set_error("Cannot open $path!");
break;
}
$total = 0;
$error = FALSE;
while (($f = readdir($dir)) !== FALSE) {
if (strcmp($f, ".") == 0)
continue;
if (strcmp($f, "..") == 0)
continue;
if (is_dir($path . "/" . $f)) {
$r = rg_repo_size($path . "/" . $f, $all);
if ($r === FALSE) {
$error = TRUE;
break;
}
$total += $r;
}
if (is_file($path . "/" . $f)) {
$s = stat($path . "/" . $f);
if ($s === FALSE) {
$error = TRUE;
break;
}
$r = $s['size'];
if ($all === FALSE) {
if ($s['nlink'] > 1)
$r = 0;
}
$total += $r;
}
}
closedir($dir);
if ($error === FALSE)
$ret = $total;
break;
}
return $ret;
}
/*
* Mark a git repo as done
*/
function rg_repo_git_done($db, $repo_id)
{
rg_prof_start("repo_git_done");
rg_log_enter("repo_git_done: repo_id=$repo_id...");
$ret = FALSE;
while (1) {
$params = array("repo_id" => $repo_id);
rg_cache_merge('repo_by_id::' . $repo_id,
array('git_dir_done' => 1), RG_SOCKET_NO_WAIT);
$sql = "UPDATE repos SET git_dir_done = 1"
. " WHERE repo_id = @@repo_id@@";
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error("Cannot query (" . rg_sql_error() . ")");
break;
}
rg_sql_free_result($res);
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end("repo_git_done");
return $ret;
}
/*
* Returns the status of a repo
*/
function rg_repo_lock_status($db, $repo_id)
{
rg_prof_start('repo_lock_status');
rg_log_enter('repo_lock_status');
$ret = array();
$ret['ok'] = 0;
while (1) {
$key = 'locks' . '::' . 'repos' . '::' . $repo_id;
$c = rg_cache_get($key);
if ($c !== FALSE) {
$ret = $c;
$ret['ok'] = 1;
break;
}
$params = array('repo_id' => $repo_id);
$sql = 'SELECT * FROM repo_locks'
. ' WHERE repo_id = @@repo_id@@';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error('cannot get lock status');
break;
}
$rows = rg_sql_num_rows($res);
if ($rows == 0) {
$ret['status'] = 0;
} else {
$ret = rg_sql_fetch_array($res);
$ret['status'] = 1;
}
rg_sql_free_result($res);
rg_cache_set($key, $ret, RG_SOCKET_NO_WAIT);
$ret['ok'] = 1;
break;
}
rg_log_exit();
rg_prof_end('repo_lock_status');
return $ret;
}
/*
* Locks/unlocks a repo
* @lock: 1 = lock, 0 = unlock
*/
function rg_repo_lock($db, $repo_id, $uid, $lock, $reason)
{
rg_prof_start('repo_lock');
rg_log_enter('repo_lock');
$ret = FALSE;
while (1) {
$si = rg_repo_lock_status($db, $repo_id);
if ($si['ok'] != 1)
break;
// If we already are in the same state as the passed one
if ($si['status'] == $lock) {
$ret = TRUE;
break;
}
$params = array(
'itime' => time(),
'repo_id' => $repo_id,
'uid' => $uid,
'reason' => $reason);
if ($lock == 1)
$sql = 'INSERT INTO repo_locks (repo_id, uid, itime)'
. ' VALUES (@@repo_id@@, @@uid@@, @@itime@@)';
else
$sql = 'DELETE FROM repo_locks'
. ' WHERE repo_id = @@repo_id@@';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error('db lock/unlock error');
break;
}
$key = 'locks' . '::' . 'repos' . '::' . $repo_id;
if ($lock == 1) {
$params['status'] = 1;
rg_cache_set($key, $params, RG_SOCKET_NO_WAIT);
} else {
rg_cache_unset($key, RG_SOCKET_NO_WAIT);
}
$h = array('ri' => array(), 'ui' => array());
$h['ri']['repo_id'] = $repo_id;
$h['ui']['uid'] = $uid;
$h['history_category'] = $lock == 1 ? REPO_CAT_LOCK : REPO_CAT_UNLOCK;
$h['history_message'] = 'Repository ' .
($lock == 1 ? 'locked' : 'unlocked') . ': ' . $reason;
rg_repo_history_insert($db, $h);
$ret = TRUE;
break;
}
rg_log_exit();
rg_prof_end('repo_lock');
return $ret;
}
/*
* High level function for Repo -> Admin -> Rights -> Repo/Refs/Path rights menu.
*/
function rg_repo_admin_rights($db, $rg, $type)
{
rg_log("rg_repo_admin_rights type=$type");
/* 'repo' is correct here, we test for granting rights on repo */
$x = array();
$x['obj_id'] = $rg['ri']['repo_id'];
$x['type'] = 'repo';
$x['owner'] = $rg['ri']['uid'];
$x['uid'] = $rg['login_ui']['uid'];
$x['username'] = $rg['login_ui']['username'];
$x['needed_rights'] = 'G';
$x['ip'] = $rg['ip'];
$x['misc'] = "";
if (rg_rights_allow($db, $x) !== TRUE)
return rg_template("user/repo/rights/deny.html", $rg, TRUE /* xss */);
$ret = "";
$a = array();
$a['type'] = $type;
$a['right_id'] = rg_var_uint("right_id");
$a['edit_id'] = rg_var_uint("edit_id");
$a['username'] = rg_var_str("username");
$a['rights'] = rg_rights_a2s(rg_var_str("rights"));
$a['misc'] = rg_var_str("misc");
$a['ip'] = rg_var_str("ip");
$a['prio'] = rg_var_uint("prio");
$a['description'] = trim(rg_var_str("description"));
//rg_log_ml("CHECK: a(POST)=" . print_r($a, TRUE));
$errmsg = array();
$list_errmsg = array();
$load_defaults = 1;
$delete = rg_var_bool("delete");
while ($delete == 1) {
if (!rg_valid_referer()) {
$errmsg[] = "invalid referer; try again";
break;
}
if (!rg_token_valid($db, $rg, 'repo_admin_rights', FALSE)) {
$errmsg[] = "invalid token; try again";
break;
}
$list = rg_var_uint("rights_delete_ids");
if (empty($list)) {
$list_errmsg[] = "please select at least one item";
break;
}
$my_list = array();
foreach ($list as $k => $junk)
$my_list[] = $k;
$r = rg_rights_delete_list($db, $type, $rg['ri']['repo_id'], $my_list);
if ($r !== TRUE) {
$list_errmsg[] = "cannot delete rights: " . rg_rights_error();
break;
}
$ret .= rg_template("user/repo/rights/delete_ok.html", $rg, TRUE /* xss */);
break;
}
// edit
while ($a['edit_id'] > 0) {
$owner = $rg['ri']['uid'];
$r = rg_rights_get($db, $rg['ri']['repo_id'], $type,
$owner, -1, $a['edit_id']);
if ($r['ok'] != 1) {
$list_errmsg[] = "cannot load rights: " . rg_rights_error();
break;
}
if (empty($r['list'])) {
$list_errmsg[] = "right not found";
break;
}
// Only one right is returned when we edit by right_id
$a = $r['list'][0];
$load_defaults = 0;
break;
}
$grant = rg_var_bool("grant");
while ($grant == 1) {
$load_defaults = 0;
if (!rg_valid_referer()) {
$errmsg[] = "invalid referer; try again";
break;
}
if (!rg_token_valid($db, $rg, 'repo_admin_rights', FALSE)) {
$errmsg[] = "invalid token; try again";
break;
}
if (strcmp($a['username'], '*') == 0) {
$a['uid'] = 0;
} else {
$_ui = rg_user_info($db, 0, $a['username'], "");
if ($_ui['exists'] == 1) {
$a['uid'] = $_ui['uid'];
} else {
$errmsg[] = rg_user_error();
break;
}
}
$a['obj_id'] = $rg['ri']['repo_id'];
$a['who'] = $rg['login_ui']['uid'];
$r = rg_rights_set($db, $type, $a);
if ($r !== TRUE) {
$errmsg[] = rg_rights_error();
break;
}
$ret .= rg_template("user/repo/rights/grant_ok.html", $rg, TRUE /* xss */);
$load_defaults = 1;
break;
}
if ($load_defaults == 1) {
$rg['right_id'] = 0;
$rg['username'] = "";
$rg['rights'] = rg_rights_default($type);
$rg['description'] = "";
$rg['misc'] = "";
$rg['ip'] = "";
$rg['prio'] = 100;
} else {
$rg = rg_array_merge($rg, '', $a);
}
$rg['rg_form_token'] = rg_token_get($db, $rg, 'repo_admin_rights');
$rg['HTML:errmsg'] = rg_template_errmsg($errmsg);
$rg['HTML:list_errmsg'] = rg_template_errmsg($list_errmsg);
$rg['HTML:rights_checkboxes'] = rg_rights_checkboxes($type, "rights",
$rg['rights']);
// list rights
$r = rg_rights_get($db, $rg['ri']['repo_id'], $type, $rg['ri']['uid'],
-1, 0);
if ($r['ok'] != 1)
$ret .= rg_warning("Cannot load rights. Try later.");
else
$ret .= rg_template_table("user/repo/rights/list_" . $type,
$r['list'], $rg);
$ret .= rg_template("user/repo/rights/form_" . $type . ".html", $rg, TRUE /* xss */);
// hints
$hints = array();
$hints[]['HTML:hint'] = rg_template("hints/repo/edit_rights.html",
$rg, TRUE /* xss */);
$hints[]['HTML:hint'] = rg_template("hints/repo/edit_"
. $type . "_rights.html", $rg, TRUE /* xss */);
$ret .= rg_template_table("hints/list", $hints, $rg);
return $ret;
}
/*
* High level function for repo deletion
*/
function rg_repo_admin_delete($db, $rg)
{
$ret = "";
$x = array();
$x['obj_id'] = $rg['ri']['repo_id'];
$x['type'] = 'repo';
$x['owner'] = $rg['ri']['uid'];
$x['uid'] = $rg['login_ui']['uid'];
$x['username'] = $rg['login_ui']['username'];
$x['needed_rights'] = 'D';
$x['ip'] = $rg['ip'];
$x['misc'] = "";
if (rg_rights_allow($db, $x) !== TRUE)
return rg_template("user/repo/delete/deny.html", $rg, TRUE /* xss */);
$are_you_sure = rg_var_uint("are_you_sure");
$errmsg = array();
$show_form = 1;
while (1) {
if ($rg['doit'] != 1)
break;
if ($are_you_sure == 0) {
$ret .= rg_template("user/repo/delete/no.html", $rg, TRUE /* xss */);
$show_form = 0;
break;
}
if (!rg_valid_referer()) {
$errmsg[] = "invalid referer; try again";
break;
}
if (!rg_token_valid($db, $rg, 'repo_admin_delete', FALSE)) {
$errmsg[] = "invalid token; try again";
break;
}
rg_log_ml("CHECK: rg: " . print_r($rg, TRUE));
$r = rg_repo_delete($db, $rg['ri']['repo_id'], $rg['login_ui']);
if ($r === FALSE) {
$errmsg[] = rg_repo_error();
break;
}
$ret .= rg_template("user/repo/delete/done.html", $rg, TRUE /* xss */);
$show_form = 0;
// TODO: shouldn't we invalidate the cache?
break;
}
if ($show_form == 1) {
$rg['HTML:errmsg'] = rg_template_errmsg($errmsg);
$rg['rg_form_token'] = rg_token_get($db, $rg, 'repo_admin_delete');
$ret .= rg_template("user/repo/delete/sure.html", $rg, TRUE /* xss */);
}
return $ret;
}
/*
* High level function creating/editing a repo
*/
function rg_repo_edit_high_level($db, &$rg)
{
rg_log_enter("rg_repo_edit_high_level");
$ret = "";
$errmsg = array();
$load_form = TRUE;
while (1) {
if ($rg['ri']['repo_id'] > 0)
$edit = TRUE;
else
$edit = FALSE;
// User is not logged in?
if (!$edit && ($rg['login_ui']['uid'] == 0)) {
$ret .= rg_template("user/repo/deny_create.html", $rg, TRUE /* xss */);
$load_form = FALSE;
break;
}
if ($edit) {
$x = array();
$x['obj_id'] = $rg['ri']['repo_id'];
$x['type'] = 'repo';
$x['owner'] = $rg['ri']['uid'];
$x['uid'] = $rg['login_ui']['uid'];
$x['username'] = $rg['login_ui']['username'];
$x['needed_rights'] = 'E'; // E = create/edit
$x['ip'] = $rg['ip'];
$x['misc'] = "";
if (rg_rights_allow($db, $x) !== TRUE) {
$ret .= rg_template("user/repo/deny_edit.html", $rg, TRUE /* xss */);
$load_form = FALSE;
break;
}
}
if ($rg['doit'] != 1) {
if (!$edit) {
// Defaults
$rg['ri'] = array();
$rg['ri']['repo_id'] = '0';
$rg['ri']['master'] = '0';
$rg['ri']['name'] = '';
$rg['ri']['max_commit_size'] = '0';
$rg['ri']['description'] = '';
$rg['ri']['public'] = '1';
$rg['ri']['license'] = '';
$rg['ri']['template'] =
'OS type(s) and version(s)?' . "\n\n"
. 'Application version(s) affected?' . "\n\n"
. 'Steps to reproduce?';
}
break;
}
$rg['ri']['repo_id'] = rg_var_uint('repo_id');
$rg['ri']['master'] = rg_var_uint('master');
$rg['ri']['name'] = trim(rg_var_str('name')); // TODO: filter name!
$rg['ri']['max_commit_size'] = rg_var_uint('max_commit_size');
$rg['ri']['description'] = trim(rg_var_str('description'));
$rg['ri']['public'] = rg_var_bool('public');
$rg['ri']['license'] = trim(rg_var_str('license'));
$rg['ri']['template'] = trim(rg_var_str('template'));
rg_repo_cosmetic($db, $rg['ri']);
//rg_log_ml("CHECK: after repo edit: rg[ri]=" . print_r($rg['ri'], TRUE));
if (!rg_valid_referer()) {
$errmsg[] = "invalid referer; try again";
break;
}
if (!rg_token_valid($db, $rg, 'repo_edit_hl', FALSE)) {
// TODO: replace all of these with a template
$errmsg[] = "invalid token; try again.";
break;
}
$r = rg_repo_edit($db, $rg['login_ui'], $rg['ri']);
if ($r === FALSE) {
$errmsg[] = rg_repo_error();
break;
}
$rg['ri']['home'] = rg_re_repopage($rg['login_ui'],
$rg['ri']['name']);
if ($edit) {
$ret .= rg_template("repo/edit_ok.html", $rg, TRUE /*xss*/);
} else {
$ret .= rg_template("repo/create_ok.html", $rg, TRUE /*xss*/);
}
$load_form = FALSE;
break;
}
if ($load_form) {
if ($rg['ri']['master'] > 0) {
$rg['ri']['master_name'] = $rg['ri']['master'];
$_mi = rg_repo_info($db, $rg['ri']['master'], "");
if ($_mi['exists'] == 1)
$rg['ri']['master_name'] = $_mi['name'];
} else {
$rg['ri']['master_name'] = "";
}
$rg['HTML:errmsg'] = rg_template_errmsg($errmsg);
$rg['rg_form_token'] = rg_token_get($db, $rg, 'repo_edit_hl');
$hints = array();
$hints[]['HTML:hint'] = rg_template("hints/repo/create_repo.html", $rg, TRUE /* xss */);
$rg['HTML:repo_edit_hints'] = rg_template_table("hints/list", $hints, $rg);
$ret .= rg_template("repo/add_edit.html", $rg, TRUE /* xss */);
}
rg_log_exit();
return $ret;
}
/*
* High level function for locking a repo
*/
function rg_repo_lock_high_level($db, &$rg)
{
rg_log_enter("rg_repo_lock_high_level");
$ret = "";
$errmsg = array();
$show_form = TRUE;
while (1) {
$x = array();
$x['obj_id'] = $rg['ri']['repo_id'];
$x['type'] = 'repo';
$x['owner'] = $rg['ri']['uid'];
$x['uid'] = $rg['login_ui']['uid'];
$x['username'] = $rg['login_ui']['username'];
$x['needed_rights'] = 'K';
$x['ip'] = $rg['ip'];
$x['misc'] = '';
if (rg_rights_allow($db, $x) !== TRUE) {
$ret .= rg_template("user/repo/deny_lock.html",
$rg, TRUE /*xss*/);
$show_form = FALSE;
break;
}
$ls = rg_repo_lock_status($db, $rg['ri']['repo_id']);
if ($ls['ok'] != 1)
break;
$rg['ri']['next_status'] = 1 - $ls['status'];
$rg['ri']['reason'] = rg_var_str('ri::reason');
$lock = rg_var_int('ri::lock');
rg_log('ZZZ: status=' . $ls['status']);
rg_log('ZZZ: lock=' . $lock);
if ($rg['doit'] != 1)
break;
if (!rg_valid_referer()) {
$errmsg[] = "invalid referer; try again";
break;
}
if (!rg_token_valid($db, $rg, 'repo_lock_hl', FALSE)) {
// TODO: replace all of these with a template
$errmsg[] = "invalid token; try again.";
break;
}
$r = rg_repo_lock($db, $rg['ri']['repo_id'],
$rg['login_ui']['uid'], $lock, $rg['ri']['reason']);
if ($r === FALSE) {
$errmsg[] = rg_repo_error();
break;
}
if ($lock == 1)
$t = 'repo/lock_ok.html';
else
$t = 'repo/unlock_ok.html';
$ret .= rg_template($t, $rg, TRUE /*xss*/);
// clean form
$rg['ri']['reason'] = '';
$rg['ri']['next_status'] = 1 - $lock;
break;
}
if ($show_form) {
$rg['HTML:errmsg'] = rg_template_errmsg($errmsg);
$hints = array();
$hints[]['HTML:hint'] = rg_template("hints/repo/lock_repo.html",
$rg, TRUE /*xss*/);
$rg['HTML:repo_lock_hints'] = rg_template_table("hints/list",
$hints, $rg);
$rg['rg_form_token'] = rg_token_get($db, $rg, 'repo_lock_hl');
$ret .= rg_template("repo/lock.html", $rg, TRUE /*xss*/);
}
rg_log_exit();
return $ret;
}
/*
* High level function for 'Repo -> Admin' menu
*/
function rg_repo_admin($db, &$rg, $paras)
{
rg_log("rg_repo_admin paras=" . rg_array2string($paras));
$ret = "";
$_op = empty($paras) ? "edit" : array_shift($paras);
$rg['allow_edit_repo'] = 0;
$rg['allow_lock_repo'] = 0;
$rg['allow_grant_rights'] = 0;
$rg['allow_delete_repo'] = 0;
$x = array();
$x['obj_id'] = $rg['ri']['repo_id'];
$x['type'] = 'repo';
$x['owner'] = $rg['ri']['uid'];
$x['uid'] = $rg['login_ui']['uid'];
$x['username'] = $rg['login_ui']['username'];
$x['ip'] = $rg['ip'];
$x['misc'] = '';
$x['needed_rights'] = 'E';
if (rg_rights_allow($db, $x) === TRUE)
$rg['allow_edit_repo'] = 1;
$x['needed_rights'] = 'K';
if (rg_rights_allow($db, $x) === TRUE)
$rg['allow_lock_repo'] = 1;
$x['needed_rights'] = 'G';
if (rg_rights_allow($db, $x) === TRUE)
$rg['allow_grant_rights'] = 1;
$x['needed_rights'] = 'D';
if (rg_rights_allow($db, $x) === TRUE)
$rg['allow_delete_repo'] = 1;
$rg['menu']['repo'][$_op] = 1;
$rg['HTML:menu_repo_level2'] = rg_template("user/repo/menu.html", $rg, TRUE /* xss */);
switch ($_op) {
case 'repo_rights':
$ret .= rg_repo_admin_rights($db, $rg, "repo");
break;
case 'refs_rights':
$ret .= rg_repo_admin_rights($db, $rg, "repo_refs");
break;
case 'path_rights':
$ret .= rg_repo_admin_rights($db, $rg, "repo_path");
break;
case 'delete':
$ret .= rg_repo_admin_delete($db, $rg);
break;
case 'lock':
$rg['form_url'] = $rg['ri']['url_repo'] . "/admin/lock";
$ret .= rg_repo_lock_high_level($db, $rg);
break;
default:
$rg['form_url'] = $rg['ri']['url_repo'] . "/admin";
$ret .= rg_repo_edit_high_level($db, $rg);
break;
}
return $ret;
}
/*
* HL function for repo search
*/
function rg_repo_search_high_level($db, $rg, $ui, $url)
{
$q = rg_var_str('q');
$ret = '';
$errmsg = array();
$rg['q'] = $q;
$rg['HTML:errmsg'] = rg_template_errmsg($errmsg);
$rg['search_url'] = $url;
$ret .= rg_template("repo/search.html", $rg, TRUE /* xss */);
while ($rg['doit'] == 1) {
$_t = rg_repo_search($db, $ui, $q);
if ($_t === FALSE) {
$errmsg[] = rg_repo_error();
break;
}
$ret .= $_t;
break;
}
return $ret;
}
/*
* Discover top menu
* @ui - act like login_ui
*/
function rg_repo_discover($db, $op, $rg, $ui)
{
rg_log("repo_discover: op=$op");
$ret = '';
if (empty($op))
$op = 'list';
switch ($op) {
case 'search':
$x = rg_repo_search_high_level($db, $rg, $ui, '/op/discover');
break;
default:
$x = rg_repo_list($db, $rg, '', $ui['uid']);
break;
}
$rg['discover_menu_' . $op] = 1;
$ret .= rg_template('repo/discover.html', $rg, TRUE /* xss */);
$ret .= '<div class="generic_body">' . "\n";
$ret .= $x;
$ret .= '</div>' . "\n";
return $ret;
}
/*
* Show some stats about a repo
*/
function rg_repo_stats($rg)
{
rg_log_enter('repo_start');
while (1) {
if ($rg['ri']['git_dir_done'] == 0) {
$ret = rg_template('repo/no_git_dir.html', $rg, TRUE /*xss*/);
break;
}
$path = rg_repo_path_by_id($rg['ri']['uid'], $rg['ri']['repo_id']);
$log = rg_git_log($path, 0 /*max*/, '', '', FALSE);
if ($log === FALSE) {
$ret = rg_template('internal_err.html', $rg, TRUE /*xss*/);
break;
}
$s = rg_git_stats($log);
//rg_log_ml('DEBUG: stats: ' . print_r($s, TRUE));
$rg['ri']['stats'] = $s['global'];
$rg['ri']['HTML:authors'] = rg_template_table('repo/stats/authors',
$s['authors'], $rg);
$ret = rg_template('repo/stats/stats.html', $rg, TRUE /*xss*/);
break;
}
rg_log_exit();
return $ret;
}
/*************************** commit_labels stuff */
/*
* Returns a list of labels attached to a commit
*/
function rg_repo_commit_labels($db, $repo_id, $head)
{
rg_prof_start('repo_commit_labels');
rg_log_enter('repo_commit_labels: repo_id='
. $repo_id . ' head=' . $head);
$ret = array();
$ret['ok'] = 0;
while(1) {
$key = 'repo_commit_labels' . '::' . $repo_id . '::' . $head;
$c = rg_cache_get($key);
if ($c !== FALSE) {
$ret = $c;
//rg_repo_cosmetic($db, $ret);
break;
}
$params = array(
'repo_id' => $repo_id,
'head' => $head
);
$sql = 'SELECT * FROM commit_labels'
. ' WHERE repo_id = @@repo_id@@'
. ' AND head = @@head@@';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
rg_repo_set_error('cannot query');
break;
}
$rows = rg_sql_num_rows($res);
$ret['list'] = array();
while (($row = rg_sql_fetch_array($res))) {
// Cosmetic
$row['labels'] = @unserialize($row['labels']);
$row['itime_nice'] = gmdate('Y-m-d H:i', $row['itime']);
$ret['list'][] = $row;
}
rg_sql_free_result($res);
$ret['ok'] = 1;
rg_cache_set($key, $ret, RG_SOCKET_NO_WAIT);
break;
}
//rg_log_ml('DEBUG: commit_labels: ' . print_r($ret, TRUE));
rg_log_exit();
rg_prof_end('repo_commit_labels');
return $ret;
}
/*
* Show commit labels as nice html
*/
function rg_repo_commit_labels_html($db, $repo_id, $commit)
{
rg_log_enter('repo_commit_labels_html');
$ret = '';
while (1) {
if (empty($commit))
break;
$rg = array();
$r = rg_repo_commit_labels($db, $repo_id, $commit);
//rg_log_ml('DEBUG: r: ' . print_r($r, TRUE));
if ($r['ok'] != 1) {
$ret = rg_template('repo/cl/ierror.html',
$rg, TRUE /*xss*/);
break;
}
if (empty($r['list']))
break;
foreach ($r['list'] as $index => &$i)
$i['labels_nice'] = implode(' ', $i['labels']);
$ret = rg_template_table('repo/cl/list', $r['list'], $rg);
break;
}
rg_log_exit();
return $ret;
}
/*
* API dispatch function
*/
function rg_repo_api($db, $a)
{
rg_prof_start('repo_api');
rg_log_enter('repo_api');
$cmd = $a['cmd'];
$ret = array();
while (1) {
if (isset($a['name'])) {
$ri = rg_repo_info($db, 0, $a['connect_uid'],
$a['name']);
} else if (isset($a['repo_id'])) {
$repo_id = intval($a['repo_id']);
$ri = rg_repo_info($db, $repo_id, '', '');
} else {
$ri = FALSE;
}
if (strcmp($cmd, 'repo_list') == 0) {
$params = array('uid' => $a['cui']['uid']);
$sql = 'SELECT name FROM repos'
. ' WHERE uid = @@uid@@';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
$ret['error'] = 'internal error';
break;
}
$ret = array();
while (($row = rg_sql_fetch_array($res))) {
$ret[] = $row['name'];
}
rg_sql_free_result($res);
break;
}
// From this point below, we have a repo
if ($ri === FALSE) {
$ret['error'] = 'no repo_id=/name= specified';
break;
}
if ($ri['exists'] != 1) {
$ret['error'] = 'invalid user or no rights';
break;
}
$x = array();
$x['obj_id'] = $ri['repo_id'];
$x['type'] = 'repo';
$x['owner'] = $ri['uid'];
$x['uid'] = $a['cui']['uid'];
$x['username'] = $a['cui']['username'];
$x['needed_rights'] = 'A'; // A = Access repo; E = create/edit
$x['ip'] = $a['ip'];
$x['misc'] = '';
if (strcmp($cmd, 'repo_info') == 0) {
if (rg_rights_allow($db, $x) !== TRUE) {
$ret['error'] = 'invalid user or no rights';
break;
}
$ret = $ri;
break;
}
if (strcmp($cmd, 'repo_pr_list') == 0) {
if (rg_rights_allow($db, $x) !== TRUE) {
$ret['error'] = 'invalid user or no rights';
break;
}
$params = array('repo_id' => $ri['repo_id']);
$sql = 'SELECT * FROM merge_requests'
. ' WHERE repo_id = @@repo_id@@';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
$ret['error'] = 'internal error';
break;
}
$ret = array();
while (($row = rg_sql_fetch_array($res))) {
$ret[] = $row;
}
rg_sql_free_result($res);
break;
}
if (strcmp($cmd, 'repo_bug_list') == 0) {
$y = $x;
$y['needed_rights'] = 'a'; // a = Access bug repo
if (rg_rights_allow($db, $y) !== TRUE) {
$ret['error'] = 'invalid user or no rights';
break;
}
$params = array('repo_id' => $ri['repo_id']);
$sql = 'SELECT bug_id FROM bugs'
. ' WHERE repo_id = @@repo_id@@';
$res = rg_sql_query_params($db, $sql, $params);
if ($res === FALSE) {
$ret['error'] = 'internal error';
break;
}
$ret = array();
while (($row = rg_sql_fetch_array($res))) {
$ret[] = $row['bug_id'];
}
rg_sql_free_result($res);
break;
}
// TODO: for bugs, we do layer violation here!
if (strcmp($cmd, 'repo_bug_info') == 0) {
if (!isset($a['bug_id'])) {
$ret['error'] = 'bug_id= para not specified';
break;
}
$bug_id = intval($a['bug_id']);
$y = $x;
$y['needed_rights'] = 'a'; // a = Access bug repo
if (rg_rights_allow($db, $y) !== TRUE) {
$ret['error'] = 'invalid user or no rights';
break;
}
$bi = rg_bug_info($db, $ri['repo_id'], $bug_id);
if ($bi['exists'] != 1) {
$ret['error'] = 'bug does not exists';
break;
}
$ret = $bi;
break;
}
$ret['error'] = 'invalid command';
break;
}
rg_log_exit();
rg_prof_end('repo_api');
return $ret;
}
/*
* Helper for SSH/GIT/HTTP(S) fetch/push
* @host - the host name accessed - may be different than the good one
* @ip - from where the connections come
* @cmd: 'git-upload-pack' or 'git-receive-pack'
* @need_namespace_copy - true if we need to clone refs
* TODO: move it to user.inc.php?!
*/
function rg_repo_fetch_push_helper($db, $host, $ip, $login_ui, $prefix, $user,
$repo, $cmd, $need_namespace_copy)
{
rg_prof_start('repo_fetch_push_helper');
rg_log_enter('repo_fetch_push_helper: host=' . $host
. ' prefix=' . $prefix . ' user=' . $user
. ' repo=' . $repo . ' cmd=' . $cmd
. ' need_namespace_copy=' . ($need_namespace_copy ? 'yes' : 'no'));
$ret = array('ok' => 0, 'allow' => 0, 'push_allowed' => 0);
while (1) {
// Extracts command and computes permissions
if (strncasecmp($cmd, 'git-upload-pack', 15) == 0) {
$cmd = 'git-upload-pack';
$needed_rights = 'F';
$ret['push'] = 0;
} else if (strncasecmp($cmd, 'git-receive-pack', 16) == 0) {
$cmd = 'git-receive-pack';
// We need push or anonymous push
$needed_rights = 'P|H';
$ret['push'] = 1;
} else {
$ret['error'] = 'unknown command';
break;
}
// Validity/security checks
// Load info about the owner
if (rg_user_ok($user) !== TRUE) {
$ret['error'] = 'user is invalid (' . rg_user_error() . ')';
break;
}
$ret['owner_ui'] = rg_user_info($db, 0, $user, '');
if ($ret['owner_ui']['ok'] != 1) {
$ret['error'] = 'internal problems; try again later, please';
break;
}
if ($ret['owner_ui']['exists'] != 1) {
$ret['error'] = 'user does not exists';
break;
}
// TODO: What if the user is deleted?
// Loading info about the repository
if (rg_repo_ok($repo) !== TRUE) {
$ret['error'] = 'repository is invalid ('
. rg_repo_error() . ')';
break;
}
$ret['ri'] = rg_repo_info($db, 0, $ret['owner_ui']['uid'], $repo);
if ($ret['ri']['ok'] != 1) {
$ret['error'] = 'internal problems; try again later, please';
break;
}
if ($ret['ri']['exists'] != 1) {
$ret['error'] = 'repository does not exists';
break;
}
if ($ret['ri']['deleted'] == 1) {
$ret['error'] = 'repository has been deleted';
break;
}
$ls = rg_repo_lock_status($db, $ret['ri']['repo_id']);
if ($ls['ok'] != 1) {
$ret['error'] = 'could not get lock status: ' . rg_repo_error();
break;
}
if (($ls['status'] == 1) && ($login_ui['uid'] != $ls['uid'])) {
$_u = rg_user_info($db, $ls['uid'], '', '');
if ($_u['exists'] == 1)
$_user = $_u['username'];
else
$_user = '?';
$ret['error'] = 'repository has been locked user ' . $_user
. ' at ' . gmdate('Y-m-d H:i', $ls['itime']) . ' UTC;'
. ' reason: ' . $ls['reason'];
break;
}
$repo_path = rg_repo_path_by_id($ret['owner_ui']['uid'],
$ret['ri']['repo_id']);
$ret['repo_path'] = $repo_path;
// TODO: signal user that the repo moved and provide a hint how to follow
$ret['ok'] = 1;
$x = array();
$x['obj_id'] = $ret['ri']['repo_id'];
$x['type'] = 'repo_refs';
$x['owner'] = $ret['ri']['uid'];
$x['uid'] = $login_ui['uid'];
$x['username'] = $login_ui['uid'] == 0 ? '' : $login_ui['username'];
$x['needed_rights'] = $needed_rights;
$x['ip'] = $ip;
$x['misc'] = '';
$r = rg_rights_allow($db, $x);
// TODO: what if an error occured? How we signal this?!
if ($r !== TRUE) {
$ret['error'] = 'non existing repo or you are not allowed to push';
break;
}
// We need to know if Push (and not anon) is allowed, so we can
// give the user a chance to authenticate.
// TODO: change rg_rights_allow to return what rights are
// allowed and use it.
if ($ret['push'] == 1) {
$x['needed_rights'] = 'P';
$r = rg_rights_allow($db, $x);
if ($r === TRUE)
$ret['push_allowed'] = 1;
}
// If we are enrolled, ask for login token
// For push we always ask for it, for fetch only if repo is
// NOT public. And we can ask only if we have a ssh connection.
// For git protocol we cannot because we do not have the
// username/uid of the connecting user.
if (isset($_SERVER['SSH_CONNECTION'])) {
if (($ret['ri']['public'] == 0) || ($ret['push'] == 1)) {
$r = rg_totp_verify_ip($db, $login_ui['uid'],
$ip);
if (($r['ok'] !== 1)
|| ($r['enrolled'] && empty($r['ip_list']))) {
$ret['error'] = rg_totp_error();
$ret['ok'] = 0;
break;
}
}
}
// TODO: limit per connection
// TODO: limit time and/or cpu
// TODO: limit cpuset
// TODO: limit io
// TODO: put process in a cgroup?
if (($ret['push'] == 1)
&& rg_user_over_limit($db, $ret['owner_ui'], $max)) {
// TODO: be aware that a push may mean a delete =>
// free space?!
// We should mark the repo over limit when we compute
// the disk space - same problem
$ret['error'] = 'cannot push: user is over limit'
. ' (' . $ret['owner_ui']['disk_used_mb'] . 'MiB >= '
. $max . 'MiB)';
break;
}
// We are allowed at this point
$ret['allow'] = 1;
// Put in environment all we need
putenv('ROCKETGIT_LOGIN_UID=' . $login_ui['uid']);
putenv('ROCKETGIT_LOGIN_URL=' . rg_re_userpage($login_ui));
putenv('ROCKETGIT_REPO_ID=' . $ret['ri']['repo_id']);
putenv('ROCKETGIT_REPO_PATH=' . $repo_path);
putenv('ROCKETGIT_REPO_NAME=' . $ret['ri']['name']);
putenv('ROCKETGIT_REPO_UID=' . $ret['ri']['uid']);
putenv('ROCKETGIT_REPO_CLONE_URL=' . $ret['ri']['clone_url_http']);
putenv('ROCKETGIT_IP=' . $ip);
putenv('ROCKETGIT_ITIME=' . microtime(TRUE));
putenv('ROCKETGIT_HOST=' . $host);
if (($ret['push'] == 1) && $need_namespace_copy) {
$namespace = 'rg_' . rg_id(8);
rg_log('namespace is ' . $namespace);
putenv('GIT_NAMESPACE=' . $namespace);
// Prepare refs to avoid the following message:
// 'No refs in common and none specified; doing nothing.
// Perhaps you should specify a branch such as 'master'.'
// TODO: cannot we copy only the pushed branch?!
$dst = $repo_path . '/refs/namespaces/' . $namespace . '/refs';
$r = rg_copy_tree($repo_path . '/refs/heads', $dst . '/heads/', 0700);
if ($r !== TRUE) {
$ret['error'] = 'internal error (cannot copy heads refs)';
break;
}
$r = rg_copy_tree($repo_path . '/refs/tags',
$dst . '/tags/', 0700);
if ($r !== TRUE) {
$ret['error'] =
'internal error (cannot copy tags refs)';
break;
}
}
// TODO: we may want to call here the git-shell cmd and remove
// it from remote.php?
$ret['ok'] = 1;
break;
}
rg_log_exit();
rg_prof_end('repo_fetch_push_helper');
return $ret;
}
?>