array('uid', 'username', 'repository', 'options' => NULL), ); $theme['versioncontrol_user_statistics_table'] = array( 'arguments' => array('statistics', 'options'), ); $theme['versioncontrol_user_statistics_item_list'] = array( 'arguments' => array('statistics', 'more_link'), ); $theme['versioncontrol_user_statistics_account'] = array( 'arguments' => array('user_stats'), ); return $theme; } /** * Implementation of hook_user(): * Register additional user account edit tabs, * and delete VCS accounts when the associated user account is deleted. */ function versioncontrol_user($type, &$edit, &$user, $category = NULL) { switch ($type) { case 'categories': $categories = array(); $categories[] = array( 'name' => 'versioncontrol', // user_menu() pipes 'title' though check_plain() already. 'title' => 'Repository accounts', 'weight' => 99, ); return $categories; case 'delete': $accounts = versioncontrol_user_accounts_load($user->uid, TRUE); foreach ($accounts as $uid => $usernames_by_repository) { foreach ($usernames_by_repository as $repo_id => $account) { $account->delete(); } } break; } } /** * Implementation of hook_menu(). */ function versioncontrol_menu() { // FIXME this is hacky; it forces autoload to rebuild its cache to ensure // everything is up-to-date, since we need it to be for this function. autoload_registry_update(); $items = array(); $admin = array( 'page callback' => 'drupal_get_form', 'access arguments' => array('administer version control systems'), 'file' => 'versioncontrol.admin.inc', ); // 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['admin/project'] = array( 'title' => 'Project administration', 'description' => 'Administrative interface for project management and related modules.', 'position' => 'left', 'weight' => 3, 'page callback' => 'system_admin_menu_block_page', 'access arguments' => array('administer site configuration'), 'file' => 'system.admin.inc', 'file path' => drupal_get_path('module', 'system'), ); } $items['admin/project/versioncontrol-settings'] = array( 'title' => 'Version control settings', 'description' => 'Configure settings for Version Control API and related modules.', 'page arguments' => array('versioncontrol_admin_settings'), 'type' => MENU_NORMAL_ITEM, ) + $admin; $items['admin/project/versioncontrol-settings/general'] = array( 'title' => 'General', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => -1, ); $items['admin/project/versioncontrol-repositories'] = array( 'title' => 'VCS repositories', 'description' => '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.', 'page arguments' => array('versioncontrol_admin_repository_list'), ) + $admin; $weight = 1; $items['admin/project/versioncontrol-repositories/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => $weight, ); // former !$may_cache /// TODO: Backend specific stuff was done in !$may_cache, as it once /// screwed up after activating a new backend in admin/build/modules. /// Make sure this works now. foreach (versioncontrol_get_backends() as $vcs => $backend) { $items['admin/project/versioncontrol-repositories/add-'. $vcs] = array( 'title' => 'Add @vcs repository', 'title arguments' => array('@vcs' => $backend->name), 'page arguments' => array('versioncontrol_admin_repository_edit', VERSIONCONTROL_FORM_CREATE, $vcs ), 'type' => MENU_LOCAL_TASK, 'weight' => ++$weight, ) + $admin; } // end former !$may_cache $items['admin/project/versioncontrol-repositories/edit/%versioncontrol_repository'] = array( 'title' => 'Edit repository', 'page arguments' => array('versioncontrol_admin_repository_edit', 4), 'type' => MENU_CALLBACK, ) + $admin; $items['admin/project/versioncontrol-repositories/delete/%versioncontrol_repository'] = array( 'title' => 'Delete repository', 'page arguments' => array('versioncontrol_admin_repository_delete_confirm', 4), 'type' => MENU_CALLBACK, ) + $admin; $items['admin/project/versioncontrol-repositories/clearlock/%versioncontrol_repository'] = array( 'title' => 'Clear lock', 'page arguments' => array('versioncontrol_admin_repository_clearlock_confirm', 4), 'type' => MENU_CALLBACK, ) + $admin; $items['admin/project/versioncontrol-repositories/fetch/%versioncontrol_repository'] = array( 'title' => 'Fetch logs', 'page arguments' => array('versioncontrol_admin_repository_fetch_confirm', 4), 'type' => MENU_CALLBACK, ) + $admin; $items['admin/project/versioncontrol-accounts'] = array( 'title' => 'VCS accounts', 'description' => 'Manage associations of Drupal users to VCS user accounts.', 'page arguments' => array('versioncontrol_admin_account_list_form'), 'type' => MENU_NORMAL_ITEM, ) + $admin; $items['admin/project/versioncontrol-accounts/list'] = array( 'title' => 'List', 'type' => MENU_DEFAULT_LOCAL_TASK, 'weight' => 0, ); // Account registration and editing pages for the regular user. $items['versioncontrol/register'] = array( 'title' => 'Get commit access', 'page callback' => 'versioncontrol_account_register_page', 'access callback' => TRUE, // access checking is done in the page callback 'file' => 'versioncontrol.pages.inc', 'type' => MENU_SUGGESTED_ITEM, ); $items['user/%versioncontrol_user_accounts/edit/versioncontrol'] = array( // Load with $include_unauthorized == TRUE, so that the user can inspect // his/her VCS accounts even if they are not approved by the admin yet. 'load arguments' => array(TRUE), 'title callback' => 'versioncontrol_user_accounts_title_callback', 'title arguments' => array(1), 'page callback' => 'versioncontrol_account_page', 'page arguments' => array(1), 'access callback' => 'versioncontrol_private_account_access', 'access arguments' => array(1), 'file' => 'versioncontrol.pages.inc', 'weight' => 99, 'type' => MENU_LOCAL_TASK, ); // Autocomplete callback for Drupal usernames that have access to // the repo_id given in arg(3). (No need to fetch the full repository, // as the callback uses a raw & safe database query anyways.) $items['versioncontrol/user/autocomplete'] = array( 'title' => 'Version control user autocomplete', 'page callback' => 'versioncontrol_user_autocomplete', 'access callback' => 'versioncontrol_user_access', 'type' => MENU_CALLBACK, ); return $items; } /** * Implementation of hook_views_api(). * * @return array */ function versioncontrol_views_api() { return array( 'api' => 2, 'path' => drupal_get_path('module', 'versioncontrol'). '/includes/views', ); } /** * General helper function to get an array relating type id to string */ function versioncontrol_operation_types() { return array( VERSIONCONTROL_OPERATION_COMMIT => t('Commit'), VERSIONCONTROL_OPERATION_BRANCH => t('Branch'), VERSIONCONTROL_OPERATION_TAG => t('Tag'), ); } /** * Custom access callback, determining if the current user (or the one given * in @p $account, if set) is permitted to administer version control system * functionality. */ function versioncontrol_admin_access($account = NULL) { return user_access('administer version control systems', $account); } /** * Custom access callback, determining if the current user (or the one given * in @p $account, if set) is permitted to use version control system * functionality. */ function versioncontrol_user_access($account = NULL) { return user_access('use version control systems', $account) || user_access('administer version control systems', $account); } /** * Determine if we are operating in single or multi-backend mode, and set a * $conf variable accordingly. * */ function versioncontrol_determine_backend_mode() { $single = count(versioncontrol_get_backends()) <= 1; variable_set('versioncontrol_single_backend_mode', $single); } /** * Custom access callback, determining if the current user (or the one given * in @p $account, if set) is permitted to view version control account * settings of the user specified the first user id in @p $vcs_accounts. * (We take that parameter because it's what the '%versioncontrol_user_accounts' * wildcard returns.) */ function versioncontrol_private_account_access($vcs_accounts, $account = NULL) { $viewed_uid = key($vcs_accounts); if (!$viewed_uid) { return FALSE; } if (is_null($account)) { global $user; $account = clone $user; } return ($viewed_uid == $account->uid && user_access('use version control systems', $account)) || user_access('administer version control systems', $account); } /** * Title callback for the "user/%versioncontrol_user_accounts/edit/versioncontrol" tab. */ function versioncontrol_user_accounts_title_callback($accounts) { $repo_ids = array(); foreach ($accounts as $account) { $repo_ids[] = $account->repo_id; } $repositories = versioncontrol_repository_load_multiple($repo_ids); $vcses = array(); foreach ($repositories as $repository) { $vcses[$repository['vcs']] = TRUE; } if (count($vcses) == 1) { $repo = array_shift($repositories); return check_plain($repo->getBackend()->name); } return t('Repository accounts'); } /** * Implementation of hook_perm(). */ function versioncontrol_perm() { return array( 'administer version control systems', 'use version control systems', ); } // API functions start here. /** * Menu wildcard loader for repository ids ('%versioncontrol_repository'). */ function versioncontrol_repository_load($repo_id) { $repository = versioncontrol_repository_load_multiple(array($repo_id)); return empty($repository) ? FALSE : reset($repository); } /** * Load multiple versioncontrol repositories, given provided conditions and * options. * * This function statically caches a global VersioncontrolRepositoryController * entity controller, and uses it to load repositories. * * @param $ids * @param $conditions * @param $options * @return array * */ function versioncontrol_repository_load_multiple($ids = array(), $conditions = array(), $options = array()) { static $controller; if (!isset($controller)) { $controller = new VersioncontrolRepositoryController(); } return $controller->load($ids, $conditions, $options); } /** * Menu wildcard loader for '%versioncontrol_user_accounts': * Load and return all VCS accounts of a given user. * * @param $uid * Drupal user id of the user whose VCS accounts should be loaded. * @param $include_unauthorized * Will be passed on to VersioncontrolAccountCache::getInstance()->getAccounts(), see the * API documentation of that function. */ function versioncontrol_user_accounts_load($uid, $include_unauthorized = FALSE) { return versioncontrol_user_accounts_load_multiple(array($uid), array(), array('include unauthorized' => $include_unauthorized)); } function versioncontrol_user_accounts_load_multiple($ids = array(), $conditions = array(), $options = array()) { static $controller; if (!isset($controller)) { $controller = new VersioncontrolAccountController(); } return $controller->load($ids, $conditions, $options); } /** * Get a list of all backends with its detailed information. * * @param string $backend * Optional; the backend type's backend object to be returned. If not * specified, all backend types are returned. * * @return mixed * Either a structured array containing backend objects from each backend, * keyed on the unique string identifier corresponding to that backend (e.g. * 'cvs', 'svn'). * The backend objects are all descendents of VersioncontrolBackend. * * An example of the result array can be found in the FakeVCS example module. */ function versioncontrol_get_backends($backend = '', $reset = FALSE) { static $backends; if (!isset($backends) || $reset) { $backends = module_invoke_all('versioncontrol_backends'); } if (!empty($backend)) { return isset($backends[$backend]) ? $backends[$backend] : array(); } else { return $backends; } } /** * 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'; } /** * Retrieve additional information about the origin of a given set of items. * * @param $repository * The repository that the items are located in. * @param $items * An array of item arrays, for example as returned by * VersioncontrolOperation::getItems(). * * @return * This function does not have a return value; instead, it alters the * given item arrays and adds additional information about their origin. * The following elements will be set for all items whose source items * could be retrieved. * * - 'action': Specifies how the item was changed. * One of the predefined VERSIONCONTROL_ACTION_* values. * - 'source_items': An array with the previous revision(s) of the affected * item. Empty if 'action' is VERSIONCONTROL_ACTION_ADDED. The key for * all items in this array is the respective item path. * - 'replaced_item': The previous but technically unrelated item at the * same location as the current item. Only exists if this previous item * was deleted and replaced by a different one that was just moved * or copied to this location. * - 'line_changes': Only exists if line changes have been recorded for this * action - if so, this is an array containing the number of added lines * in an element with key 'added', and the number of removed lines in * the 'removed' key. */ function versioncontrol_fetch_source_items($repository, &$items) { if (empty($items)) { return; } $placeholders = array(); $ids = array(); $item_keys = array(); foreach ($items as $key => $item) { // If we don't yet know the item_revision_id (required for db queries), try // to retrieve it. If we don't find it, we can't fetch this item's sources. if ($item->fetchItemRevisionId()) { $placeholders[] = '%d'; $ids[] = $item->item_revision_id; $item_keys[$item->item_revision_id] = $key; } } if (empty($ids)) { return; } $result = db_query( 'SELECT item_revision_id, source_item_revision_id, action, line_changes_recorded, line_changes_added, line_changes_removed, path, revision, type FROM {versioncontrol_item_revisions} WHERE item_revision_id IN ('. implode(',', $placeholders) .')', $ids); while ($item_revision = db_fetch_object($result)) { $successor_key = $item_keys[$item_revision->item_revision_id]; if (!isset($items[$successor_key]->source_items)) { $items[$successor_key]->source_items = array(); } // FIXME this should pass through the factory (VersioncontrolBackend::buildEntity()), // but since we're getting rid of this whole 'source items' thing anyway, // just gonna make this note since the whole function will be going away. $item_backend = $repository->getBackend(); $item = new $backend->classes['item']( $item_revision->type, $item_revision->path, $item_revision->revision, NULL, $repository ); $item->selected_label = new stdClass(); $item->selected_label->get_from = 'other_item'; $item->selected_label->other_item = &$items[$successor_key]; $item->selected_label->other_item_tags = array('successor_item'); // Insert the item and its associated action into the successor item. if ($item_revision->action == VERSIONCONTROL_ACTION_REPLACED) { $items[$successor_key]->replaced_item = $item; } elseif ($item_revision->action == VERSIONCONTROL_ACTION_ADDED) { $items[$successor_key]['action'] = $item_revision->action; // Added items only join to an empty (NULL) item, ignore that one // instead of adding it to the source items. } else { $items[$successor_key]->action = $item_revision->action; $items[$successor_key]->source_items[$item->path] = $item; } // Add the lines-changed information if it has been recorded. // Only a single source item entry should hold this information, // so no emphasis is placed on merging it across multiple source items. if ($item_revision->line_changes_recorded) { $items[$successor_key]->line_changes = array( 'added' => $item_revision->line_changes_added, 'removed' => $item_revision->line_changes_removed, ); } } } /** * Retrieve additional information about the successors of a given set * of items. * * @param $repository * The repository that the items are located in. * @param $items * An array of item arrays, for example as returned by * VersioncontrolOperation::getItems(). * * @return * This function does not have a return value; instead, it alters the * given item arrays and adds additional information about their successors. * The following elements will be set for all items whose successor items * could be retrieved. * * - 'successor_items': An array with the previous revision(s) of the * affected item. The key for all items in this array is the respective * item path, and all of these items will have the 'actions' and * 'source_items' properties (as documented by * versioncontrol_fetch_source_items()) filled in. * - 'replaced_by_item': The succeeding but technically unrelated item at the * same location as the current item. Only exists if the original item * was deleted and replaced by a the succeeding one that was just moved * or copied to this location. */ function versioncontrol_fetch_successor_items($repository, &$items) { if (empty($items)) { return; } $placeholders = array(); $ids = array(); $item_keys = array(); foreach ($items as $key => $item) { // If we don't yet know the item_revision_id (required for db queries), try // to retrieve it. If we don't find it, we can't fetch this item's sources. if ($item->fetchItemRevisionId()) { $placeholders[] = '%d'; $ids[] = $item->item_revision_id; $item_keys[$item->item_revision_id] = $key; } } $result = db_query( 'SELECT item_revision_id, source_item_revision_id, action, path, revision, type FROM {versioncontrol_item_revisions} WHERE source_item_revision_id IN ('. implode(',', $placeholders) .')', $ids); while ($item_revision = db_fetch_object($result)) { $source_key = $item_keys[$item_revision->source_item_revision_id]; if (!isset($items[$source_key]['successor_items'])) { $items[$source_key]['successor_items'] = array(); } $item = array( 'path' => $item_revision->path, 'revision' => $item_revision->revision, 'type' => $item_revision->type, 'item_revision_id' => $item_revision->item_revision_id, ); $item['selected_label'] = new stdClass(); $item['selected_label']->get_from = 'other_item'; $item['selected_label']->other_item = &$items[$source_key]; $item['selected_label']->other_item_tags = array('source_item'); // Insert the item and its associated action into the source item. if ($item_revision->action == VERSIONCONTROL_ACTION_REPLACED) { $items[$source_key]['replaced_by_item'] = $item; } else { if ($item_revision->action == VERSIONCONTROL_ACTION_MERGED) { // If we've got a merge action then there are multiple source items, // the one that we know is not sufficient. (And of course, we won't // return an item with an incomplete 'source_items' property.) // So let's retrieve all of those source items. $successor_items = array($item['path'] => $item); versioncontrol_fetch_source_items($repository, $successor_items); $item = $successor_items[$item['path']]; } else { // No "merged" action: the original item is the only source item. $item['action'] = $item_revision->action; $item['source_items'] = array( $items[$source_key]['path'] => $items[$source_key], ); } $items[$source_key]['successor_items'][$item['path']] = $item; } } } /** * Retrieve the deepest-level directory path in the repository that is common * to all the given items, e.g. '/src' if there are two items with the paths * '/src/subdir/code.php' and '/src/README.txt', or '/' for items being located * at '/src/README.txt' and '/doc'. * * @param $items * An array of items of which the common directory path should be retrieved. * * @return * The common directory path of all given items. If no items were passed, * the root directory path '/' will be returned. */ function versioncontrol_get_common_directory_path($items) { if (empty($items)) { return '/'; } $paths = _versioncontrol_get_item_paths($items); $dirparts = explode('/', dirname(array_shift($paths))); foreach ($paths as $path) { $new_dirparts = array(); $current_dirparts = explode('/', dirname($path)); $mincount = min(count($dirparts), count($current_dirparts)); for ($i = 0; $i < $mincount; $i++) { if ($dirparts[$i] == $current_dirparts[$i]) { $new_dirparts[] = $dirparts[$i]; } else { break; } } $dirparts = $new_dirparts; } if (count($dirparts) == 1) { return '/'; } return implode('/', $dirparts); } function _versioncontrol_get_item_paths($items) { $paths = array(); // Store the paths as keys and return the array_keys() afterwards, // in order to get automatic removal of duplicates. foreach ($items as $item) { $paths[$item['path']] = TRUE; } return array_keys($paths); } /** * Return TRUE if @p $parent_path is a parent directory path of @p $child_path. */ function versioncontrol_path_contains($parent_path, $child_path) { if ($parent_path == $child_path) { return TRUE; } if ($parent_path != '/') { $parent_path .= '/'; } return (strpos($child_path, $parent_path) === 0); } /** * Return a the username of a VCS account. * * @param $uid * The Drupal user id of the user. If this is 0, the corresponding * Drupal user naturally can't be retrieved, with all implications for * displaying the username. * @param $username * The VCS username for the account. * @param $repository * The repository where this account is registered. * @param $options * An array of options that further influence the output format: * * - 'prefer_drupal_username': By default, this function tries to get the * corresponding Drupal user for the supplied uid and returns the * "real" username rather than the given one. If this is set to FALSE, * the given VCS username is always returned. * - 'format': By default, the username will be linked to the user page * (for Drupal users) or to the commit log page containing the user's * commits (for unassociated accounts). If 'plaintext', the username * will be returned without markup. * - 'include_repository_name': By default, an account that is not associated * to a Drupal user will get the repository name appended in order to * make for a unique account descriptor. If this option is set to TRUE, * the repository name will be suppressed anyways. */ function theme_versioncontrol_account_username($uid, $username, $repository, $options = array()) { $prefer_drupal_username = isset($options['prefer_drupal_username']) ? $options['prefer_drupal_username'] : TRUE; $format = isset($options['format']) ? $options['format'] : 'html'; if ($uid && $prefer_drupal_username) { $user = user_load($uid); if ($user && $prefer_drupal_username) { return ($format == 'html') ? theme('username', $user) : $user->name; } } if (!empty($options['include_repository_name'])) { $username = t('!user @ !repository', array( '!user' => $username, '!repository' => $repository['name'], )); } if ($format == 'html' && module_exists('commitlog')) { return l($username, commitlog_get_account_url($repository, $username)); } return $username; } /** * Return a table of contributors for the specified per-user statistics. * * @param $statistics * An array of statistics objects as returned by * versioncontrol_get_operation_statistics(), grouped by at least uid and * optionally repo_id/username columns. * @param $options * An array of optional further options. Currently, the only supported * array key is 'constraints' which contains the operation constraints used * to determine these statistics. If given, the "Commits" column in the table * will link to the contributor's commits in addition to displaying the * commit count. */ function theme_versioncontrol_user_statistics_table($statistics, $options = array()) { $header = array(t('User'), t('Last commit'), t('First commit'), t('Commits')); $rows = array(); foreach ($statistics as $user_stats) { $last_operation_date = t('!time ago', array( '!time' => format_interval(time() - $user_stats->last_operation_date, 1), )); $first_operation_date = t('!time ago', array( '!time' => format_interval(time() - $user_stats->first_operation_date, 1), )); $total_operations = format_plural( $user_stats->total_operations, '1 commit', '@count commits' ); if (isset($options['constraints']) && module_exists('commitlog')) { if (isset($user_stats->repo_id) && isset($user_stats->username)) { $options['constraints']['repo_ids'] = array($user_stats->repo_id); $options['constraints']['usernames'] = array($user_stats->username); } else { $options['constraints']['uids'] = array($user_stats->uid); } $total_operations = l($total_operations, commitlog_get_url($options['constraints'])); } $rows[] = array( theme('versioncontrol_user_statistics_account', $user_stats), $last_operation_date, $first_operation_date, $total_operations, ); } return theme('table', $header, $rows); } /** * Return a condensed item list of contributors for the specified per-user * statistics. An empty string is returned if the given array is empty. * * @param $statistics * An array of statistics objects as returned by * versioncontrol_get_operation_statistics(), grouped by at least uid and * optionally repo_id/username columns. */ function theme_versioncontrol_user_statistics_item_list($statistics, $more_link = NULL) { $items = array(); if (empty($statistics)) { return ''; } drupal_add_css(drupal_get_path('module', 'versioncontrol') . '/versioncontrol.css'); foreach ($statistics as $user_stats) { $item = '
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: