array($user->uid)), TRUE); if (empty($accounts)) { return; } $accounts_flat = array(); $repo_ids = array(); foreach ($accounts as $uid => $usernames_by_repository) { foreach ($usernames_by_repository as $repo_id => $username) { $accounts_flat[] = array('uid' => $uid, 'username' => $username, 'repo_id' => $repo_id); $repo_ids[] = $repo_id; } } $repositories = versioncontrol_get_repositories(array('repo_ids' => $repo_ids)); foreach ($accounts_flat as $account) { if (isset($repositories[$account['repo_id']])) { versioncontrol_delete_account( $repositories[$account['repo_id']], $account['uid'], $account['username'] ); } } return; } } /** * Implementation of hook_menu(). */ function versioncontrol_menu($may_cache) { global $user; $items = array(); $admin_access = user_access('administer version control systems'); $user_access = user_access('use version control systems') || $admin_access; if ($may_cache) { // If Version Control API is used without the Project module, // we need to define our own version of /admin/project // so the rest of our admin pages all work. if (!module_exists('project')) { $items[] = array( 'path' => 'admin/project', 'title' => t('Project administration'), 'description' => t('Administrative interface for project management and related modules.'), 'callback' => 'system_admin_menu_block_page', 'access' => $admin_access, 'type' => MENU_NORMAL_ITEM, ); } $items[] = array( 'path' => 'admin/project/versioncontrol-settings', 'title' => t('Version control settings'), 'description' => t('Configure settings for Version Control API and related modules.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('versioncontrol_admin_settings'), 'access' => $admin_access, 'type' => MENU_NORMAL_ITEM, ); $items[] = array( 'path' => 'admin/project/versioncontrol-settings/general', 'title' => t('General'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -1, ); $items[] = array( 'path' => 'admin/project/versioncontrol-repositories', 'title' => t('VCS repositories'), 'description' => t('Define and configure what version control repositories are connected to your site, and how to integrate each repository with repository browser tools such as ViewVC or WebSVN.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('versioncontrol_admin_repository_list'), 'access' => $admin_access, 'type' => MENU_NORMAL_ITEM, ); $items[] = array( 'path' => 'admin/project/versioncontrol-repositories/list', 'title' => t('List'), 'type' => MENU_DEFAULT_LOCAL_TASK, ); $items[] = array( 'path' => 'admin/project/versioncontrol-accounts', 'title' => t('VCS accounts'), 'description' => t('Manage associations of Drupal users to VCS user accounts.'), 'callback' => 'versioncontrol_admin_account_list_page', 'access' => $admin_access, 'type' => MENU_NORMAL_ITEM, ); $items[] = array( 'path' => 'admin/project/versioncontrol-accounts/list', 'title' => t('List'), 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 0, ); $items[] = array( 'path' => 'versioncontrol/register', // [."/$uid" [."/$repo_id"]] 'title' => t('Get commit access'), 'callback' => 'versioncontrol_account_register_page', 'access' => TRUE, // access checking is done in the callback 'type' => MENU_SUGGESTED_ITEM, ); // autocomplete callback for Drupal usernames that have access to // the repository given in arg(3). $items[] = array( 'path' => 'versioncontrol/user/autocomplete', // ."/$repo_id/$string" 'title' => t('Version control user autocomplete'), 'callback' => 'versioncontrol_user_autocomplete', 'access' => $user_access, 'type' => MENU_CALLBACK, ); } else { // Backend specific stuff is done in !$may_cache, as it once screwed up // after activating a new backend in admin/build/modules. $backends = versioncontrol_get_backends(); $i = 2; // weight of the local task, 'List' is 1. foreach ($backends as $vcs => $backend) { $items[] = array( 'path' => 'admin/project/versioncontrol-repositories/add-'. $vcs, 'title' => t('Add @vcs repository', array('@vcs' => $backend['name'])), 'callback' => 'drupal_get_form', 'callback arguments' => array('versioncontrol_admin_repository_edit', 0, $vcs), 'access' => $admin_access, 'type' => MENU_LOCAL_TASK, 'weight' => $i, ); ++$i; if (versioncontrol_backend_implements($vcs, 'import_accounts')) { $items[] = array( 'path' => 'admin/project/versioncontrol-accounts/import', 'title' => t('Import'), 'description' => t('Import an existing set of VCS user accounts.'), 'callback' => 'drupal_get_form', 'callback arguments' => array('versioncontrol_admin_account_import_form'), 'access' => $admin_access, 'type' => MENU_LOCAL_TASK, 'weight' => 2, ); } if (versioncontrol_backend_implements($vcs, 'export_accounts')) { $items[] = array( 'path' => 'admin/project/versioncontrol-accounts/export', 'title' => t('Export'), 'description' => t('Export VCS user accounts of a specific repository.'), 'callback' => 'versioncontrol_admin_account_export_page', 'access' => $admin_access, 'type' => MENU_LOCAL_TASK, 'weight' => 3, ); } } if (is_numeric(arg(4))) { $items[] = array( 'path' => 'admin/project/versioncontrol-repositories/edit/'. arg(4), 'title' => t('Edit repository'), 'callback' => 'drupal_get_form', 'callback arguments' => array('versioncontrol_admin_repository_edit', arg(4)), 'access' => $admin_access, 'type' => MENU_CALLBACK, ); $items[] = array( 'path' => 'admin/project/versioncontrol-repositories/delete/'. arg(4), 'title' => t('Delete repository'), 'callback' => 'drupal_get_form', 'callback arguments' => array('versioncontrol_admin_repository_delete_confirm', arg(4)), 'access' => $admin_access, 'type' => MENU_CALLBACK, ); } if (arg(0) == 'user' && is_numeric(arg(1)) && arg(2) == 'edit') { $items = array_merge($items, _versioncontrol_user_account_edit_items($user)); } } return $items; } /** * This needs to be split out of versioncontrol_menu() because user.module * only sets 'user/$uid/edit/account' as default local task if * hook_user($op='categories) returns more than one entry. Which means * we have to provide those separately from the menu hook. */ function _versioncontrol_user_account_edit_items($user, $op = 'items') { static $items; static $categories; $admin_access = user_access('administer version control systems'); $user_access = user_access('use version control systems') || $admin_access; $account_access = ($user_access && $uid == $user->uid) || $admin_access; // Cache the items, we don't want this being done twice. if (isset($items)) { if ($op == 'categories') { return $categories; } return $items; } $items = array(); $categories = array(); $uid = arg(1); $accounts = versioncontrol_get_accounts(array('uids' => array($uid)), TRUE); if (empty($accounts)) { $items = array(); $categories = array(); return array(); } $repo_ids = array(); foreach ($accounts as $uid => $usernames_by_repository) { foreach ($usernames_by_repository as $repo_id => $username) { $repo_ids[] = $repo_id; } } $repositories = versioncontrol_get_repositories(array('repo_ids' => $repo_ids)); // The form for editing an account. foreach ($accounts as $uid => $usernames_by_repository) { foreach ($usernames_by_repository as $repo_id => $username) { if (isset($repositories[$repo_id])) { $items[] = array( 'path' => 'user/'. $uid .'/edit/versioncontrol/'. $repo_id, 'title' => check_plain($repositories[$repo_id]['name']), 'callback' => 'drupal_get_form', 'callback arguments' => array( 'versioncontrol_account_edit_form', $uid, $username, $repositories[$repo_id] ), 'access' => $account_access, 'type' => MENU_LOCAL_TASK, 'weight' => 99, ); $categories[] = array( 'name' => 'versioncontrol/'. $repo_id, 'title' => check_plain($repositories[$repo_id]['name']), 'weight' => 99, ); } } } if ($op == 'categories') { return $categories; } return $items; } /** * Implementation of hook_perm(). */ function versioncontrol_perm() { return array( 'administer version control systems', 'use version control systems', ); } /** * Get a list of all backends and more detailed information about each of them. * * @return * A structured array containing information about all known backends. * Array keys are the unique string identifier of the version control system. * The corresponding array values are again structured arrays and consist * of elements with the following keys: * * - 'name': The user-visible name of the VCS. * - 'description': A short description of the backend, if possible * not longer than one or two sentences. * - 'capabilities': An array listing optional capabilities, in addition * to the required functionality like retrieval of detailed * commit information. Array values can be an arbitrary combination * of VERSIONCONTROL_CAPABILITY_* values. If no additional capabilities * are supported by the backend, this array will be empty. * - 'flags': An array listing which tables should be managed by * Version Control API instead of doing it manually in the backend. * Array values can be an arbitrary combination of VERSIONCONTROL_FLAG_* * values. If no array additions should be automatically managed, * this array will be empty. * * If no single backends can be found, an empty array is returned. * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_backends() { static $backends; if (!isset($backends)) { $backends = module_invoke_all('versioncontrol_backends'); } return $backends; } /** * Determine if a given backend module implements a specific backend function. * * @param $vcs * The unique string identifier of the version control system. * @param $function * The function name without module prefix. * * @return * TRUE if the backend implements the function, or FALSE otherwise. */ function versioncontrol_backend_implements($vcs, $function) { if (function_exists('versioncontrol_'. $vcs .'_'. $function)) { return TRUE; } return FALSE; } /** * Call a function from the desired VCS backend and return its result value. * * @param $vcs * The unique string identifier of the version control system. * @param $function * The function name without module prefix. * @param $args * An array of arguments that will be passed to the backend function. * * @return * Returns the result of the backend function. The result value of calls * where the backend function has no implementation is undefined, as they * are supposed to be checked with versioncontrol_backend_implements() * before those functions are called. */ function _versioncontrol_call_backend($vcs, $function, $args) { return call_user_func_array('versioncontrol_'. $vcs .'_'. $function, $args); } /** * Call a function from the desired VCS backend, return its result value, * and make sure that the @p $operations parameter (which is a reference) * can be altered by the backend. * * @param $vcs * The unique string identifier of the version control system. * @param $function * The function name without module prefix. * @param $operations * A modifiable operation array that will be passed to the backend function. * @param $constraints * An array of VCS specific filter constraints. * * @return * Returns the result of the backend function. The result value of calls * where the backend function has no implementation is undefined, as they * are supposed to be checked with versioncontrol_backend_implements() * before those functions are called. */ function _versioncontrol_call_backend_alter($vcs, $function, &$operations, $constraints = array()) { $function = 'versioncontrol_'. $vcs .'_'. $function; return $function($operations, $constraints); } /** * Determine all user account authorization methods * (free for all, only admin may create accounts, per-repository approval, ...) * by invoking hook_versioncontrol_authorization_methods(). * * @return * A structured array with the unique string identifier of the method as keys * and the user-visible description (wrapped in t()) as values. */ function versioncontrol_get_authorization_methods() { static $methods; if (!isset($methods)) { $methods = module_invoke_all('versioncontrol_authorization_methods'); } return $methods; } /** * Implementation of hook_versioncontrol_authorization_methods(). * * @return * A structured array containing information about authorization methods * provided by this module, wrapped in a structured array. Array keys are * the unique string identifiers of each authorization method, and * array values are the user-visible method descriptions (wrapped in t()). */ function versioncontrol_versioncontrol_authorization_methods() { return array( 'versioncontrol_admin' => t('Only administrators can create accounts'), 'versioncontrol_none' => t('No approval required'), ); } function _versioncontrol_get_fallback_authorization_method() { return 'versioncontrol_admin'; } /** * Convenience function for retrieving one single repository by repository id. * * @return * A single repository array that consists of the following elements: * * - 'repo_id': The unique repository id. * - 'name': The user-visible name of the repository. * - 'vcs': The unique string identifier of the version control system * that powers this repository. * - 'root': The root directory of the repository. In most cases, * this will be a local directory (e.g. '/var/repos/drupal'), * but it may also be some specialized string for remote repository * access. How this string may look like depends on the backend. * - 'authorization_method': The string identifier of the repository's * authorization method, that is, how users may register accounts * in this repository. Modules can provide their own methods * by implementing hook_versioncontrol_authorization_methods(). * - 'url_backend': The prefix (excluding the trailing underscore) * for URL backend retrieval functions. * - '[xxx]_specific': An array of VCS specific additional repository * information. How this array looks like is defined by the * corresponding backend module (versioncontrol_[xxx]). * * If no repository corresponds to the given repository id, NULL is returned. */ function versioncontrol_get_repository($repo_id) { $repos = versioncontrol_get_repositories(array('repo_ids' => array($repo_id))); foreach ($repos as $repo_id => $repository) { return $repository; } return NULL; // in case of empty($repos) } /** * Retrieve a set of repositories that match the given constraints. * * @param $constraints * An optional array of constraints. Possible array elements are: * * - 'vcs': An array of strings, like array('cvs', 'svn', 'git'). * If given, only repositories for these backends will be returned. * - 'repo_ids': An array of repository ids. * If given, only the corresponding repositories will be returned. * - 'names': An array of repository names, like * array('Drupal CVS', 'Experimental SVN'). If given, * only repositories with these repository names will be returned. * - '[xxx]_specific': An array of VCS specific constraints. How this array * looks like is defined by the corresponding backend module * (versioncontrol_[xxx]). Other backend modules won't get to see this * constraint, so in theory you can provide one of those for each backend * in one single query. * * @return * An array of repositories where the key of each element is the * repository id. The corresponding value contains a structured array * with the following keys: * * - 'repo_id': The unique repository id. * - 'name': The user-visible name of the repository. * - 'vcs': The unique string identifier of the version control system * that powers this repository. * - 'root': The root directory of the repository. In most cases, * this will be a local directory (e.g. '/var/repos/drupal'), * but it may also be some specialized string for remote repository * access. How this string may look like depends on the backend. * - 'authorization_method': The string identifier of the repository's * authorization method, that is, how users may register accounts * in this repository. Modules can provide their own methods * by implementing hook_versioncontrol_authorization_methods(). * - 'url_backend': The prefix (excluding the trailing underscore) * for URL backend retrieval functions. * - '[xxx]_specific': An array of VCS specific additional repository * information. How this array looks like is defined by the * corresponding backend module (versioncontrol_[xxx]). * * If not a single repository matches these constraints, * an empty array is returned. */ function versioncontrol_get_repositories($constraints = array()) { static $repository_cache = array(); $backends = versioncontrol_get_backends(); $auth_methods = versioncontrol_get_authorization_methods(); // "Normalize" repo_ids to integers so the cache doesn't distinguish // between string and integer values. if (isset($constraints['repo_ids'])) { $repo_ids = array(); foreach ($constraints['repo_ids'] as $repo_id) { $repo_ids[] = (int) $repo_id; } $constraints['repo_ids'] = $repo_ids; } $constraints_serialized = serialize($constraints); if (isset($repository_cache[$constraints_serialized])) { return $repository_cache[$constraints_serialized]; } list($and_constraints, $params) = _versioncontrol_construct_repository_constraints($constraints, $backends); // All the constraints have been gathered, assemble them to a WHERE clause. $where = empty($and_constraints) ? '' : ' WHERE '. implode(' AND ', $and_constraints); $result = db_query('SELECT * FROM {versioncontrol_repositories} r'. $where, $params); // Sort the retrieved repositories by backend. $repositories_by_backend = array(); while ($repository = db_fetch_array($result)) { if (!isset($backends[$repository['vcs']])) { // don't include repositories for which no backend module exists continue; } if (!isset($auth_methods[$repository['authorization_method']])) { $repository['authorization_method'] = _versioncontrol_get_fallback_authorization_method(); } if (!isset($repositories_by_backend[$repository['vcs']])) { $repositories_by_backend[$repository['vcs']] = array(); } $repository[$repository['vcs'] .'_specific'] = array(); $repositories_by_backend[$repository['vcs']][$repository['repo_id']] = $repository; } $repositories_by_backend = _versioncontrol_amend_repositories( $repositories_by_backend, $backends ); // Add the fully assembled repositories to the result array. $result_repositories = array(); foreach ($repositories_by_backend as $vcs => $vcs_repositories) { foreach ($vcs_repositories as $repository) { $result_repositories[$repository['repo_id']] = $repository; } } $repository_cache[$constraints_serialized] = $result_repositories; // cache the results return $result_repositories; } /** * Assemble a list of query constraints given as string array that's * supposed to be imploded with an SQL "AND", and a $params array containing * the corresponding parameter values for all the '%d' and '%s' placeholders. */ function _versioncontrol_construct_repository_constraints($constraints, $backends) { $and_constraints = array(); $params = array(); // Filter out repositories of which the corresponding backend is not enabled, // and handle the 'vcs' constraint at the same time. $placeholders = array(); $vcses = array_keys($backends); if (isset($constraints['vcs'])) { $vcses = array_intersect($vcses, $constraints['vcs']); } if (empty($vcses)) { $and_constraints[] = 'FALSE'; // no backends are enabled of those that have been requested } else { foreach ($vcses as $vcs) { $placeholders[] = "'%s'"; $params[] = $vcs; } $and_constraints[] = 'r.vcs IN ('. implode(',', $placeholders) .')'; } if (isset($constraints['repo_ids'])) { if (empty($constraints['repo_ids'])) { $and_constraints[] = 'FALSE'; } else { $placeholders = array(); foreach ($constraints['repo_ids'] as $repo_id) { $placeholders[] = '%d'; $params[] = $repo_id; } $and_constraints[] = 'r.repo_id IN ('. implode(',', $placeholders) .')'; } } if (isset($constraints['names'])) { if (empty($constraints['names'])) { $and_constraints[] = 'FALSE'; } else { $placeholders = array(); foreach ($constraints['names'] as $name) { $placeholders[] = "'%s'"; $params[] = $name; } $and_constraints[] = 'r.name IN ('. implode(',', $placeholders) .')'; } } return array($and_constraints, $params); } /** * Fetch VCS specific repository data additions, either by ourselves (if the * VERSIONCONTROL_FLAG_AUTOADD_REPOSITORIES flag has been set by the backend) * and/or by calling [vcs_backend]_alter_repositories(). */ function _versioncontrol_amend_repositories($repositories_by_backend, $backends, $constraints = array()) { foreach ($repositories_by_backend as $vcs => $vcs_repositories) { $is_autoadd = in_array(VERSIONCONTROL_FLAG_AUTOADD_REPOSITORIES, $backends[$vcs]['flags']); if ($is_autoadd) { $repo_ids = array(); foreach ($vcs_repositories as $repo_id => $repository) { $repo_ids[] = $repo_id; } $additions = _versioncontrol_db_get_additions( 'versioncontrol_'. $vcs .'_repositories', 'repo_id', $repo_ids ); foreach ($additions as $repo_id => $addition) { if (isset($vcs_repositories[$repo_id])) { $vcs_repositories[$repo_id][$vcs .'_specific'] = $addition; } } } $vcs_specific_constraints = isset($constraints[$vcs .'_specific']) ? $constraints[$vcs .'_specific'] : array(); // Provide an opportunity for the backend to add its own stuff. if (versioncontrol_backend_implements($vcs, 'alter_repositories')) { _versioncontrol_call_backend_alter( $vcs, 'alter_repositories', $vcs_repositories, $vcs_specific_constraints ); } $repositories_by_backend[$vcs] = $vcs_repositories; } return $repositories_by_backend; } /** * Retrieve a set of commits that match the given constraints. * * @param $constraints * An optional array of constraints. Possible array elements are: * * - 'vcs': An array of strings, like array('cvs', 'svn', 'git'). * If given, only commits for these backends will be returned. * - 'repo_ids': An array of repository ids. If given, only commits * for the corresponding repositories will be returned. * - 'branches': An array of strings, specified separately for each * repository, like array($repo_id => array('HEAD', 'DRUPAL-5')). * If given, only commits on the given branches will be returned. * - 'paths': An array of strings (item locations), like * array( * '/trunk/contributions/modules/versioncontrol', * '/trunk/contributions/themes/b2', * ). * If given, only commits that affected this item (or its children, * in case the item is a directory) will be returned. * - 'vc_op_ids': An array of operation ids. If given, only commits matching * those ids will be returned. * - 'date_lower': A Unix timestamp. If given, the result set will not * contain commits that were committed earlier than this lower bound. * - 'date_upper': A Unix timestamp. If given, the result set will not * contain commits that were committe later later than this upper bound. * - 'uids': An array of Drupal user ids. If given, the result set will only * contain commits that correspond to any of the specified users. * - 'usernames': An array of system-specific usernames (the ones that the * version control systems themselves get to see), like * array('dww', 'jpetso'). If given, the result set will only * contain commits that correspond to any of the specified users. * - '[xxx]_specific': An array of VCS specific constraints. How this array * looks like is defined by the corresponding backend module * (versioncontrol_[xxx]). Other backend modules won't get to see * this constraint, so in theory you can provide one of those for * each backend in one single query. * * @param $result_count * This variable will be set to the overall number of commits that matched * the constraints. You're likely to need this if you use paging, in all * other cases this will be the same value as count($commits). * @param $page * If given, this function only return a subset of the result, where * $page == 0 would return the first $limit matching commits, * $page == 1 would return the second set, and so on. * @param $limit * The number of query results to return per page. * * @return * An array of commits, reversely sorted by the time of the commit. * Each element contains a structured array with the 'vc_op_id' identifier * as key (which doesn't influence the sorting) and the following keys: * * - 'vc_op_id': The Drupal-specific operation identifier (a simple integer) * which is unique among all operations (commits, branch ops, tag ops) * in all repositories. * - 'type': The type of the operation, which is * VERSIONCONTROL_OPERATION_COMMIT for commits. * - 'repository': The repository where this commit occurred. * This is a structured array, like a single element of * what is returned by versioncontrol_get_repositories(). * - 'date': The time when the revision was committed, * given as Unix timestamp. * - 'uid': The Drupal user id of the committer, or 0 if no Drupal user * could be associated to the committer. * - 'username': The system specific VCS username of the committer. * - 'directory': The deepest-level directory in the repository that is * common to all the changed items, e.g. '/src' if the commit changed * the files '/src/subdir/code.php' and '/src/README.txt'. * - 'message': The commit message. * - 'revision': The VCS specific repository-wide revision identifier, * like '' in CVS, '27491' in Subversion or some SHA-1 key in various * distributed version control systems. If there is no such revision * (which may be the case for version control systems that don't support * atomic commits) then the 'revision' element is an empty string. * - '[xxx]_specific': An array of VCS specific additional commit information. * How this array looks like is defined by the corresponding * backend module (versioncontrol_[xxx]). * * If not a single commit matches these constraints, * an empty array is returned. */ function versioncontrol_get_commits($constraints = array(), &$result_count = NULL, $page = NULL, $limit = 10) { $backends = versioncontrol_get_backends(); list($and_constraints, $params) = _versioncontrol_construct_commit_constraints($constraints, $backends); $where = empty($and_constraints) ? '' : ' WHERE '. implode(' AND ', $and_constraints); $result = db_query('SELECT op.vc_op_id, op.date, op.uid, op.username, op.directory, c.message, c.revision, r.repo_id, r.vcs FROM {versioncontrol_commits} c INNER JOIN {versioncontrol_operations} op ON c.vc_op_id = op.vc_op_id INNER JOIN {versioncontrol_repositories} r ON op.repo_id = r.repo_id '. $where .' ORDER BY op.date DESC', $params); $commits = array(); $op_ids_by_backend = array(); $repo_ids = array(); $selected_backends = array(); while ($row = db_fetch_object($result)) { if (!isset($selected_backends[$row->vcs])) { $selected_backends[$row->vcs] = $backends[$row->vcs]; } if (!isset($op_ids_by_backend[$row->vcs])) { $op_ids_by_backend[$row->vcs] = array(); } $op_ids_by_backend[$row->vcs][] = $row->vc_op_id; if (!in_array($row->repo_id, $repo_ids)) { $repo_ids[] = $row->repo_id; } $commits[$row->vc_op_id] = array( 'vc_op_id' => $row->vc_op_id, 'type' => VERSIONCONTROL_OPERATION_COMMIT, 'repo_id' => $row->repo_id, // 'repo_id' is replaced by 'repository' further down 'date' => $row->date, 'uid' => $row->uid, 'username' => $row->username, 'directory' => $row->directory, 'message' => $row->message, 'revision' => $row->revision, $row->vcs .'_specific' => array(), ); } if (empty($commits)) { return array(); } $repositories = versioncontrol_get_repositories(array('repo_ids' => $repo_ids)); foreach ($selected_backends as $vcs => $backend) { $is_autoadd = in_array(VERSIONCONTROL_FLAG_AUTOADD_COMMITS, $backend['flags']); _versioncontrol_amend_operations($commits, $op_ids_by_backend, $repositories, $vcs, $constraints, 'commits', $is_autoadd); } _versioncontrol_filter_commits($commits, $constraints); $result_count = count($commits); if (isset($page)) { return _versioncontrol_page_operations($commits, $page, $limit); } return $commits; } /** * Assemble a list of query constraints given as string array that's * supposed to be imploded with an SQL "AND", and a $params array containing * the corresponding parameter values for all the '%d' and '%s' placeholders. */ function _versioncontrol_construct_commit_constraints($constraints, $backends) { // Filter by a set of common constraints whose code is shared with the // branch and tag retrieval functions. list($and_constraints, $params) = _versioncontrol_construct_common_constraints($constraints, $backends); // Filter by item paths. if (isset($constraints['paths'])) { if (empty($constraints['paths'])) { $and_constraints[] = 'FALSE'; } else { $or_constraints = array(); foreach ($constraints['paths'] as $path) { list($path_or_constraints, $path_params) = _versioncontrol_construct_path_constraint('op.directory', $path); $or_constraints = array_merge($or_constraints, $path_or_constraints); $params = array_merge($params, $path_params); } if (!empty($or_constraints)) { $and_constraints[] = '('. implode(' OR ', $or_constraints) .')'; } } } return array($and_constraints, $params); } /** * Assemble a list of SQL "AND" constraints that both the commit and * the branch/tag retrieval functions make use of. */ function _versioncontrol_construct_common_constraints($constraints, $backends, $operations_table_alias = 'op', $repository_table_alias = 'r') { $and_constraints = array(); $params = array(); // Filter out entries of which the corresponding backend is not enabled, // and handle the 'vcs' constraint at the same time. $placeholders = array(); $vcses = array_keys($backends); if (isset($constraints['vcs'])) { $vcses = array_intersect($vcses, $constraints['vcs']); } if (empty($vcses)) { // No backends are enabled of those that have been requested. $and_constraints = array('FALSE'); return array($and_constraints, $params); } else { foreach ($vcses as $vcs) { $placeholders[] = "'%s'"; $params[] = $vcs; } $and_constraints[] = $repository_table_alias .'.vcs IN ('. implode(',', $placeholders) .')'; } // Filter by commit, branch or tag ids. if (isset($constraints['vc_op_ids'])) { if (empty($constraints['vc_op_ids'])) { $and_constraints = array('FALSE'); return array($and_constraints, $params); } else { $placeholders = array(); foreach ($constraints['vc_op_ids'] as $op_id) { $placeholders[] = '%d'; $params[] = $op_id; } if (!empty($placeholders)) { $and_constraints[] = $operations_table_alias .'.vc_op_id IN ('. implode(',', $placeholders) .')'; } } } // Filter by repository ids. if (isset($constraints['repo_ids'])) { if (empty($constraints['repo_ids'])) { $and_constraints = array('FALSE'); return array($and_constraints, $params); } else { $placeholders = array(); foreach ($constraints['repo_ids'] as $repo_id) { $placeholders[] = '%d'; $params[] = $repo_id; } if (!empty($placeholders)) { $and_constraints[] = $repository_table_alias .'.repo_id IN ('. implode(',', $placeholders) .')'; } } } // Filter by lower and/or upper date bounds. if (isset($constraints['date_lower'])) { $and_constraints[] = '('. $operations_table_alias .'.date >= %d)'; $params[] = $constraints['date_lower']; } if (isset($constraints['date_upper'])) { $and_constraints[] = '('. $operations_table_alias .'.date <= %d)'; $params[] = $constraints['date_upper']; } // Filter by Drupal user ids. if (isset($constraints['uids'])) { if (empty($constraints['uids'])) { $and_constraints = array('FALSE'); return array($and_constraints, $params); } else { $placeholders = array(); foreach ($constraints['uids'] as $uid) { $placeholders[] = '%d'; $params[] = $uid; } if (!empty($placeholders)) { $and_constraints[] = $operations_table_alias .'.uid IN ('. implode(',', $placeholders) .')'; } } } // Filter by VCS specific user names. if (isset($constraints['usernames'])) { if (empty($constraints['usernames'])) { $and_constraints = array('FALSE'); return array($and_constraints, $params); } else { $placeholders = array(); foreach ($constraints['usernames'] as $username) { $placeholders[] = "'%s'"; $params[] = $username; } if (!empty($placeholders)) { $and_constraints[] = $operations_table_alias .'.username IN ('. implode(',', $placeholders) .')'; } } } return array($and_constraints, $params); } /** * Construct an SQL constraint with all possible values for the path column * given a path from $constraints['paths']. */ function _versioncontrol_construct_path_constraint($column, $path) { $or_constraints = array(); $paths = array(); $current_path = $path; // Now, we loop over the path, using the most restrictive path first, // and create a condition for each possible path. while (TRUE) { $or_constraints[] = $column ." = '%s'"; $paths[] = $current_path; if ($current_path == dirname($current_path)) { break; // we reached the top-level directory } $current_path = dirname($current_path); } // Also include any children paths of the given one. $or_constraints[] = $column ." LIKE '%s'"; $paths[] = $path . (($path[strlen($path)-1] == '/') ? '%' : '/%'); return array($or_constraints, $paths); } /** * Code that is shared between the commit and branch/tag retrieval functions: * Fetch VCS specific operation data additions, either by ourselves * (if $is_autoadd == TRUE) and/or by calling [vcs_backend]_alter_$type(). * This is called once for each VCS backend. */ function _versioncontrol_amend_operations(&$operations, $op_ids_by_backend, $repositories, $vcs, $constraints, $type, $is_autoadd) { foreach ($op_ids_by_backend[$vcs] as $op_id) { $operations[$op_id]['repository'] = $repositories[$operations[$op_id]['repo_id']]; unset($operations[$op_id]['repo_id']); } if ($is_autoadd) { $additions = _versioncontrol_db_get_additions( // e.g. 'versioncontrol_fakevcs_commits' or 'versioncontrol_cvs_tag_operations' 'versioncontrol_'. $vcs .'_'. $type, 'vc_op_id', $op_ids_by_backend[$vcs] ); // Apply the additions to their respective commit/tag/branch operations. foreach ($additions as $op_id => $addition) { $operations[$op_id][$vcs .'_specific'] = $addition; } } $vcs_specific_constraints = isset($constraints[$vcs .'_specific']) ? $constraints[$vcs .'_specific'] : NULL; // Provide an opportunity for the backend to add its own stuff. // Calls [xxx]_alter_commits(), [xxx]_alter_branch_operations() // or [xxx]_alter_tag_operations(). if (versioncontrol_backend_implements($vcs, 'alter_'. $type)) { _versioncontrol_call_backend_alter( $vcs, 'alter_'. $type, $operations, $vcs_specific_constraints ); } } /** * Filter out false posivites that could not be filtered by the SQL query. */ function _versioncontrol_filter_commits(&$commits, $constraints) { foreach ($commits as $key => $commit) { // First, branches. That's easier. if (isset($constraints['branches'])) { $branches = versioncontrol_get_commit_branches($commit); $valid_branch = FALSE; foreach ($constraints['branches'] as $branch) { if (in_array($branch, $branches)) { $valid_branch = TRUE; break; } } if (!$valid_branch) { // the commit doesn't match the 'branches' constraint, // don't include it in the result value. unset($commits[$key]); } } // Then, paths. More complicated, but doable. if (isset($constraints['paths'])) { $valid_path = FALSE; foreach ($constraints['paths'] as $path) { // Append a trailing slash to the commit and constraint directories. $commit_directory = ($commit['directory'] == '/') ? '/' : $commit['directory'] .'/'; if ($path[strlen($path)-1] != '/') { $path .= '/'; } // Check if $commit_directory is at least as deep as $path. if (strpos($commit_directory, $path) !== FALSE) { $valid_path = TRUE; break; } } if (!$valid_path) { // Bad luck, we need to check on the single item paths // as the common directory is too high-level to tell. $actions = versioncontrol_get_commit_actions($commit); foreach ($actions as $action) { $items = isset($action['current item']) ? array($action['current item']) : array(); $items = isset($action['source items']) ? array_merge($items, $action['source items']) : $items; foreach ($items as $item) { foreach ($constraints['paths'] as $path) { if ($path[strlen($path)-1] != '/') { $path .= '/'; } if (strpos($item['path'] .'/', $path) !== FALSE) { // The item is the same or a child element of the path constraint $valid_path = TRUE; break; } } } if ($valid_path) { break; } } } if (!$valid_path) { // the commit doesn't match the 'paths' constraint, // don't include it in the result value. unset($commits[$key]); } } } } /** * Return a subset of the given operations according to the * $page and $limit values. */ function _versioncontrol_page_operations($operations, $page, $limit) { $i = 0; $paged_operations = array(); foreach ($operations as $operation) { if ($i >= ($page * $limit) && $i < (($page+1) * $limit)) { $paged_operations[] = $operation; } ++$i; } return $paged_operations; } /** * Convenience function for retrieving one single branch operation * by operation id. * * @return * A single branch operation array that consists of the following elements: * * - 'vc_op_id': The Drupal-specific operation identifier (a simple integer) * which is unique among all operations (commits, branch ops, tag ops) * in all repositories. The same one that was given as parameter. * - 'type': The type of the operation, which is * VERSIONCONTROL_OPERATION_BRANCH for branches. * - 'branch_name': The name of the target branch * (a string like 'DRUPAL-6--1'). * - 'action': Specifies what happened to the branch. This is * VERSIONCONTROL_ACTION_ADDED if the branch was created, * VERSIONCONTROL_ACTION_MOVED if was renamed, * or VERSIONCONTROL_ACTION_DELETED if was deleted. * - 'date': The time when the branching was done, given as Unix timestamp. * - 'uid': The Drupal user id of the committer, or 0 if no Drupal user * could be associated to the committer. * - 'username': The system specific VCS username of the committer. * - 'repository': The repository where the branching occurred, * given as a structured array, like the return value * of versioncontrol_get_repository(). * - 'directory': The deepest-level directory in the repository that is * common to all the branched items, e.g. '/src' if the files * '/src/subdir/code.php' and '/src/README.txt' were branched. * - '[xxx]_specific': An array of VCS specific additional branch operation * info. How this array looks like is defined by the corresponding * backend module (versioncontrol_[xxx]). The 'branch_id' element will * already be included in this array for the convenience of VCS backends. * * If no branch operation corresponds to the given operation id, * NULL is returned. */ function versioncontrol_get_branch_operation($vc_op_id) { $branch_ops = versioncontrol_get_branch_operations( array('vc_op_ids' => array($vc_op_id)) ); foreach ($branch_ops as $vc_op_id => $branch_op) { return $branch_op; } return NULL; // in case of empty($branch_ops) } /** * Convenience function for retrieving one single tag operation by operation id. * * @return * A single tag operation array that consists of the following elements: * * - 'vc_op_id': The Drupal-specific operation identifier (a simple integer) * which is unique among all operations (commits, branch ops, tag ops) * in all repositories. The same one that was given as parameter. * - 'type': The type of the operation, which is * VERSIONCONTROL_OPERATION_TAG for tags. * - 'tag_name': The name of the tag (a string like 'DRUPAL-6--1-1'). * - 'action': Specifies what happened to the tag. This is * VERSIONCONTROL_ACTION_ADDED if the tag was created, * VERSIONCONTROL_ACTION_MOVED if was renamed, * or VERSIONCONTROL_ACTION_DELETED if was deleted. * - 'date': The time when the tagging was done, given as Unix timestamp. * - 'uid': The Drupal user id of the committer, or 0 if no Drupal user * could be associated to the committer. * - 'username': The system specific VCS username of the committer. * - 'repository': The repository where the tagging occurred, * given as a structured array, like the return value * of versioncontrol_get_repository(). * - 'directory': The deepest-level directory in the repository that is * common to all the tagged items, e.g. '/src' if the files * '/src/subdir/code.php' and '/src/README.txt' were tagged. * - 'message': The tag message that the user has given. If the version * control system doesn't support tag messages, this is an empty string. * - '[xxx]_specific': An array of VCS specific additional tag operation info. * How this array looks like is defined by the corresponding * backend module (versioncontrol_[xxx]). * * If no tag operation corresponds to the given operation id, * NULL is returned. */ function versioncontrol_get_tag_operation($vc_op_id) { $tag_ops = versioncontrol_get_tag_operations( array('vc_op_ids' => array($vc_op_id)) ); foreach ($tag_ops as $vc_op_id => $tag_op) { return $tag_op; } return NULL; // in case of empty($tag_ops) } /** * Retrieve a set of branch operations that match the given constraints. * (Branch operation means the stuff that happens when commands like * 'cvs tag -b' or 'svn cp /trunk/project /branches/project/2.x' are executed.) * * @param $constraints * An optional array of constraints. Possible array elements are: * * - 'repo_ids': An array of repository ids. If given, only branch operations * for the corresponding repositories will be returned. * - 'vcs': An array of strings, like array('cvs', 'svn', 'git'). * If given, only branches for these backends will be returned. * - 'branch_names': An array of branch names. If given, only * branch operations with these names will be returned. * - 'vc_op_ids': An array of operation ids. If given, only * branch operations matching those ids will be returned. * - 'date_lower': A Unix timestamp. If given, the result set will not * contain branch operations earlier than this lower bound. * - 'date_upper': A Unix timestamp. If given, the result set will not * contain branch operations later than this upper bound. * - 'uids': An array of Drupal user ids. If given, the result set will only * contain branch operations that correspond to any of the * specified users. * - 'usernames': An array of system-specific usernames (the ones that the * version control systems themselves get to see), like * array('dww', 'jpetso'). If given, the result set will only contain * branch operations that correspond to any of the specified users. * - '[xxx]_specific': An array of VCS specific constraints. How this array * looks like is defined by the corresponding backend module * (versioncontrol_[xxx]). Other backend modules won't get to see * thisconstraint, so in theory you can provide one of those * for each backend in one single query. * * @param $result_count * This variable will be set to the overall number of branch operations that * matched the constraints. You're likely to need this if you use paging, * in all other cases this will be the same value as count($branch_ops). * @param $page * If given, this function only return a subset of the result, where * $page == 0 would return the first $limit matching branch operations, * $page == 1 would return the second set, and so on. * @param $limit * The number of query results to return per page. * * @return * An array of branch operations, reversely sorted by the time of the branching. * Each element contains a structured array with the 'vc_op_id' identifier * as key (which doesn't influence the sorting) and the following keys: * * - 'vc_op_id': The Drupal-specific operation identifier (a simple integer) * which is unique among all operations (commits, branch ops, tag ops) * in all repositories. * - 'type': The type of the operation, which is * VERSIONCONTROL_OPERATION_BRANCH for branches. * - 'branch_name': The name of the target branch * (a string like 'DRUPAL-6--1'). * - 'action': Specifies what happened to the branch. This is * VERSIONCONTROL_ACTION_ADDED if the branch was created, * VERSIONCONTROL_ACTION_MOVED if was renamed, * or VERSIONCONTROL_ACTION_DELETED if was deleted. * - 'date': The time when the branching was done, given as Unix timestamp. * - 'uid': The Drupal user id of the committer, or 0 if no Drupal user * could be associated to the committer. * - 'username': The system specific VCS username of the committer. * - 'repository': The repository where the branching occurred, * given as a structured array, like the return value * of versioncontrol_get_repository(). * - 'directory': The deepest-level directory in the repository that is * common to all the branched items, e.g. '/src' if the files * '/src/subdir/code.php' and '/src/README.txt' were branched. * - '[xxx]_specific': An array of VCS specific additional branch operation * info. How this array looks like is defined by the corresponding * backend module (versioncontrol_[xxx]). The 'branch_id' element will * already be included in this array for the convenience of VCS backends. * * If not a single branch operation matches these constraints, * an empty array is returned. */ function versioncontrol_get_branch_operations($constraints = array(), &$result_count = NULL, $page = NULL, $limit = 10) { $backends = versioncontrol_get_backends(); // Filter by a set of common constraints whose code is shared with the // commit and tag retrieval functions. list($and_constraints, $params) = _versioncontrol_construct_common_constraints($constraints, $backends); // Filter by branch names. if (isset($constraints['branch_names'])) { if (empty($constraints['branch_names'])) { $and_constraints[] = 'FALSE'; } else { $or_constraints = array(); foreach ($constraints['branch_names'] as $branch_name) { $or_constraints[] = "b.branch_name = '%s'"; $params[] = $branch_name; } if (!empty($or_constraints)) { $and_constraints[] = '('. implode(' OR ', $or_constraints) .')'; } } } $where = empty($and_constraints) ? '' : ' WHERE '. implode(' AND ', $and_constraints); $result = db_query('SELECT op.vc_op_id, op.date, op.uid, op.username, op.directory, b.branch_name, b.branch_id, bo.action, r.repo_id, r.vcs FROM {versioncontrol_branch_operations} bo INNER JOIN {versioncontrol_operations} op ON bo.vc_op_id = op.vc_op_id INNER JOIN {versioncontrol_branches} b ON bo.branch_id = b.branch_id INNER JOIN {versioncontrol_repositories} r ON op.repo_id = r.repo_id '. $where .' ORDER BY op.date DESC', $params); $branches = array(); $op_ids_by_backend = array(); $repo_ids = array(); $selected_backends = array(); while ($row = db_fetch_object($result)) { if (!isset($selected_backends[$row->vcs])) { $selected_backends[$row->vcs] = $backends[$row->vcs]; } if (!isset($op_ids_by_backend[$row->vcs])) { $op_ids_by_backend[$row->vcs] = array(); } $op_ids_by_backend[$row->vcs][] = $row->vc_op_id; if (!in_array($row->repo_id, $repo_ids)) { $repo_ids[] = $row->repo_id; } $branches[$row->vc_op_id] = array( 'vc_op_id' => $row->vc_op_id, 'type' => VERSIONCONTROL_OPERATION_BRANCH, 'repo_id' => $row->repo_id, // 'repo_id' is replaced by 'repository' further down 'branch_name' => $row->branch_name, 'action' => $row->action, 'date' => $row->date, 'uid' => $row->uid, 'username' => $row->username, 'directory' => $row->directory, $row->vcs .'_specific' => array( 'branch_id' => $row->branch_id, ), ); } if (empty($branches)) { return array(); } $repositories = versioncontrol_get_repositories(array('repo_ids' => $repo_ids)); foreach ($selected_backends as $vcs => $backend) { $is_autoadd = in_array(VERSIONCONTROL_FLAG_AUTOADD_BRANCH_OPERATIONS, $backend['flags']); _versioncontrol_amend_operations($branches, $op_ids_by_backend, $repositories, $vcs, $constraints, 'branch_operations', $is_autoadd); } $result_count = count($branches); if (isset($page)) { return _versioncontrol_page_operations($branches, $page, $limit); } return $branches; } /** * Retrieve a set of tag operations that match the given constraints. * (Tag operation means the stuff that happens when commands like 'cvs tag' * or 'svn cp /branches/project/2.x /tags/project/2.x/2.2' are executed.) * * @param $constraints * An optional array of constraints. Possible array elements are: * * - 'repo_ids': An array of repository ids. If given, only tag operations * for the corresponding repositories will be returned. * - 'vcs': An array of strings, like array('cvs', 'svn', 'git'). * If given, only tags for these backends will be returned. * - 'tag_names': An array of tag names. If given, only tag operations * with these names will be returned. * - 'vc_op_ids': An array of operation ids. If given, * only tag operations matching those ids will be returned. * - 'date_lower': A Unix timestamp. If given, the result set will not * contain tag operations earlier than this lower bound. * - 'date_upper': A Unix timestamp. If given, the result set will not * contain tag operations later than this upper bound. * - 'uids': An array of Drupal user ids. If given, the result set will only * contain tag operations that correspond to any of the specified users. * - 'usernames': An array of system-specific usernames (the ones that the * version control systems themselves get to see), like * array('dww', 'jpetso'). If given, the result set will only contain * tag operations that correspond to any of the specified users. * - '[xxx]_specific': An array of VCS specific constraints. How this array * looks like is defined by the corresponding backend module * (versioncontrol_[xxx]). Other backend modules won't get to see * this constraint, so in theory you can provide one of those * for each backend in one single query. * * @param $result_count * This variable will be set to the overall number of tag operations that * matched the constraints. You're likely to need this if you use paging, * in all other cases this will be the same value as count($tag_ops). * @param $page * If given, this function only return a subset of the result, where * $page == 0 would return the first $limit matching tag operations, * $page == 1 would return the second set, and so on. * @param $limit * The number of query results to return per page. * * @return * An array of tag operations, reversely sorted by the time of the tagging. * Each element contains a structured array with the 'vc_op_id' identifier * as key (which doesn't influence the sorting) and the following keys: * * - 'vc_op_id': The Drupal-specific operation identifier (a simple integer) * which is unique among all operations (commits, branch ops, tag ops) * in all repositories. * - 'type': The type of the operation, which is * VERSIONCONTROL_OPERATION_TAG for tags. * - 'tag_name': The name of the tag (a string like 'DRUPAL-6--1-1'). * - 'action': Specifies what happened to the tag. This is * VERSIONCONTROL_ACTION_ADDED if the tag was created, * VERSIONCONTROL_ACTION_MOVED if was renamed, * or VERSIONCONTROL_ACTION_DELETED if was deleted. * - 'date': The time when the tagging was done, given as Unix timestamp. * - 'uid': The Drupal user id of the committer, or 0 if no Drupal user * could be associated to the committer. * - 'username': The system specific VCS username of the committer. * - 'repository': The repository where the tagging occurred, * given as a structured array, like the return value * of versioncontrol_get_repository(). * - 'directory': The deepest-level directory in the repository that is * common to all the tagged items, e.g. '/src' if the files * '/src/subdir/code.php' and '/src/README.txt' were tagged. * - 'message': The tag message that the user has given. If the version * control system doesn't support tag messages, this is an empty string. * - '[xxx]_specific': An array of VCS specific additional tag operation info. * How this array looks like is defined by the corresponding * backend module (versioncontrol_[xxx]). * * If not a single tag operation matches these constraints, * an empty array is returned. */ function versioncontrol_get_tag_operations($constraints = array(), &$result_count = NULL, $page = NULL, $limit = 10) { $backends = versioncontrol_get_backends(); // Filter by a set of common constraints whose code is shared with the // commit and branch retrieval functions. list($and_constraints, $params) = _versioncontrol_construct_common_constraints($constraints, $backends); // Filter by tag names. if (isset($constraints['tag_names'])) { if (empty($constraints['tag_names'])) { $and_constraints[] = 'FALSE'; } else { $or_constraints = array(); foreach ($constraints['tag_names'] as $tag_name) { $or_constraints[] = "t.name = '%s'"; $params[] = $tag_name; } if (!empty($or_constraints)) { $and_constraints[] = '('. implode(' OR ', $or_constraints) .')'; } } } $where = empty($and_constraints) ? '' : ' WHERE '. implode(' AND ', $and_constraints); $result = db_query('SELECT op.vc_op_id, op.date, op.uid, op.username, op.directory, t.tag_name, t.action, t.message, r.repo_id, r.vcs FROM {versioncontrol_tag_operations} t INNER JOIN {versioncontrol_operations} op ON t.vc_op_id = op.vc_op_id INNER JOIN {versioncontrol_repositories} r ON op.repo_id = r.repo_id '. $where .' ORDER BY op.date DESC', $params); $tags = array(); $op_ids_by_backend = array(); $repo_ids = array(); $selected_backends = array(); while ($row = db_fetch_object($result)) { if (!isset($selected_backends[$row->vcs])) { $selected_backends[$row->vcs] = $backends[$row->vcs]; } if (!isset($op_ids_by_backend[$row->vcs])) { $op_ids_by_backend[$row->vcs] = array(); } $op_ids_by_backend[$row->vcs][] = $row->vc_op_id; if (!in_array($row->repo_id, $repo_ids)) { $repo_ids[] = $row->repo_id; } $tags[$row->vc_op_id] = array( 'vc_op_id' => $row->vc_op_id, 'type' => VERSIONCONTROL_OPERATION_TAG, 'repo_id' => $row->repo_id, // 'repo_id' is replaced by 'repository' further down 'tag_name' => $row->tag_name, 'action' => $row->action, 'date' => $row->date, 'uid' => $row->uid, 'username' => $row->username, 'directory' => $row->directory, 'message' => $row->message, $row->vcs .'_specific' => array(), ); } if (empty($tags)) { return array(); } $repositories = versioncontrol_get_repositories(array('repo_ids' => $repo_ids)); foreach ($selected_backends as $vcs => $backend) { $is_autoadd = in_array(VERSIONCONTROL_FLAG_AUTOADD_TAG_OPERATIONS, $backend['flags']); _versioncontrol_amend_operations($tags, $op_ids_by_backend, $repositories, $vcs, $constraints, 'tag_operations', $is_autoadd); } $result_count = count($tags); if (isset($page)) { return _versioncontrol_page_operations($tags, $page, $limit); } return $tags; } /** * Retrieve a set of Drupal uid / VCS username mappings * that match the given constraints. * * @param $constraints * An optional array of constraints. Possible array elements are: * * - 'uids': An array of Drupal user ids. If given, only accounts that * correspond to these Drupal users will be returned. * - 'repo_ids': An array of repository ids. If given, only accounts * in the corresponding repositories will be returned. * - 'usernames': An array of system specific VCS usernames, * like array('dww', 'jpetso'). If given, only accounts * with these VCS usernames will be returned. * - 'usernames_by_repository': A structured array that looks like * array($repo_id => array('dww', 'jpetso'), ...). * You might want this if you combine multiple username and repository * constraints, otherwise you can well do without. * * @param $include_unauthorized * If FALSE (which is the default), this function does not return accounts * that are pending, queued, disabled, blocked, or otherwise non-approved. * If TRUE, all accounts are returned, regardless of their status. * * @return * A structured array that looks like * array($drupal_uid => array($repo_id => 'VCS username', ...), ...). * If not a single account matches these constraints, * an empty array is returned. */ function versioncontrol_get_accounts($constraints = array(), $include_unauthorized = FALSE) { $and_constraints = array(); $params = array(); // Filter by Drupal user id. if (isset($constraints['uids'])) { if (empty($constraints['uids'])) { $and_constraints[] = 'FALSE'; } else { $or_constraints = array(); foreach ($constraints['uids'] as $uid) { $or_constraints[] = 'uid = %d'; $params[] = $uid; } $and_constraints[] = '('. implode(' OR ', $or_constraints) .')'; } } // Filter by repository id. if (isset($constraints['repo_ids'])) { if (empty($constraints['repo_ids'])) { $and_constraints[] = 'FALSE'; } else { $or_constraints = array(); foreach ($constraints['repo_ids'] as $repo_id) { $or_constraints[] = 'repo_id = %d'; $params[] = $repo_id; } $and_constraints[] = '('. implode(' OR ', $or_constraints) .')'; } } // Filter by VCS username. if (isset($constraints['usernames'])) { if (empty($constraints['usernames'])) { $and_constraints[] = 'FALSE'; } else { $or_constraints = array(); foreach ($constraints['usernames'] as $username) { $or_constraints[] = "username = '%s'"; $params[] = $username; } $and_constraints[] = '('. implode(' OR ', $or_constraints) .')'; } } // Filter by usernames-by-repository. if (isset($constraints['usernames_by_repository'])) { if (empty($constraints['usernames_by_repository'])) { $and_constraints[] = 'FALSE'; } else { $or_constraints = array(); foreach ($usernames_by_repository as $repo_id => $usernames) { $repo_constraint = 'repo_id = %d'; $params[] = $repo_id; $username_constraints = array(); foreach ($usernames as $username) { $username_constraints[] = "username = '%s'"; $params[] = $username; } $or_constraints[] = '('. $repo_constraint .' AND ('. implode(' OR ', $username_constraints) .'))'; } $and_constraints[] = '('. implode(' OR ', $or_constraints) .')'; } } // Execute the query. // All the constraints have been gathered, assemble them to a WHERE clause. $where = empty($and_constraints) ? '' : ' WHERE '. implode(' AND ', $and_constraints); $result = db_query('SELECT uid, repo_id, username FROM {versioncontrol_accounts} '. $where .' ORDER BY uid', $params); // Assemble the return value. $account_rows = array(); $repo_ids = array(); while ($account = db_fetch_object($result)) { $repo_ids[] = $account->repo_id; $account_rows[] = $account; } if (empty($repo_ids)) { return array(); } $repo_ids = array_unique($repo_ids); $repositories = versioncontrol_get_repositories(array('repo_ids' => $repo_ids)); $accounts = array(); foreach ($account_rows as $account) { // Only include approved accounts, except in case the caller said otherwise. if ($include_unauthorized || versioncontrol_is_account_authorized($account->uid, $repositories[$account->repo_id])) { if (!isset($accounts[$account->uid])) { $accounts[$account->uid] = array(); } $accounts[$account->uid][$account->repo_id] = $account->username; } } return $accounts; } /** * Retrieve the VCS username for a given Drupal user id in a specific * repository. If you need more detailed querying functionality than this * function provides, use versioncontrol_get_accounts() instead. * * @param $repo_id * The repository id of the repository where the user has its VCS account. * @param $username * The VCS specific username (a string) corresponding to the Drupal user. * @param $include_unauthorized * If FALSE (which is the default), this function does not return accounts * that are pending, queued, disabled, blocked, or otherwise non-approved. * If TRUE, all accounts are returned, regardless of their status. * * @return * The Drupal user id that corresponds to the given username and repository, * or NULL if no Drupal user could be associated to those. */ function versioncontrol_get_account_uid_for_username($repo_id, $username, $include_unauthorized = FALSE) { $result = db_query("SELECT uid, repo_id FROM {versioncontrol_accounts} WHERE username = '%s' AND repo_id = %d", $username, $repo_id); while ($account = db_fetch_object($result)) { $repository = versioncontrol_get_repository($account->repo_id); // Only include approved accounts, except in case the caller said otherwise. if ($include_unauthorized || versioncontrol_is_account_authorized($account->uid, $repository)) { return $account->uid; } } return NULL; } /** * Retrieve the Drupal user id for a given VCS username in a specific * repository. If you need more detailed querying functionality than this * function provides, use versioncontrol_get_accounts() instead. * * @param $repo_id * The repository id of the repository where the user has its VCS account. * @param $uid * The Drupal user id corresponding to the VCS account. * @param $include_unauthorized * If FALSE (which is the default), this function does not return accounts * that are pending, queued, disabled, blocked, or otherwise non-approved. * If TRUE, all accounts are returned, regardless of their status. * * @return * The VCS username (a string) that corresponds to the given Drupal user * and repository, or NULL if no VCS account could be associated to those. */ function versioncontrol_get_account_username_for_uid($repo_id, $uid, $include_unauthorized = FALSE) { $result = db_query('SELECT uid, username, repo_id FROM {versioncontrol_accounts} WHERE uid = %d AND repo_id = %d', $uid, $repo_id); while ($account = db_fetch_object($result)) { $repository = versioncontrol_get_repository($account->repo_id); // Only include approved accounts, except in case the caller said otherwise. if ($include_unauthorized || versioncontrol_is_account_authorized($account->uid, $repository)) { return $account->username; } } return NULL; } /** * Returns TRUE if the account is authorized to commit to the given * repository, or FALSE otherwise. Only call this function on existing * accounts or uid 0, the return value for all other * uid/repository combinations is undefined. * * @param $uid * The user id of the checked account. * @param $repository * The repository where the status should be checked. (Note that the user's * authorization status may differ for each repository.) */ function versioncontrol_is_account_authorized($uid, $repository) { if (!$uid || !isset($repository)) { return FALSE; } $approved = array(); foreach (module_implements('versioncontrol_is_account_authorized') as $module) { $function = $module .'_versioncontrol_is_account_authorized'; // If at least one hook_versioncontrol_is_account_authorized returns FALSE, // the account is assumed not to be approved. if ($function($uid, $repository) === FALSE) { return FALSE; } } return TRUE; } /** * Retrieve the URL of the repository viewer that displays the given commit * in the corresponding repository. * * @param $commit * The commit whose view URL should be retrieved. * * @return * The commit view URL corresponding to the given arguments. * An empty string is returned if no commit view URL has been defined, * or if the commit cannot be viewed for any reason. */ function versioncontrol_get_url_commit_view($commit) { if (empty($commit['revision'])) { return ''; } $urls = _versioncontrol_get_repository_urls($commit['repository']); return strtr($urls['commit_view'], array( '%revision' => $commit['revision'], )); } /** * Retrieve the URL of the repository viewer that displays the commit log * of the given item in the corresponding repository. If no such URL has been * specified by the user, the appropriate URL from the Commit Log module is * used as a fallback (if that module is enabled). * * @param $repository * The repository that the item is located in. * @param $item * The item whose log view URL should be retrieved. * * @return * The item log view URL corresponding to the given arguments. * An empty string is returned if no item log view URL has been defined * (and if not even Commit Log is enabled), or if the item cannot be viewed * for any reason. */ function versioncontrol_get_url_item_log_view($repository, $item) { $urls = _versioncontrol_get_repository_urls($repository); $current_branch = versioncontrol_get_current_item_branch($repository, $item); if (!empty($urls['file_log_view'])) { if ($item['type'] == VERSIONCONTROL_ITEM_FILE) { return strtr($urls['file_log_view'], array( '%path' => $item['path'], '%revision' => $item['revision'], '%branch' => isset($current_branch) ? $current_branch : '', )); } // The default URL backend doesn't do log view URLs for directory items: return ''; } else if (module_exists('commitlog')) { // fallback, as 'file_log_view' is empty return url('commitlog', 'repos='. $repository['repo_id'] .'&paths='. drupal_urlencode($item['path']) . (isset($current_branch) ? '&branches='. $current_branch : '') ); } return ''; // in case we really can't retrieve any sensible URL } /** * Retrieve the URL of the repository viewer that displays the given item * in the corresponding repository. * * @param $repository * The repository that the item is located in. * @param $item * The item whose view URL should be retrieved. * * @return * The item view URL corresponding to the given arguments. * An empty string is returned if no item view URL has been defined, * or if the item cannot be viewed for any reason. */ function versioncontrol_get_url_item_view($repository, $item) { $urls = _versioncontrol_get_repository_urls($repository); $current_branch = versioncontrol_get_current_item_branch($repository, $item); if ($item['type'] == VERSIONCONTROL_ITEM_FILE) { return strtr($urls['file_view'], array( '%path' => $item['path'], '%revision' => $item['revision'], '%branch' => isset($current_branch) ? $current_branch : '', )); } else { // if ($item['type'] == VERSIONCONTROL_ITEM_DIRECTORY) { return strtr($urls['directory_view'], array( '%path' => $item['path'], '%revision' => $item['revision'], '%branch' => isset($current_branch) ? $current_branch : '', )); } } /** * Retrieve the URL of the repository viewer that displays the diff between * two given files in the corresponding repository. * * @param $repository * The repository that the file items are located in. * @param $file_item_new * The new version of the file that should be diffed. * @param $file_item_old * The old version of the file that should be diffed. * * @return * The diff URL corresponding to the given arguments. * An empty string is returned if no diff URL has been defined, * or if the two items cannot be diffed for any reason. */ function versioncontrol_get_url_diff($repository, $file_item_new, $file_item_old) { $urls = _versioncontrol_get_repository_urls($repository); $current_branch = versioncontrol_get_current_item_branch($repository, $file_item_new); return strtr($urls['diff'], array( '%path' => $file_item_new['path'], '%new-revision' => $file_item_new['revision'], '%old-path' => $file_item_old['path'], '%old-revision' => $file_item_old['revision'], '%branch' => isset($current_branch) ? $current_branch : '', )); } /** * Retrieve the URL of the issue tracker that displays the issue/case/bug page * of an issue id which presumably has been mentioned in a commit message. * As issue tracker urls are specific to each repository, this also needs * to be given as argument. * * @param $repository * The repository that is covered by the issue tracker. * @param $issue_id * A number that uniquely identifies the mentioned issue/case/bug. * * @return * The issue tracker URL corresponding to the given arguments. * An empty string is returned if no issue tracker URL has been defined. */ function versioncontrol_get_url_tracker($repository, $issue_id) { $urls = _versioncontrol_get_repository_urls($repository); return strtr($urls['tracker'], array('%d' => $issue_id)); } /** * Retrieve the repository viewer URLs from the database (or from the cache). * * @param $repository * The repository for which the URLs should be retrieved. * * @return * A structured array with the repository viewer URLs as values (each of them * can be an empty string), corresponding to the following keys: * * - 'commit_view': The overall summary view of a commit/revision. * - 'file_view': The 'view' URL of a file in the repository. * - 'directory_view': The 'view' URL of a directory in the repository. * - 'diff': The difference between two versions of a file * (or of two different files, if the repository viewer supports it). * - 'tracker': The issue/bug/case URL of the associated issue tracker. */ function _versioncontrol_get_repository_urls($repository) { static $urls_by_repository = array(); if (!isset($urls_by_repository[$repository['repo_id']])) { $result = db_query('SELECT * FROM {versioncontrol_repository_urls} WHERE repo_id = %d', $repository['repo_id']); while ($urls = db_fetch_array($result)) { unset($urls['repo_id']); $urls_by_repository[$repository['repo_id']] = $urls; } } if (!isset($urls_by_repository[$repository['repo_id']])) { return array( 'commit_view' => '', 'file_view' => '', 'directory_view' => '', 'diff' => '', 'tracker' => '', ); } return $urls_by_repository[$repository['repo_id']]; } /** * Retrieve detailed information about what happened in a single commit. * * @param $commit * The commit whose actions should be retrieved. * * @return * A structured array containing the exact details of what happened to * each item in this commit. Array keys are the current/new paths, also for * VERSIONCONTROL_ACTION_DELETED actions even if the file actually doesn't * exist anymore. The corresponding array values are again structured arrays * and consist of elements with the following keys: * * - 'action': Specifies how the item was modified. * One of the predefined VERSIONCONTROL_ACTION_* values. * - 'modified': Boolean value, specifies if a file was modified in addition * to the other action in the 'action' element of the array. * Only exists for the VERSIONCONTROL_ACTION_MOVED * and VERSIONCONTROL_ACTION_COPIED actions. * - 'current item': The updated state of the modified item. * Exists for all actions except VERSIONCONTROL_ACTION_DELETED. * - 'source items': An array with the previous state(s) of the modified item. * Exists for all actions except VERSIONCONTROL_ACTION_ADDED. * * Item values are structured arrays and consist of elements * with the following keys: * * - 'type': Specifies the item type, which is either * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY. * - 'path': The path of the item at the specific revision. * - 'revision': The (file-level) revision when the item was changed. * If there is no such revision (which may be the case for * directory items) then the 'revision' element is an empty string. * - '[xxx]_specific': May be set by the backend to remember additional * item info. ("[xxx]" is the unique string identifier of the respective * version control system.) * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_commit_actions($commit) { return _versioncontrol_call_backend( $commit['repository']['vcs'], 'get_commit_actions', array($commit) ); } /** * Get the user-visible version of a commit identifier a.k.a. 'revision', * as plaintext. By default, this function returns $commit['revision'] if * that property exists, or $commit['vc_op_id'] as fallback. * * Version control backends can, however, choose to implement their own version * of this function, which for example makes it possible to cut the SHA-1 hash * in distributed version control systems down to a readable length. * * @param $commit * The commit whose commit identifier should be themed. * @param $format * Either 'full' for the original version, or 'short' for a more compact form. * If the commit identifier doesn't need to be shortened, the results can * be the same for both versions. */ function versioncontrol_format_commit_identifier($commit, $format = 'short') { if (empty($commit['revision'])) { return $commit['vc_op_id']; } $vcs = $commit['repository']['vcs']; if ($format == 'short' && versioncontrol_backend_implements($vcs, 'format_short_revision_identifier')) { return _versioncontrol_call_backend( $vcs, 'format_short_revision_identifier', array($commit['revision']) ); } return $commit['revision']; } /** * Retrieve general statistics about what happened in a single commit. For more granular * details about what happened in a single commit, use versioncontrol_get_commit_actions. * * This function is optional for VCS backends to implement, be sure to check * with versioncontrol_backend_implements($repository['vcs'], 'get_commit_statistics') * if the particular backend actually implements it. * * @param $commit * The commit to retrieve statistics about. * @param $commit_actions * The commit actions array belonging to the above commit. Can be passed by * the caller if the caller already has the actions, or can be left absent * and they will be automatically obtained. * * @return * A structured array containing general statistics about this commit. * The array will consist of elements with the following keys: * * - 'lines_added': Total number of lines added during this commit. * - 'lines_removed': Total number of lines removed during this commit. * - 'action_count': Total number of actions within this commit. * - 'per_action_statistics': An array containing statistics on individual * actions. Array keys are the current/new paths (just as with * versioncontrol_get_commit_actions()). * * The array values of the 'per_action_statistics' element are again * structured arrays and consist of elements with the following keys: * * - 'lines_added': Number of lines added to this item. * - 'lines_removed': Number of lines removed to this item. * * These values are designed so that statistics from several commits can be * combined, hence why totals are given rather than averages. A real-life * example of such a result array can be found in the FakeVCS example module. */ function versioncontrol_get_commit_statistics($commit, $commit_actions = NULL) { return _versioncontrol_call_backend( $commit['repository']['vcs'], 'get_commit_statistics', array($commit, $commit_actions) ); } /** * Return the 'current item' of a commit action if there is one, * or the first (only) one of the 'source items' if the file has been deleted. * As this function is also being used within access checks, it also works * when the corresponding commit has not yet been inserted into the database. */ function versioncontrol_get_affected_item($commit_action) { return isset($commit_action['current item']) ? $commit_action['current item'] // new revision of the file, or : $commit_action['source items'][0]; // the deleted item } /** * Retrieve the item of the deepest-level directory in the repository that is * common to all the changed/branched/tagged items in a commit, branch or * tag operation. In other words, this function gets you the item * for $operation['directory']. * * @param $operation * The commit, branch or tag operation whose deepest-level * changed/branched/tagged directory should be retrieved. * * @return * The requested directory item. Item values are structured arrays and * consist of elements with the following keys: * * - 'type': Specifies the item type, which in this case can only be * VERSIONCONTROL_ITEM_DIRECTORY. * - 'path': The path of the directory, which will be the same * as $operation['directory']. * - 'revision': The (file-level) revision when the item was last changed. * If there is no such revision (which may be the case for * directory items) then the 'revision' element is an empty string. * - '[xxx]_specific': May be set by the backend to remember additional * item info. ("[xxx]" is the unique string identifier * of the respective version control system.) */ function versioncontrol_get_directory_item($operation) { return _versioncontrol_call_backend( $operation['repository']['vcs'], 'get_directory_item', array($operation) ); } /** * Retrieve the branches that have been affected by the given commit. * * @return * An array of strings that identify a branch in the respective repository, * like for example array('DRUPAL-5') or array('trunk', '5.x'). * If no branch was affected by the given commit, an empty array is returned. */ function versioncontrol_get_commit_branches($commit) { return _versioncontrol_call_backend( $commit['repository']['vcs'], 'get_commit_branches', array($commit) ); } /** * Retrieve the set of items that were affected by a branch operation. * * @param $branch * The branch operation whose items should be retrieved. This is an array * like the one returned by versioncontrol_get_branch_operation(). * * @return * An array of all items that were affected by the branching operation. * An empty result array means that the whole repository has been branched. * Item values are structured arrays and consist of elements * with the following keys: * * - 'type': Specifies the item type, which is either * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY. * - 'path': The path of the item at the specific revision. * - 'revision': The (file-level) revision when the item was changed. * If there is no such revision (which may be the case for * directory items) then the 'revision' element is an empty string. * - 'source branch': Optional, may be set by the backend if the * source branch (the one that this one branched off) can be retrieved. * If given, this is a string with the original branch name. * - '[xxx]_specific': May be set by the backend to remember additional * item info. ("[xxx]" is the unique string identifier of the respective * version control system.) * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_branched_items($branch) { return _versioncontrol_call_backend( $branch['repository']['vcs'], 'get_branched_items', array($branch) ); } /** * Retrieve the set of items that were affected by a tag operation. * * @param $tag * The tag operation whose items should be retrieved. This is an array * like the one returned by versioncontrol_get_tag_operation(). * * @return * An array of all items that were affected by the tagging operation. * An empty result array means that the whole repository has been tagged. * Item values are structured arrays and consist of elements * with the following keys: * * - 'type': Specifies the item type, which is either * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY. * - 'path': The path of the item at the specific revision. * - 'revision': The (file-level) revision when the item was changed. * If there is no such revision (which may be the case for * directory items) then the 'revision' element is an empty string. * - 'source branch': Optional, may be set by the backend if the * source branch (the one that this tag comes from) can be retrieved. * If given, this is a string with the original tag name. * - '[xxx]_specific': May be set by the backend to remember additional * item info. ("[xxx]" is the unique string identifier of the respective * version control system.) * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_tagged_items($tag) { return _versioncontrol_call_backend( $tag['repository']['vcs'], 'get_tagged_items', array($tag) ); } /** * Retrieve the current branch that this item is in. If this item was part of * the result of versioncontrol_get_commit_actions(), this will probably be * the branch that this item was committed to. If no specific branch is known * or applicable for this item, NULL is returned. The main branch ('HEAD', * 'trunk', 'master' or however it is called in the respective VCS) is also * a valid branch and should be expected as return value. * * @param $repository * The repository that the item is located in. * @param $item * The item whose current branch should be retrieved. * * @return * A string containing the current item branch, or NULL if no branch * is known or applicable. */ function versioncontrol_get_current_item_branch($repository, $item) { return _versioncontrol_call_backend( $repository['vcs'], 'get_current_item_branch', array($repository, $item) ); } /** * Retrieve the current tag operation that has been applied to this item. * If no specific tag operation is known or applicable for this item, * NULL is returned. * * @param $repository * The repository that the item is located in. * @param $item * The item whose current branch should be retrieved. * * @return * A tag operation array like the return value * of versioncontrol_get_tag_operation(), or NULL if no tag * is known or applicable. */ function versioncontrol_get_current_item_tag($repository, $item) { return _versioncontrol_call_backend( $repository['vcs'], 'get_current_item_tag', array($repository, $item) ); } /** * Retrieve the parent (directory) item of a given item. * * @param $repository * The repository that the item is located in. * @param $item * The item whose parent should be retrieved. * @param $parent_path * NULL if the direct parent of the given item should be retrieved, * or a parent path that is further up the directory tree. * * @return * The parent directory item at the same revision as the given item. * If $parent_path is not set and the item is already the topmost one * in the repository, the item is returned as is. It also stays the same * if $parent_path is given and the same as the path of the given item. * If the given directory path does not correspond to a parent item, * NULL is returned. */ function versioncontrol_get_parent_item($repository, $item, $parent_path = NULL) { return _versioncontrol_call_backend( $repository['vcs'], 'get_parent_item', array($repository, $item, $parent_path) ); } /** * Retrieve all branches that exist for the given item, and how this item * is named there. The main branch ('HEAD', 'trunk', 'master' or however it is * called in the respective VCS) is also included in the result. * * This function is optional for VCS backends to implement, be sure to check * with versioncontrol_backend_implements($repository['vcs'], 'get_all_item_branches') * if the particular backend actually implements it. * * @param $repository * The repository that the item is located in. * @param $item * The item whose set of branches should be retrieved. * * @return * A structured array that looks like * array( * $branch1_name => $branch1_item, * $branch2_name => $branch2_item, * ... * ). * * The corresponding item values are again structured arrays * and consist of elements with the following keys: * * - 'type': Specifies the item type, which is either * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY. * - 'path': The path of the item at the specific revision. * - 'revision': The (file-level) revision when the item was last changed. * If there is no such revision (which may be the case for * directory items) then the 'revision' element is an empty string. * - '[xxx]_specific': May be set by the backend to remember additional * item info. ("[xxx]" is the unique string identifier of the respective * version control system.) * * An empty array is returned if there are no branches for this item. * NULL is returned if the given item is not inside the repository at the * time of 'revision'. * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_all_item_branches($repository, $item) { return _versioncontrol_call_backend( $repository['vcs'], 'get_all_item_branches', array($repository, $item) ); } /** * Retrieve all tags that exist for the given item $path, and how this item * is named there. * * This function is optional for VCS backends to implement, be sure to check * with versioncontrol_backend_implements($repository['vcs'], 'get_all_item_tags') * if the particular backend actually implements it. * * @param $repository * The repository that the item is located in. * @param $item * The item whose history should be retrieved. * * @return * A structured array that looks like * array( * $tag1_name => $tag1_item, * $tag2_name => $tag2_item, * ... * ). * * The corresponding item values are again structured arrays * and consist of elements with the following keys: * * - 'type': Specifies the item type, which is either * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY. * - 'path': The path of the item at the specific revision. * - 'revision': The (file-level) revision when the item was last changed. * If there is no such revision (which may be the case for * directory items) then the 'revision' element is an empty string. * - '[xxx]_specific': May be set by the backend to remember additional * item info. ("[xxx]" is the unique string identifier of the respective * version control system.) * * An empty array is returned if there are no tags for this item. * NULL is returned if the given item is not inside the repository at the * time of 'revision'. * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_all_item_tags($repository, $item) { return _versioncontrol_call_backend( $repository['vcs'], 'get_all_item_tags', array($repository, $item) ); } /** * Get the user-visible version of an item's revision identifier, as plaintext. * By default, this function simply returns $item['revision']. * * Version control backends can, however, choose to implement their own version * of this function, which for example makes it possible to cut the SHA-1 hash * in distributed version control systems down to a readable length. * * @param $repository * The repository that the item is located in. * @param $item * The item whose revision identifier should be themed. * @param $format * Either 'full' for the original version, or 'short' for a more compact form. * If the revision identifier doesn't need to be shortened, the results can * be the same for both versions. */ function versioncontrol_format_item_revision($repository, $item, $format = 'short') { if ($format == 'short' && versioncontrol_backend_implements($repository['vcs'], 'format_short_revision_identifier')) { return _versioncontrol_call_backend( $repository['vcs'], 'format_short_revision_identifier', array($item['revision']) ); } return $item['revision']; } /** * Retrieve the revisions where the given item has been changed, * in reverse chronological order. * * Only direct descendants or predecessors of this item will be retrieved. * That means that history of the same item in a different (parallel) branch * will not be returned, except if the given state of the item was moved/copied * from there or goes back there in a more recent revision. In short, you'll * not get "1.7" and "1.2.2.4" items side by side if it doesn't make sense. * * @param $repository * The repository that the item is located in. * @param $item * The item whose history should be retrieved. * * @return * An array containing the exact details of all item changes. Each element * of the array is a structured array consisting of elements with the * following keys: * * - 'action': Specifies how the item was modified. * One of the predefined VERSIONCONTROL_ACTION_* values. * - 'modified': Boolean value, specifies if a file was modified in addition * to the other action in the 'action' element of the array. * Only exists for the VERSIONCONTROL_ACTION_MOVED * and VERSIONCONTROL_ACTION_COPIED actions. * - 'current item': The updated state of the modified item. * Exists for all actions except VERSIONCONTROL_ACTION_DELETED. * - 'source items': An array with the previous state(s) of the modified item. * Exists for all actions except VERSIONCONTROL_ACTION_ADDED. * * Item values are structured arrays and consist of elements * with the following keys: * * - 'type': Specifies the item type, which is either * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY. * - 'path': The path of the item at the specific revision. * - 'revision': The (file-level) revision when the item was changed. * If there is no such revision (which may be the case for * directory items) then the 'revision' element is an empty string. * - '[xxx]_specific': May be set by the backend to remember additional * item info. ("[xxx]" is the unique string identifier of the respective * version control system.) * * NULL is returned if the given item is not under version control, * or was not under version control at the time of the given revision. * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_item_history($repository, $item) { return _versioncontrol_call_backend( $repository['vcs'], 'get_item_history', array($repository, $item) ); } /** * Retrieve the set of files and directories that exist at a specified revision * in the given directory inside the repository. * * This function is optional for VCS backends to implement, be sure to check * with versioncontrol_backend_implements($repository['vcs'], 'get_directory_contents') * if the particular backend actually implements it. * * @param $repository * The repository that the directory item is located in. * @param $directory_item * The parent item of the the items that should be listed. * @param $recursive * If FALSE, only the direct children of $path will be retrieved. * If TRUE, you'll get every single descendant of $path. * * @return * A structured item array containing the exact details of which items have * been inside the directory at the time of the commit, including the * directory itself. Array keys are the current/new paths. * The corresponding item values are again structured arrays * and consist of elements with the following keys: * * - 'type': Specifies the item type, which is either * VERSIONCONTROL_ITEM_FILE or VERSIONCONTROL_ITEM_DIRECTORY. * - 'path': The path of the item at the specific revision. * - 'revision': The (file-level) revision when the item was last changed. * If there is no such revision (which may be the case for * directory items) then the 'revision' element is an empty string. * - '[xxx]_specific': May be set by the backend to remember additional * item info. ("[xxx]" is the unique string identifier of the respective * version control system.) * * NULL is returned if the given item is not inside the repository, * or if it is not a directory item at all. * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_directory_contents($repository, $directory_item, $recursive = FALSE) { if ($directory_item['type'] != VERSIONCONTROL_ITEM_DIRECTORY) { return NULL; } return _versioncontrol_call_backend( $repository['vcs'], 'get_directory_contents', array($repository, $directory_item, $recursive) ); } /** * Retrieve the text or binary data of the given file in the specified commit. * * This function is optional for VCS backends to implement, be sure to check * with versioncontrol_backend_implements($repository['vcs'], 'get_file_contents') * if the particular backend actually implements it. * * @param $repository * The repository that the file item is located in. * @param $file_item * The file item whose contents should be retrieved. * * @return * A structured array that consists of elements with the following keys: * * - 'file type': Specifies the file type, which is either * VERSIONCONTROL_FILE_TEXT or VERSIONCONTROL_FILE_BINARY. * - 'contents': The raw contents of the file. * * NULL is returned if the given item is not under version control, * or was not under version control at the time of the given revision, * or if it is not a file item at all. * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_file_contents($repository, $file_item) { if (!($file_item['type'] == VERSIONCONTROL_ITEM_FILE || $file_item['type'] == VERSIONCONTROL_ITEM_FILE_DELETED)) { return NULL; } return _versioncontrol_call_backend( $repository['vcs'], 'get_file_contents', array($repository, $file_item) ); } /** * Retrieve an array where each element represents a single line of the * given file in the specified commit, annotated with the committer who last * modified that line. Note that annotations are generally a quite slow * operation, so expect this function to take a bit more time as well. * * This function is optional for VCS backends to implement, be sure to check * with versioncontrol_backend_implements($repository['vcs'], 'get_file_annotation') * if the particular backend actually implements it. * * @param $repository * The repository that the file item is located in. * @param $file_item * The file item whose annotation should be retrieved. * * @return * A structured array that consists of one element per line, with * line numbers as keys (starting from 1) and a structured array as values, * where each of them consists of elements with the following keys: * * - 'username': The system specific VCS username of the last committer. * - 'line': The contents of the line, without linebreak characters. * * NULL is returned if the given item is not under version control, * or was not under version control at the time of the given revision, * or if it is not a file item at all, or if it is marked as binary file. * * A real-life example of such a result array can be found * in the FakeVCS example module. */ function versioncontrol_get_file_annotation($repository, $file_item) { if (!($file_item['type'] == VERSIONCONTROL_ITEM_FILE || $file_item['type'] == VERSIONCONTROL_ITEM_FILE_DELETED)) { return NULL; } return _versioncontrol_call_backend( $repository['vcs'], 'get_file_annotation', array($repository, $file_item) ); } /** * Print a list of contributors for the specified project. * The parameters are directly relayed to versioncontrol_get_user_statistics() * and determine how and for which commits the statistics are displayed. * @see versioncontrol_get_user_statistics */ function theme_versioncontrol_user_statistics($constraints, $order_by = 'last_commit', $limit = NULL) { $statistics = versioncontrol_get_user_statistics($constraints, $order_by, $limit); $header = array(t('User'), t('Last commit'), t('First commit'), t('Commits')); $rows = array(); foreach ($statistics as $user_stats) { $uid = $user_stats['uid']; $last_commit = t('!time ago', array( '!time' => format_interval(time() - $user_stats['last_commit']['date'], 1)) ); $first_commit = t('!time ago', array( '!time' => format_interval(time() - $user_stats['first_commit']['date'], 1)) ); $number_commits = format_plural( $user_stats['number_commits'], '1 commit', '@count commits' ); if (module_exists('commitlog')) { $last_commit = theme('commitlog_vc_op_id', $user_stats['last_commit']['vc_op_id'], $last_commit ); $first_commit = theme('commitlog_vc_op_id', $user_stats['first_commit']['vc_op_id'], $first_commit ); $number_commits = l($number_commits, 'user/'. $uid .'/track/code'); } $rows[] = array( // $user_stats contains 'uid' and 'name', which is enough for theming theme('username', (object) $user_stats), $last_commit, $first_commit, $number_commits, ); } return theme('table', $header, $rows); } /** * Retrieve statistics about users from a set of commits. * * @param $constraints * The constraints (as passed to versioncontrol_get_commits()) that define * which commits are taken into consideration for the statistics. * @param $order_by * Determines how the result array is ordered: * - 'last_commit' for descending order of the last commit's date (in other * words, the first item corresponds to the most recent committer). * - 'number_commits' for descending order of the commit count (in other * words, the first item corresponds to the most active committer). * @param $limit * If given, only the n most active users (in terms of commits) * will be included in the result. * * @return * An array of information about the projects. Each element contains * one user's statistics and consists of the following elements: * - 'uid': The Drupal user id to which these statistics correspond. * - 'name': The respective Drupal username. * - 'number_commits': The number of commits by this user * (out of the given commits). * - 'first_commit': The commit array of the first commit by this user * (out of the given commits). * - 'last_commit': The commit array of the last commit by this user * (out of the given commits). * * The order of the items in this array is determined by the * $order_by parameter. If no commits matching the given constraints * can be found, an empty array is returned. */ function versioncontrol_get_user_statistics($constraints, $order_by = 'last_commit', $limit = NULL) { $uids = array(); $first_commits = array(); $last_commits = array(); $commits = versioncontrol_get_commits($constraints); // Find out which projects are affected, and how active they have been. $i = 0; foreach ($commits as $commit) { $uid = $commit['uid']; $uids[] = $uid; // Write the first commit for each iteration, the last one encountered is // the first one in time. ($commits is sorted reverse chronologically.) $first_commits[$uid] = $commit; // Similar for the last commit which is the first one that we get to see. if (!isset($last_commits[$uid])) { $last_commits[$uid] = $commit; } } // Sort by commit count. $commit_counts = array_count_values($uids); arsort($commit_counts); // Default sorting: Put users with more recent commits first. if ($order_by == 'last_commit') { $limited_uids = array(); $i = 0; foreach (array_unique($uids) as $uid) { $limited_uids[] = $uid; ++$i; if (isset($limit) && $i >= $limit) { break; } } $uids = $limited_uids; } // Alternative sorting: Put users with the most commits first. else { // if ($order_by == 'number_commits') { $limited_commit_counts = array(); $i = 0; foreach ($commit_counts as $uid => $count) { $limited_commit_counts[$uid] = $count; ++$i; if (isset($limit) && $i >= $limit) { break; } } $uids = array_unique(array_keys($limited_commit_counts)); $commit_counts = $limited_commit_counts; } // No projects in the result, so we can return right now. if (empty($uids)) { return array(); } // Ok, now construct a query and get the desired node titles $placeholders = array(); foreach ($uids as $uid) { $placeholders[] = '%d'; } $result = db_query('SELECT uid, name FROM {users} WHERE uid IN ('. implode(',', $placeholders) .')', $uids); $sorted_statistics = array(); while ($user = db_fetch_object($result)) { $statistics[$user->uid] = array( 'uid' => $user->uid, 'name' => $user->name, 'number_commits' => $commit_counts[$user->uid], 'first_commit' => $first_commits[$user->uid], 'last_commit' => $last_commits[$user->uid], ); } if (empty($statistics)) { return array(); } $sorted_statistics = array(); foreach ($uids as $uid) { // the $uids array is already in the right order if (isset($statistics[$uid])) { $sorted_statistics[] = $statistics[$uid]; } } return $sorted_statistics; } /** * Implementation of hook_block(): * Present a list of the most active developers. */ function versioncontrol_block($op = 'list', $delta = 0) { if ($op == 'list') { $blocks[0]['info'] = t('Most active developers'); return $blocks; } else if ($op == 'view') { if ($delta == 0) { $interval = 7 * 24 * 60 * 60; $length = 15; $result = db_query_range( 'SELECT COUNT(op.vc_op_id) AS count, op.uid, u.name FROM {versioncontrol_operations} op INNER JOIN {users} u ON op.uid = u.uid AND op.uid != 0 AND op.date > %d WHERE op.type = %d GROUP BY op.uid ORDER BY count DESC', array(time() - $interval, VERSIONCONTROL_OPERATION_COMMIT), // placeholders 0, $length // query range arguments: 'from' and 'count' ); $usernames = array(); while ($user = db_fetch_object($result)) { $usernames[] = theme('username', $user); } if (!empty($usernames)) { $block = array( 'subject' => t('Most active developers'), 'content' => theme('item_list', $usernames), ); return $block; } } } } /** * Returns a string of suggestions for Drupal usernames with accounts for * the given repository, formatted to be suitable for use with * JS autocomplete fields. */ function versioncontrol_user_autocomplete($repo_id, $string) { if (!is_numeric($repo_id)) { return drupal_to_js(array()); } $matches = array(); $result = db_query_range("SELECT u.uid, u.name FROM {users} u INNER JOIN {versioncontrol_accounts} a ON u.uid = a.uid WHERE repo_id = %d AND LOWER(u.name) LIKE LOWER('%s%%')", $repo_id, $string, 0, 10); while ($user = db_fetch_object($result)) { if (!isset($repository)) { $repository = versioncontrol_get_repository($repo_id); } if (versioncontrol_is_account_authorized($user->uid, $repository)) { $matches[$user->name] = check_plain($user->name); } } print drupal_to_js($matches); exit(); } /** * Generate and execute a SELECT query for the given table base on the name * and given values of this table's primary key. This function basically * accomplishes the retrieval part of Version Control API's 'autoadd' feature. * In order to avoid unnecessary complexity, the primary key may not consist * of multiple columns and has to be a numeric value. */ function _versioncontrol_db_get_additions($table_name, $primary_key_name, $keys) { $placeholders = array(); foreach ($keys as $key) { $placeholders[] = '%d'; } $result = db_query('SELECT * FROM {'. $table_name .'} WHERE '. $primary_key_name .' IN ('. implode(',', $placeholders) .')', $keys); $additions = array(); while ($addition = db_fetch_array($result)) { $primary_key = $addition[$primary_key_name]; unset($addition[$primary_key_name]); foreach ($addition as $key => $value) { if (!is_numeric($addition[$key])) { $addition[$key] = unserialize($addition[$key]); } } $additions[$primary_key] = $addition; } return $additions; } /** * Return preset values for strings that are used in the user interface. */ function _versioncontrol_get_string_presets() { $presets = array(); $presets['versioncontrol_registration_message_unauthorized'] = t('
The Concurrent Versioning System (CVS) is a software development tool available to volunteers with experience in software development, translation, theming, or documentation who wish to participate in the Drupal project.
To request access to the Drupal CVS repository you must create an account and login. Come back to this page after you have logged on.
', array('!register' => url('user/register'), '!login' => url('user/login')) ); $presets['versioncontrol_registration_message_authorized'] = t('The Concurrent Versioning System (CVS) is a software development tool available to volunteers with experience in software development, translation, theming, or documentation who wish to participate in the Drupal project.
A version control system account is not required to contribute patches to the Drupal project or community contributed projects. Anonymous access to the Drupal CVS repository is available which can be used to accomplish this. Please peruse the CVS handbook and patch guide for more information.
If you are an open source software developer, themer, translator or documentation writer, please choose one of Drupal\'s repositories from the selection below in order to request commit access to this repository. Prior to applying, please ensure that:
The Concurrent Versioning System (CVS) is a software development tool available to volunteers with experience in software development, translation, theming, or documentation who wish to participate in the Drupal project.
A version control system account is not required to contribute patches to the Drupal project or community contributed projects. Anonymous access to the Drupal CVS repository is available which can be used to accomplish this. Please peruse the CVS handbook and patch guide for more information.
If you are an open source software developer, themer, translator or documentation writer, please use the form below to create an account in Drupal\'s CVS repository. Prior to registering, please ensure that: