array( // The user-visible name of the VCS. 'name' => 'Subversion', // A short description of the VCS, if possible not longer than one or two sentences. 'description' => t('Subversion (SVN) is a code management system that supports file and directory revisions, atomic commits, serverless diffs and renaming items. Tags and branches are emulated by directory naming conventions, and merge functionality is still lacking.'), // A list of optional capabilities, in addition to the required retrieval // of detailed commit information. 'capabilities' => array( // Able to cancel commits if the committer lacks permissions // to commit to specific paths and/or branches. // Not implemented yet. VERSIONCONTROL_CAPABILITY_COMMIT_RESTRICTIONS, // Able to cancel branch or tag assignments if the committer lacks // permissions to create/update/delete those. // Not implemented yet. //VERSIONCONTROL_CAPABILITY_BRANCH_TAG_RESTRICTIONS, // Able to retrieve a file or its revision number based on a global // revision identifier. VERSIONCONTROL_CAPABILITY_ATOMIC_COMMITS, // The version control system assigns revisions not only to files // but also to directories. VERSIONCONTROL_CAPABILITY_DIRECTORY_REVISIONS, ), // An array listing which tables should be managed by Version Control API // instead of doing it manually in the backend. 'flags' => array( // versioncontrol_insert_repository() will automatically insert // array elements from $repository['svn_specific'] into // {versioncontrol_svn_repositories} and versioncontrol_get_repositories() // will automatically fetch it from there. VERSIONCONTROL_FLAG_AUTOADD_REPOSITORIES, ), ), ); } /** * Implementation of hook_menu(). */ function versioncontrol_svn_menu() { global $user; $items = array(); $items['admin/project/versioncontrol-repositories/update/svn/%versioncontrol_repository'] = array( 'title' => 'Fetch log', 'page callback' => 'versioncontrol_svn_update_repository_callback', 'page arguments' => array(5), 'access callback' => 'versioncontrol_admin_access', 'type' => MENU_CALLBACK, ); return $items; } /** * Implementation of hook_cron(): * Update repositories that have log fetching enabled. */ function versioncontrol_svn_cron() { $result = db_query("SELECT repo_id FROM {versioncontrol_svn_repositories} WHERE update_method = %d", VERSIONCONTROL_SVN_UPDATE_CRON); // Set timeout limit to 3600 seconds as it can take a long time to process // the log initially. (And hook_cron() might be called by poormanscron.) if (!ini_get('safe_mode')) { set_time_limit(3600); } while ($repo = db_fetch_object($result)) { $repository = versioncontrol_get_repository($repo->repo_id); if (isset($repository)) { _versioncontrol_svn_update_repository($repository); } } } /** * Implementation of [versioncontrol_backend]_format_revision_identifier(): * Get the user-visible version of a commit identifier a.k.a. 'revision', * as plaintext. */ function versioncontrol_svn_format_revision_identifier($repository, $revision, $format = 'full') { switch ($format) { case 'full': case 'short': default: return 'r'. $revision; } } /** * Implementation of [versioncontrol_backend]_get_selected_operation_item_label(): * Retrieve the tag or branch that applied to that item during the given * operation. The result of this function will be used for the 'selected_label' * property of each item, which is necessary to provide a starting point for * branch and tag navigation. */ function versioncontrol_svn_get_selected_label_from_operation($operation, $target_item) { // No branch/tag support yet, might be implemented in the future. return NULL; } /** * Implementation of [versioncontrol_backend]_get_selected_label_from_other_item(): * Retrieve a valid label (tag or branch) for a new @p $target_item that is * (hopefully) similar or related to that of the given @p $other_item which * already has a selected label assigned. If the backend cannot find a related * label, return any valid label. The result of this function will be used for * the selected label property of each item, which is necessary to preserve the * item state throughout navigational API functions. */ function versioncontrol_svn_get_selected_label_from_other_item($repository, $target_item, &$other_item, $other_item_tags = array()) { // No branch/tag support yet, might be implemented in the future. return NULL; } /** * Include the svnlib.inc helper library and initialize persistent * repository settings that will be reused in later svnlib function calls. */ function _versioncontrol_svn_init_svnlib(&$repository) { if (!empty($repository['repo']) && $repository['repo'] instanceof SvnInstance) { // Cut out early if the repo object has already been built. return; } if (!class_exists('SvnInstance')) { require_once(drupal_get_path('module', 'versioncontrol_svn') .'/new_svnlib/svn.php'); } $repository['repo'] = &svnlib_get_repository($repository['root']); $repo = &$repository['repo']; if (!empty($repository['svn_specific']['auth_username'])) { $repo->username($repository['svn_specific']['auth_username']) ->password(str_rot13($repository['svn_specific']['auth_password'])); } } /** * Implementation of [versioncontrol_backend]_get_item(): * Try to retrieve a given item in a repository. */ function versioncontrol_svn_get_item(&$repository, $path, $constraints = array()) { _versioncontrol_svn_init_svnlib($repository); $revision = empty($constraints['revision']) ? 'HEAD' : $constraints['revision']; $info = reset($repository['repo']->svn('info')->target($path)->revision($revision) ->execute()->seekPath($path)); if (empty($info)) { return NULL; } $item = array( 'path' => $info['path'], 'revision' => $info['created_rev'], 'type' => ($info['type'] == 'dir') ? VERSIONCONTROL_ITEM_DIRECTORY : VERSIONCONTROL_ITEM_FILE, ); return array('item' => $item, 'selected_label' => NULL); } /** * Implementation of [versioncontrol_backend]_get_directory_contents(): * Retrieve the set of files and directories that exist at a specified revision * in the given directory inside the repository. */ function versioncontrol_svn_get_directory_contents(&$repository, $directory_item, $recursive = FALSE) { _versioncontrol_svn_init_svnlib($repository); $info = $repository['repo']->svn('info')->target($directory_item['path']) ->revision($directory_item['revision']) ->depth($recursive ? 'infinite' : 'immediates') ->execute()->seekPath($path); $items = array(); foreach ($info as $item_info) { $items[$item_info['path']] = array( 'item' => array( 'path' => $item_info['path'], 'revision' => $item_info['created_rev'], 'type' => ($item_info['type'] == 'dir') ? VERSIONCONTROL_ITEM_DIRECTORY : VERSIONCONTROL_ITEM_FILE, ), 'selected_label' => NULL, ); } return $items; } /** * Implementation of [versioncontrol_backend]_export_file(): * Retrieve a copy of the contents of a given item in the repository. * (You won't get the original because repositories can often be remote.) */ function versioncontrol_svn_export_file(&$repository, $file_item, $destination) { _versioncontrol_svn_init_svnlib($repository); $success = svnlib_cat($destination, $repository['root'] . $file_item['path'], $file_item['revision'] ); return $success; } /** * Menu callback for * 'admin/project/versioncontrol-repositories/update/svn/%versioncontrol_repository': * Retrieve/validate the specified repository, fetch new commits by invoking * the svn executable, output messages and redirect back to the repository page. */ function versioncontrol_svn_update_repository_callback(&$repository) { if ($repository['svn_specific']['update_method'] == VERSIONCONTROL_SVN_UPDATE_CRON) { _versioncontrol_svn_init_svnlib($repository); // Set timeout limit to 3600 seconds as it can take a long time // to process the log initially. if (!ini_get('safe_mode')) { set_time_limit(3600); } $message = _versioncontrol_svn_update_repository($repository); drupal_set_message($message); } else { drupal_set_message(t('Repository update method does not allow manual updates, did not fetch anything.')); } drupal_goto('admin/project/versioncontrol-repositories'); } /** * Actually update the repository by fetching commits directly from * the repository, invoking the svn executable. * * @return * TRUE if the logs were updated, or FALSE if fetching and updating the logs * failed for whatever reason. */ function _versioncontrol_svn_update_repository(&$repository) { include_once(drupal_get_path('module', 'versioncontrol_svn') .'/versioncontrol_svn.log.inc'); return _versioncontrol_svn_log_update_repository($repository); } /** * Implementation of [versioncontrol_backend]_is_account_username_valid(): * Determine if the given repository allows a username to exist. * * It looks like Subversion can take any characters (any of the ascii printable * characters, at least) as a username; the issue is just safely escaping the * strings. * * At the very least [a-zA-Z0-9_-]+ should be fine, and would probably take care * of most of the cases worth worrying about. Adding [.@+] would get most email * addresses, and shouldn't be too much to worry about, security-wise. * * @param $repository * The repository where the the VCS account exists or will be located. * @param $username * The username to check. It is passed by reference so if the username is * valid but needs minor adaptions (such as cutting away unneeded parts) then * it the backend can modify it before returning the result. * * @return * TRUE if the username is valid, FALSE if not. */ function versioncontrol_svn_is_account_username_valid($repository, &$username) { return drupal_validate_utf8($username); }