array( // The user-visible name of the VCS. 'name' => 'CVS', // A short description of the VCS, if possible not longer than one or two sentences. 'description' => t('CVS (Concurrent Versions System) is a slightly older code management system that supports file revisions, tags and branches, but lacks atomic commits, advanced merge functionality and support for renaming items.'), // 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. VERSIONCONTROL_CAPABILITY_COMMIT_RESTRICTIONS, // Able to cancel branch or tag assignments if the committer lacks // permissions to create/update/delete those. VERSIONCONTROL_CAPABILITY_BRANCH_TAG_RESTRICTIONS, ), // 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['cvs_specific'] into // {versioncontrol_cvs_repositories} and versioncontrol_get_repositories() // will automatically fetch it from there. VERSIONCONTROL_FLAG_AUTOADD_REPOSITORIES, // versioncontrol_insert_commit() will automatically insert // array elements from $commit['cvs_specific'] into // {versioncontrol_cvs_commits} and versioncontrol_get_commits() // will automatically fetch it from there. VERSIONCONTROL_FLAG_AUTOADD_COMMITS, ), ), ); } /** * Implementation of hook_menu(). */ function versioncontrol_cvs_menu($may_cache) { global $user; $items = array(); $admin_access = user_access('administer version control systems'); if ($may_cache) { $items[] = array( 'path' => 'admin/project/versioncontrol-repositories/update/cvs', 'title' => t('Fetch log'), 'callback' => 'versioncontrol_cvs_update_repository_callback', 'access' => $admin_access, 'type' => MENU_CALLBACK, ); } return $items; } /** * Implementation of hook_cron(): * Update repositories that have log fetching enabled. */ function versioncontrol_cvs_cron() { $result = db_query("SELECT repo_id FROM {versioncontrol_cvs_repositories} WHERE update_method = %d", VERSIONCONTROL_CVS_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_cvs_update_repository($repository); } } } /** * Implementation of [versioncontrol_backend]_get_commit_actions(): * 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. * Path and branch will always be the same as in the current * item except for the VERSIONCONTROL_ACTION_MOVED, * VERSIONCONTROL_ACTION_COPIED and * VERSIONCONTROL_ACTION_MERGED actions. * 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.) */ function versioncontrol_cvs_get_commit_actions($commit) { $actions = array(); $result = db_query('SELECT item_revision_id, action, type, path, revision, lines_added, lines_removed FROM {versioncontrol_cvs_item_revisions} WHERE vc_op_id = %d', $commit['vc_op_id']); while ($item_revision = db_fetch_object($result)) { $action = array( 'action' => $item_revision->action, 'cvs_specific' => array( 'lines_added' => $item_revision->lines_added, 'lines_removed' => $item_revision->lines_removed, ), ); if ($item_revision->action != VERSIONCONTROL_ACTION_DELETED) { $action['current item'] = array( 'type' => $item_revision->type, 'path' => $item_revision->path, 'revision' => $item_revision->revision, 'cvs_specific' => array( 'item_revision_id' => $item_revision->item_revision_id, 'selected_branch_id' => $commit['cvs_specific']['branch_id'], 'selected_op' => $commit, ), ); } if ($item_revision->action != VERSIONCONTROL_ACTION_ADDED) { $source_revision = versioncontrol_cvs_get_previous_revision_number($item_revision->revision); $action['source items'] = array(array( 'type' => $item_revision->type, 'path' => $item_revision->path, 'revision' => isset($source_revision) ? $source_revision : '', 'cvs_specific' => array( 'item_revision_id' => $item_revision->item_revision_id, 'selected_branch_id' => $commit['cvs_specific']['branch_id'], 'selected_op' => $commit, ), )); } $actions[$item_revision->path] = $action; } return $actions; } /** * Implementation of [versioncontrol_backend]_get_commit_statistics(): * 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. * * @param $commit * The commit to retrieve statistics about * * @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 corresponding * array values are again structured arrays and consist of elements with the following keys: * * - 'lines_added': Number of lines added in this action * - 'lines_removed': Number of lines removed in this action */ function versioncontrol_cvs_get_commit_statistics($commit, $commit_actions = NULL) { if ($commit_actions == NULL) { $commit_actions = versioncontrol_cvs_get_commit_actions($commit); } $total_lines_added = 0; $total_lines_removed = 0; $per_action = array(); foreach ($commit_actions as $path => $action) { $per_action[$path] = array(); $per_action[$path]['lines_added'] = $action['cvs_specific']['lines_added']; $total_lines_added += $per_action[$path]['lines_added']; $per_action[$path]['lines_removed'] = $action['cvs_specific']['lines_removed']; $total_lines_removed += $action['cvs_specific']['lines_removed']; } return array( 'lines_added' => $total_lines_added, 'lines_removed' => $total_lines_removed, 'action_count' => count($per_action), 'per_action_statistics' => $per_action, ); } /** * Implementation of [versioncontrol_backend]_get_directory_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_cvs_get_directory_item($operation) { $item = array( 'type' => VERSIONCONTROL_ITEM_DIRECTORY, 'path' => $operation['directory'], 'revision' => '', 'cvs_specific' => array( 'selected_op' => $operation, ), ); if (isset($operation['cvs_specific']['branch_id'])) { // it's a commit or branch $item['cvs_specific']['selected_branch_id'] = $operation['cvs_specific']['branch_id']; } return $item; } /** * Implementation of [versioncontrol_backend]_get_commit_branches(): * Retrieve the branches that have been affected by the given commit. * * @return * An array of strings that identify a branch in the respective repository, * or an empty array if no branches were affected at all. (For CVS, there * should always be a exactly one branch in the resulting array.) */ function versioncontrol_cvs_get_commit_branches($commit) { if (!isset($commit['cvs_specific']['branch_id'])) { return array(); } $branch = versioncontrol_get_branch($commit['cvs_specific']['branch_id']); if (!isset($branch)) { return array(); // should only happen in case of database inconsistencies } return array($branch['branch_name']); } /** * 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.) */ function versioncontrol_cvs_get_branched_items($branch) { $items = array(); $result = db_query('SELECT ir.item_revision_id, ir.type, ir.path, ir.revision FROM {versioncontrol_cvs_item_branch_points} ib INNER JOIN {versioncontrol_cvs_item_revisions} ir ON ib.item_revision_id = ir.item_revision_id WHERE ib.vc_op_id = %d', $branch['vc_op_id']); while ($item_revision = db_fetch_object($result)) { $items[] = array( 'type' => $item_revision->type, 'path' => $item_revision->path, 'revision' => $item_revision->revision, 'cvs_specific' => array( 'item_revision_id' => $item_revision->item_revision_id, 'selected_branch_id' => $branch['cvs_specific']['branch_id'], 'selected_op' => $branch, ), ); } return $items; } /** * 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 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.) */ function versioncontrol_cvs_get_tagged_items($tag) { $items = array(); $result = db_query('SELECT ir.item_revision_id, ir.type, ir.path, ir.revision FROM {versioncontrol_cvs_item_tags} it INNER JOIN {versioncontrol_cvs_item_revisions} ir ON it.item_revision_id = ir.item_revision_id WHERE it.vc_op_id = %d', $tag['vc_op_id']); while ($item_revision = db_fetch_object($result)) { $items[] = array( 'type' => $item_revision->type, 'path' => $item_revision->path, 'revision' => $item_revision->revision, 'cvs_specific' => array( 'item_revision_id' => $item_revision->item_revision_id, 'selected_op' => $tag, ), ); } return $items; } /** * Implementation of [versioncontrol_backend]_get_current_item_branch(): * 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. The main branch ('HEAD' for CVS) * 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_cvs_get_current_item_branch($repository, $item) { if (!isset($item['cvs_specific']['selected_branch_id'])) { return NULL; } $branch = versioncontrol_get_branch($item['cvs_specific']['selected_branch_id']); if (!isset($branch)) { return NULL; } return $branch['branch_name']; } /** * Implementation of [versioncontrol_backend]_get_current_item_tag(): * Retrieve the current tag of this item. * * @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_cvs_get_current_item_tag($repository, $item) { if ($item['cvs_specific']['selected_op']['type'] != VERSIONCONTROL_OPERATION_TAG) { return NULL; } return $item['cvs_specific']['selected_op']; } /** * Implementation of [vcs_backend]_get_parent_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_cvs_get_parent_item($repository, $item, $parent_path = NULL) { if (!isset($parent_path)) { $item['path'] = dirname($item['path']); return $item; } else if (strpos($item['path'] .'/', $parent_path .'/') !== FALSE) { $item['path'] = $parent_path; return $item; } return NULL; } /** * CVS tells us that the file was modified (has a previous revision) even if * it has been deleted before. Technically that's correct, but we'd like to * have it show up as "added", so this function tries to be smart and alters * a commit actions array accordingly if the database tells us that the * previous revision of a file was dead. */ function _versioncontrol_cvs_fix_commit_actions($commit, &$commit_actions) { foreach ($commit_actions as $path => $action) { if ($action['action'] != VERSIONCONTROL_ACTION_MODIFIED) { continue; } $repo_id = isset($commit['repository']) ? $commit['repository']['repo_id'] : $commit['repo_id']; $count = db_result(db_query( "SELECT COUNT(*) FROM {versioncontrol_cvs_item_revisions} ir INNER JOIN {versioncontrol_operations} op ON ir.vc_op_id = op.vc_op_id WHERE op.repo_id = %d AND op.type = %d AND ir.type = %d AND ir.path = '%s' AND ir.revision = '%s'", $repo_id, VERSIONCONTROL_OPERATION_COMMIT, VERSIONCONTROL_ITEM_FILE_DELETED, $path, $action['source items'][0]['revision'] )); if ($count > 0) { $commit_actions[$path]['action'] = VERSIONCONTROL_ACTION_ADDED; unset($commit_actions[$path]['source items']); } } } /** * Implementation of [versioncontrol_backend]_commit(): * Manage (insert or delete) additional commit data in the database. * * @param $op * Either 'insert' when the commit is in the process of being created, * or 'delete' if it will be deleted after this function has been called. * @param $commit * A single commit array, like the ones returned * by versioncontrol_get_commits(). * @param $commit_actions * A structured array containing the exact details of what happened to * each item in this commit. The structure of this array is the same as * the return value of versioncontrol_get_commit_actions(). */ function versioncontrol_cvs_commit($op, $commit, $commit_actions) { switch ($op) { case 'insert': foreach ($commit_actions as $path => $action) { $revision = ''; // If available, get item type and revision from the contained items. if (isset($action['current item'])) { $type = $action['current item']['type']; $revision = $action['current item']['revision']; } else { // no current item -> file has been deleted $type = VERSIONCONTROL_ITEM_FILE_DELETED; // Calculate the current revision from the (only, in CVS) source item // by simply increasing the last identifier part by 1. // (That's how CVS does it as well.) $source_revision = $action['source items'][0]['revision']; $parts = explode('.', $source_revision); $last_part = array_pop($parts); $last_part = ((int) $last_part) + 1; $parts[] = (string) $last_part; $revision = implode('.', $parts); } $item_revision_id = db_next_id('{versioncontrol_cvs_item_revisions}_item_revision_id'); db_query( "INSERT INTO {versioncontrol_cvs_item_revisions} (item_revision_id, vc_op_id, type, path, revision, action, lines_added, lines_removed) VALUES (%d, %d, %d, '%s', '%s', %d, %d, %d)", $item_revision_id, $commit['vc_op_id'], $type, $path, $revision, $action['action'], $action['cvs_specific']['lines_added'], $action['cvs_specific']['lines_removed'] ); } break; case 'delete': $result = db_query('SELECT item_revision_id FROM {versioncontrol_cvs_item_revisions} WHERE vc_op_id = %d', $commit['vc_op_id']); while ($revision = db_fetch_object($result)) { db_query('DELETE FROM {versioncontrol_cvs_item_tags} WHERE item_revision_id = %d', $revision->item_revision_id); db_query('DELETE FROM {versioncontrol_cvs_item_branch_points} WHERE item_revision_id = %d', $revision->item_revision_id); } db_query('DELETE FROM {versioncontrol_cvs_item_revisions} WHERE vc_op_id = %d', $commit['vc_op_id']); break; } } /** * Implementation of [versioncontrol_backend]_branch_operation(): * Manage (insert or delete) additional branch operation data in the database. * * @param $op * Either 'insert' when the branch operation is in the process of being created, * or 'delete' if it will be deleted after this function has been called. * @param $branch * A single branch operation array, like the one returned * by versioncontrol_get_branch_operation(). * @param $branched_items * An array of all items that are affected by the branching operation. * Compared to standard item arrays, the ones in here may not have the * 'revision' element set (however, the CVS backend always provides those) * and can optionally contain a 'source branch' element that specifies * the original branch name of this item. * (For $op == 'delete', 'source branch' is never set.) * An empty $branched_items array means that the whole repository has been * branched (which is not used for CVS, as branches/tags are always assigned * to specific files). */ function versioncontrol_cvs_branch_operation($op, $branch, $branched_items) { _versioncontrol_cvs_branch_or_tag_operation( $op, $branch, $branched_items, 'item_branch_points' ); } /** * Implementation of [versioncontrol_backend]_tag_operation(): * Manage (insert or delete) additional tag operation data in the database. * * @param $op * Either 'insert' when the tag operation is in the process of being created, * or 'delete' if it will be deleted after this function has been called. * @param $tag * A single tag operation array, like the one returned * by versioncontrol_get_tag_operation(). * @param $tagged_items * An array of all items that are affected by the tagging operation. * Compared to standard item arrays, the ones in here may not have the * 'revision' element set (however, the CVS backend always provides those) * and can optionally contain a 'source branch' element that specifies * the original branch name of this item. * (For $op == 'move' or $op == 'delete', 'source branch' is never set.) * An empty $tagged_items array means that the whole repository has been * tagged (which is not used for CVS, as branches/tags are always assigned * to specific files). */ function versioncontrol_cvs_tag_operation($op, $tag, $tagged_items) { _versioncontrol_cvs_branch_or_tag_operation( $op, $tag, $tagged_items, 'item_tags' ); } /** * The implementation of the branch and tag operation hooks is essentially the * same (just different table names), so let's share the code in this function. */ function _versioncontrol_cvs_branch_or_tag_operation($op, $branch_or_tag, $items, $table_name) { switch ($op) { case 'insert': foreach ($items as $item) { $result = db_query("SELECT item_revision_id FROM {versioncontrol_cvs_item_revisions} WHERE path = '%s' AND revision = '%s'", $item['path'], $item['revision']); while ($revision = db_fetch_object($result)) { db_query('INSERT INTO {versioncontrol_cvs_'. $table_name .'} (vc_op_id, item_revision_id) VALUES (%d, %d)', $branch_or_tag['vc_op_id'], $revision->item_revision_id); } } break; case 'delete': db_query('DELETE FROM {versioncontrol_cvs_'. $table_name .'} WHERE vc_op_id = %d', $branch_or_tag['vc_op_id']); break; } } /** * Implementation of [versioncontrol_backend]_account(): * Manage (insert, update or delete) additional CVS user account data * in the database. * * @param $op * Either 'insert' when the account is in the process of being created, * or 'update' when username or additional module data change, * or 'delete' if it will be deleted after this function has been called. * @param $uid * The Drupal user id corresponding to the VCS account. * @param $username * The VCS specific username (a string). * @param $repository * The repository where the user has its VCS account. * @param $additional_data * An array of additional author information. */ function versioncontrol_cvs_account($op, $uid, $username, $repository, $additional_data = array()) { $cvs_specific = $additional_data['cvs_specific']; switch ($op) { case 'insert': if (!isset($cvs_specific) || !isset($cvs_specific['password'])) { drupal_set_message(t('Error: no CVS password given on account creation!'), 'error'); return; } db_query("INSERT INTO {versioncontrol_cvs_accounts} (uid, repo_id, password) VALUES (%d, %d, '%s')", $uid, $repository['repo_id'], $cvs_specific['password']); break; case 'update': if (!isset($cvs_specific) || !isset($cvs_specific['password'])) { return; // the user didn't update the password in the process. } db_query("UPDATE {versioncontrol_cvs_accounts} SET password = '%s' WHERE uid = %d AND repo_id = %d", $cvs_specific['password'], $uid, $repository['repo_id']); if (!user_access('administer version control systems')) { // Admins get "The account has been updated successfully" anyways. drupal_set_message(t('The CVS password has been updated successfully.')); } break; case 'delete': db_query('DELETE FROM {versioncontrol_cvs_accounts} WHERE uid = %d AND repo_id = %d', $uid, $repository['repo_id']); break; } } /** * Implementation of [vcs_backend]_import_accounts(): * Import accounts into a repository, given text data from the accounts file. * No accounts are deleted, new accounts are inserted, and existing accounts * are updated with imported ones. * * @param $repository * The repository where the accounts will be imported. * @param $data * The contents of the "account data" text area where the user has to * enter/copy the contents of the version control system's accounts file. */ function versioncontrol_cvs_import_accounts($repository, $data) { $lines = explode("\n", $data); $names = array(); foreach ($lines as $line) { if (preg_match('/^\s*(#.*)?$/', $line)) { // filter out empty and commented lines continue; } // Extract the account information and create or update the user accounts. list($username, $password, $run_as_user) = explode(':', $line); if (!empty($username) && !empty($password)) { $additional_data = array( 'cvs_specific' => array('password' => $password), ); $uid = versioncontrol_get_account_uid_for_username($repository['repo_id'], $username, TRUE); if (isset($uid)) { versioncontrol_update_account($repository, $uid, $username, $additional_data); $names[] = t('updated !username', array('!username' => $username)); } else { $uid = db_result(db_query("SELECT uid FROM {users} WHERE name = '%s'", $username)); if ($uid) { versioncontrol_insert_account($repository, $uid, $username, $additional_data); $names[] = t('added !username', array('!username' => $username)); } else { $names[] = t('didn\'t add !username (no matching Drupal username exists)', array('!username' => $username)); } } } } if (empty($names)) { drupal_set_message(t('Failed to import CVS accounts.'), 'error'); } else { drupal_set_message(theme('item_list', $names, t('The import of CVS accounts has been completed successfully:'))); } } /** * Implementation of [vcs_backend]_export_accounts(): * Export accounts of a repository to text data that is suitable for * copying to the version control system's accounts file. * * @param $repository * The repository whose accounts will be exported. * @param $accounts * The list (array) of accounts that should be exported, given in the same * format as the return value of versioncontrol_get_accounts(). * All accounts in this list are from the above repository. * * @return * The exported textual representation of the account list. */ function versioncontrol_cvs_export_accounts($repository, $accounts) { if (empty($accounts)) { return '# '. t('no user accounts available to export'); } $accounts_flat = array(); $uid_constraints = array(); $params = array($repository['repo_id']); foreach ($accounts as $uid => $usernames_per_repository) { foreach ($usernames_per_repository as $repo_id => $username) { $accounts_flat[$uid] = array('uid' => $uid, 'username' => $username); $uid_constraints[] = 'uid = %d'; $params[] = $uid; } } $result = db_query('SELECT uid, password FROM {versioncontrol_cvs_accounts} WHERE repo_id = %d AND ('. implode(' OR ', $uid_constraints) .')', $params); while ($account = db_fetch_object($result)) { $accounts_flat[$account->uid]['password'] = $account->password; } $run_as_user = ''; if (!empty($repository['run_as_user'])) { $run_as_user = ':'. $repository['run_as_user']; } $data = ''; foreach ($accounts_flat as $uid => $account) { $data .= '# '. url('user/'. $uid, NULL, NULL, TRUE) ."\n"; $data .= $account['username'] .':'. $account['password'] . $run_as_user ."\n\n"; } return $data; } /** * Menu callback for 'admin/project/versioncontrol-repositories/update/cvs' * (expecting a $repo_id as one more path argument): * Retrieve/validate the specified repository, fetch new commits, tags * and branches by invoking the cvs executable, output messages and * redirect back to the repository page. */ function versioncontrol_cvs_update_repository_callback($repo_id) { if (is_numeric($repo_id)) { $repository = versioncontrol_get_repository($repo_id); if (isset($repository)) { $update_method = $repository['cvs_specific']['update_method']; } } if (isset($update_method) && $update_method == VERSIONCONTROL_CVS_UPDATE_CRON) { // 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); } if (_versioncontrol_cvs_update_repository($repository)) { drupal_set_message(t('Fetched new log entries.')); } } else { // $repo_id is not a number or doesn't correlate to any repository. drupal_set_message(t('No such repository, did not fetch anything.')); } drupal_goto('admin/project/versioncontrol-repositories'); } /** * Actually update the repository by fetching commits and other stuff * directly from the repository, invoking the cvs executable. * * @return * TRUE if the logs were updated, or FALSE if fetching and updating the logs * failed for whatever reason. */ function _versioncontrol_cvs_update_repository(&$repository) { include_once(drupal_get_path('module', 'versioncontrol_cvs') .'/versioncontrol_cvs.log.inc'); return _versioncontrol_cvs_log_update_repository($repository); } /** * Calculate the previous revision number of file under version control, * given the current revision. This can be done in a purely programmatical way * because of the quite special numbering scheme of CVS (so no database queries * need to be done in order to get the result). * * @return * The previous revision number (e.g. "1.1" for a given "1.2", * or "1.3" for a given "1.3.2.1"), or NULL if $current_revision is "1.1" * (which obviously means that there is no previous revision number). */ function versioncontrol_cvs_get_previous_revision_number($current_revision) { if ($current_revision === '1.1') { return NULL; } $parts = explode('.', $current_revision); $last_part = array_pop($parts); // For the first commit to a new branch, cut off the two rightmost parts // in order to get the previous revision number. (e.g. "1.3.4.1" is the // first commit on branch 1.3.0.4 which originated in "1.3" from HEAD.) if ($last_part === '1') { array_pop($parts); // e.g., removes the "2" from the example above return implode('.', $parts); } // If we don't have a "1" as last part, we can just decrease this by one // and implode it again to get the previous revision. $last_part = ((int) $last_part) - 1; $parts[] = (string) $last_part; return implode('.', $parts); }