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, ), // 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($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/svn', 'title' => t('Fetch log'), 'callback' => 'versioncontrol_svn_update_repository_callback', 'access' => $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]_commit(): * Manage (insert or delete) additional commit data in the database. */ function versioncontrol_svn_commit($op, $commit, $commit_actions) { switch ($op) { case 'insert': foreach ($commit_actions as $path => $action) { // Default values in case source and/or replaced items don't exist. $current_item_revision_id = 0; $source_item_revision_id = 0; $replaced_item_revision_id = 0; // Preparation: store the new item in the database. if (isset($action['current item'])) { $action['current item'] = _versioncontrol_svn_insert_item_revision( $commit['repository'], $action['current item'] ); $current_item_revision_id = $action['current item']['svn_specific']['item_revision_id']; } // Get item revision ids of source and replaced item (if those exist) // and create them if the database doesn't already have entries. if (isset($action['source items'][0])) { $action['source items'][0] = _versioncontrol_svn_ensure_item_revision( $commit['repository'], $action['source items'][0] ); $source_item_revision_id = $action['source items'][0]['svn_specific']['item_revision_id']; } if (isset($action['replaced item'])) { $action['replaced item'] = _versioncontrol_svn_ensure_item_revision( $commit['repository'], $action['replaced item'] ); $replaced_item_revision_id = $action['replaced item']['svn_specific']['item_revision_id']; } // All information gathered, insert it into the database! db_query( "INSERT INTO {versioncontrol_svn_commit_actions} (vc_op_id, item_revision_id, action, source_item_revision_id, replaced_item_revision_id) VALUES (%d, %d, %d, %d, %d)", $commit['vc_op_id'], $current_item_revision_id, $action['action'], $source_item_revision_id, $replaced_item_revision_id ); } break; case 'delete': db_query('DELETE FROM {versioncontrol_svn_commit_actions} WHERE vc_op_id = %d', $commit['vc_op_id']); break; } } /** * Implementation of [versioncontrol_backend]_get_commit_actions(): * Retrieve detailed information about what happened in a single commit. */ function versioncontrol_svn_get_commit_actions($commit) { $commit_actions = array(); $result = db_query( 'SELECT item_revision_id, action, source_item_revision_id, replaced_item_revision_id FROM {versioncontrol_svn_commit_actions} WHERE vc_op_id = %d', $commit['vc_op_id']); while ($item_revision = db_fetch_object($result)) { $action = array('action' => $item_revision->action); $item_revision_ids = array( $item_revision->item_revision_id, $item_revision->source_item_revision_id, $item_revision->replaced_item_revision_id, ); $items = _versioncontrol_svn_get_item_revisions_by_id($item_revision_ids); if (isset($items[$item_revision->item_revision_id])) { $action['current item'] = $items[$item_revision->item_revision_id]; } if (isset($items[$item_revision->source_item_revision_id])) { $action['source items'] = array( $items[$item_revision->source_item_revision_id] ); } if (isset($items[$item_revision->replaced_item_revision_id])) { $action['replaced item'] = $items[$item_revision->replaced_item_revision_id]; } $commit_actions[$action['current item']['path']] = $action; } return $commit_actions; } function _versioncontrol_svn_get_item_revisions_by_id($item_revision_ids) { $item_revision_ids = array_filter($item_revision_ids); if (empty($item_revision_ids)) { return array(); } $placeholders = array(); foreach ($item_revision_ids as $id) { $placeholders[] = '%d'; } $result = db_query( 'SELECT item_revision_id, path, revision, type FROM {versioncontrol_svn_item_revisions} WHERE item_revision_id IN ('. implode(',', $placeholders) .')', $item_revision_ids ); $items = array(); while ($item_revision = db_fetch_object($result)) { $items[$item_revision->item_revision_id] = array( 'path' => $item_revision->path, 'revision' => $item_revision->revision, 'type' => $item_revision->type, 'svn_specific' => array( 'item_revision_id' => $item_revision->item_revision_id, ), ); } return $items; } function _versioncontrol_svn_ensure_item_revision($repository, $item) { $result = db_query( "SELECT item_revision_id, type FROM {versioncontrol_svn_item_revisions} WHERE repo_id = %d AND path = '%s' AND revision = '%s'", $repository['repo_id'], $item['path'], $item['revision'] ); while ($item_revision = db_fetch_object($result)) { // Replace / fill in properties that were not in the WHERE condition. $item['type'] = $item_revision->type; $item['svn_specific']['item_revision_id'] = $item_revision->item_revision_id; } // The item doesn't yet exist in the database, so create it. return _versioncontrol_svn_insert_item_revision($repository, $item); } function _versioncontrol_svn_insert_item_revision($repository, $item) { $item_revision_id = db_next_id('{versioncontrol_svn_item_revisions}_item_revision_id'); db_query( "INSERT INTO {versioncontrol_svn_item_revisions} (item_revision_id, repo_id, path, revision, type) VALUES (%d, %d, '%s', '%s', %d)", $item_revision_id, $repository['repo_id'], $item['path'], $item['revision'], $item['type'] ); $item['svn_specific']['item_revision_id'] = $item_revision_id; return $item; } /** * 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 operation or tag operation. In other words, this function * gets you the item for $operation['directory']. */ function versioncontrol_svn_get_directory_item($operation) { return array( 'type' => VERSIONCONTROL_ITEM_DIRECTORY, 'path' => $operation['directory'], 'revision' => $operation['revision'], 'svn_specific' => array(), ); } /** * Implementation of [versioncontrol_backend]_get_commit_branches(): * Retrieve the branches that have been affected by the given commit. */ function versioncontrol_svn_get_commit_branches($commit) { return array(); // no branch emulation for now, might follow later on } /** * Implementation of [versioncontrol_backend]_get_branched_items(): * Retrieve the set of items that were affected by a branch operation. */ function versioncontrol_svn_get_branched_items($branch) { return array(); // no branch emulation for now, might follow later on } /** * Implementation of [versioncontrol_backend]_get_tagged_items(): * Retrieve the set of items that were affected by a tag operation. */ function versioncontrol_svn_get_tagged_items($tag) { return array(); // no tag emulation for now, might follow later on } /** * Implementation of [versioncontrol_backend]_get_current_item_branch(): * Retrieve the current branch that this item is in. */ function versioncontrol_svn_get_current_item_branch($repository, $item) { return NULL; // no branch emulation for now, might follow later on } /** * Implementation of [versioncontrol_backend]_get_current_item_tag(): * Retrieve the current tag that has been applied to this item. */ function versioncontrol_svn_get_current_item_tag($repository, $item) { return NULL; // no tag emulation for now, might follow later on } /** * Implementation of [versioncontrol_backend]_repository(): * Delete additional Subversion repository data from the database. */ function versioncontrol_svn_repository($op, $repository) { if ($op == 'delete') { db_query('DELETE FROM {versioncontrol_svn_item_revisions} WHERE repo_id = %d', $repository['repo_id']); } } /** * Menu callback for 'admin/project/versioncontrol-repositories/update/svn' * (expecting a $repo_id as one more path argument): * 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($repo_id) { if (is_numeric($repo_id)) { $repository = versioncontrol_get_repository($repo_id); if (isset($repository)) { $update_method = $repository['svn_specific']['update_method']; } } if (isset($update_method) && $update_method == VERSIONCONTROL_SVN_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); } $message = _versioncontrol_svn_update_repository($repository); drupal_set_message($message); } 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 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); }