'Commit messages', 'page callback' => 'commitlog_operations_page', 'access arguments' => array($view_access), 'type' => MENU_SUGGESTED_ITEM, ); $items['commitlog/%/%'] = array( 'title' => 'Commit messages', 'page callback' => 'commitlog_operations_filtered_page', 'page arguments' => array(1, 2), 'access arguments' => array($view_access), 'type' => MENU_CALLBACK, ); $items['commitlog/feed'] = array( 'title' => 'Commit messages', 'page callback' => 'commitlog_operations_rss', 'access arguments' => array($view_access), 'type' => MENU_CALLBACK, ); // Search pages: /* // until this works, no sense displaying this page // see http://drupal.org/node/59659 $items['search/commitlog'] = array( 'title' => 'Commit messages', 'page callback' => 'commitlog_operations_page_search', 'access arguments' => array($view_access), 'type' => MENU_LOCAL_TASK, 'weight' => 2, ); */ // If the user has a VCS account, add a 'track commit messages' tab to the tracker page. $items['user/%user/track/code'] = array( 'title' => 'Track code', 'page callback' => 'commitlog_account_tracker', 'page arguments' => array(1), 'access callback' => 'commitlog_account_tracker_access', 'access arguments' => array(1), 'type' => MENU_LOCAL_TASK, 'weight' => 2, ); return $items; } /** * Custom access callback for the 'user/%user/track/code' page. */ function commitlog_account_tracker_access($account) { if (!user_access('access commit messages')) { return FALSE; } $vcs_accounts = versioncontrol_get_accounts(array('uids' => array($account->uid))); return !empty($vcs_accounts); } /** * Implementation of hook_perm(). */ function commitlog_perm() { return array('access commit messages'); } /** * Implementation of hook_theme(). */ function commitlog_theme() { return array( 'commitlog_operations_page' => array( 'arguments' => array('constraints' => NULL), ), 'commitlog_rss' => array( 'arguments' => array('constraints' => NULL), ), 'commitlog_operation_html' => array( 'arguments' => array('variables' => NULL), ), 'commitlog_operation_plaintext' => array( 'arguments' => array('variables' => NULL), ), 'commitlog_repository' => array( 'arguments' => array('repository' => NULL, 'format' => 'html'), ), 'commitlog_operation_location' => array( 'arguments' => array('operation' => NULL, 'operation_items' => NULL, 'directory_item' => NULL, 'format' => 'html'), ), 'commitlog_revision_identifier' => array( 'arguments' => array('operation' => NULL, 'format' => 'html'), ), 'commitlog_vc_op_id' => array( 'arguments' => array('vc_op_id' => NULL, 'text' => NULL, 'format' => 'html'), ), 'commitlog_operation_message' => array( 'arguments' => array('operation' => NULL, 'format' => 'html'), ), 'commitlog_operation_caption' => array( 'arguments' => array('variables' => NULL, 'options' => array()), ), 'commitlog_operation_subject' => array( 'arguments' => array('variables' => NULL, 'options' => array()), ), 'commitlog_label' => array( 'arguments' => array('label' => NULL, 'options' => array()), ), 'commitlog_operation_items' => array( 'arguments' => array('operation' => NULL, 'operation_items' => NULL, 'format' => 'html'), ), 'commitlog_item' => array( 'arguments' => array('repository' => NULL, 'item' => NULL, 'path_override' => NULL, 'format' => 'html'), ), 'commitlog_item_revision' => array( 'arguments' => array('repository' => NULL, 'item' => NULL, 'format' => 'html'), ), 'commitlog_diff_link' => array( 'arguments' => array('operation' => NULL, 'item' => NULL), ), 'commitlog_project' => array( 'arguments' => array('repository' => NULL, 'project' => NULL, 'format' => 'html'), ), ); } /** * Implementation of hook_form_alter(): * Add a fieldset for to the general settings form so that the user can * enable/disable admin notification mails and configure the pager limit. */ function commitlog_form_alter(&$form, $form_state, $form_id) { if ($form['#id'] == 'versioncontrol-settings-form') { $form['#validate'][] = 'commitlog_settings_validate'; $form['#submit'][] = 'commitlog_settings_submit'; $form['commitlog'] = array( '#type' => 'fieldset', '#title' => t('Commit messages'), '#collapsible' => TRUE, '#collapsed' => TRUE, '#weight' => 3, ); $form['commitlog']['commitlog_send_notification_mails'] = array( '#type' => 'checkbox', '#title' => 'Send commit notification mails to the VCS administrator', '#description' => 'If this is enabled, each commit that is recorded on this site causes a notification mail to be sent to the VCS administrator\'s e-mail address. This mail includes all relevant commit data like the commit message and the files and directories that were changed. Note that disabling the Commit Log module also disables notification mails.', '#default_value' => variable_get('commitlog_send_notification_mails', 0), ); $form['commitlog']['commitlog_pager'] = array( '#type' => 'textfield', '#title' => t('Number of commits per page'), '#description' => t('Controls how many commits can be shown on a single page of the !commitlog. Paging is used when there are more commits to show than specified by this value.', array('!commitlog' => l(t('commit message log'), 'commitlog'))), '#default_value' => variable_get('commitlog_pager', 10), ); } } /** * Validation handler for the settings form: * Make sure that the pager limit is a positive number. */ function commitlog_settings_validate($form, &$form_state) { if (!is_numeric($form_state['values']['commitlog_pager']) || $form_state['values']['commitlog_pager'] <= 0) { form_error($form['commitlog']['commitlog_pager'], t('The number of commits per page needs to be a positive number.') ); } } /** * Submit handler for the settings form. */ function commitlog_settings_submit($form, &$form_state) { variable_set('commitlog_pager', $form_state['values']['commitlog_pager']); variable_set('commitlog_send_notification_mails', $form_state['values']['commitlog_send_notification_mails']); } /** * Implementation of hook_versioncontrol_operation(): * If enabled, send out a notification mail to the VCS admin. */ function commitlog_versioncontrol_operation($op, $operation, $operation_items) { if ($op == 'insert') { if (variable_get('commitlog_send_notification_mails', 0)) { $mailto = variable_get('versioncontrol_email_address', 'versioncontrol@example.com'); commitlog_notification_mail($mailto, $operation, $operation_items); } } } /** * Send out a notification mail to the given mail address, or just construct * the message if @p $send is TRUE. * * @return * The $message array structure containing all details of the message - * same result value as is returned by drupal_mail(). If already sent * ($send == TRUE), then the 'result' element will contain the success * indicator of the e-mail, failure being already written to the watchdog. * (Success means nothing more than the message being accepted at php-level, * which still doesn't guarantee it to be delivered.) */ function commitlog_notification_mail($mailto, $operation, $operation_items, $send = TRUE, $language = NULL) { if (!isset($language)) { $language = language_default(); } return _commitlog_notification_mail($mailto, $operation, $operation_items, $language, $send); } /** * Send out a notification mail to the given Drupal user, or just construct * the message if @p $send is TRUE. * * @return * An empty array if the anonymous user is passed as @p $account. Otherwise, * the $message array structure containing all details of the message - * same result value as is returned by drupal_mail(). If already sent * ($send == TRUE), then the 'result' element will contain the success * indicator of the e-mail, failure being already written to the watchdog. * (Success means nothing more than the message being accepted at php-level, * which still doesn't guarantee it to be delivered.) */ function commitlog_user_notification_mail($account, $operation, $operation_items, $send = TRUE) { if (empty($account->uid)) { // Make sure we don't try to send to Anonymous. return array(); } $mailto = $account->mail; $language = user_preferred_language($account); return _commitlog_notification_mail($mailto, $operation, $operation_items, $language, $send); } /** * Common code for the above notification mail functions. */ function _commitlog_notification_mail($mailto, $operation, $operation_items, $language, $send) { $username = theme('versioncontrol_account_username', $operation['uid'], $operation['username'], $operation['repository'], array('format' => 'plaintext') ); $admin_mail = variable_get('versioncontrol_email_address', 'versioncontrol@example.com'); $from = "$username <$admin_mail>"; $params = array( 'operation' => $operation, 'operation_items' => $operation_items, 'username' => $username, ); return drupal_mail('commitlog', 'notification_email', $mailto, $language, $params, $from, $send); } /** * Implementation of hook_mail(). */ function commitlog_mail($key, &$message, $params) { if ($key != 'notification_email') { return; } $variables = array( 'operation' => $params['operation'], 'operation_items' => $params['operation_items'], ); $message['subject'] = theme('commitlog_operation_subject', $variables, array('format' => 'plaintext') ); $message['body'] = commitlog_operation( $params['operation'], $params['operation_items'], array('format' => 'plaintext', 'username' => $params['username']) ); } /** * Page callback for the 'track code' tab on the user page. */ function commitlog_account_tracker($user) { drupal_set_title($user->name); $constraints = _commitlog_get_constraints(); $constraints['uids'] = array($user->uid); return commitlog_operations_page($constraints); } /** * Page callback for the more targeted 'commitlog/%/%' menu path. */ function commitlog_operations_filtered_page($constraint_name, $constraint_value) { $constraints = array(); $constraint_request_map = module_invoke_all('commitlog_constraints'); foreach ($constraint_request_map as $constraint => $request) { if (isset($request['multiple']) && $request['multiple'] == $constraint_name) { $constraints[$constraint] = explode(',', $constraint_value); } if (isset($request['single']) && $request['single'] == $constraint_name) { $constraints[$constraint][] = $constraint_value; } } return commitlog_operations($constraints); } /** * Page callback for the 'commitlog' menu path, also usable by other modules * as commit log page with additional constraints. * * @param $additional_constraints * A fixed set of operation constraints that will be used in addition to * the constraints given by the user in the URL's query attributes. * If a constraint is given both in the @p $additional_constraints array * and as the query attribute then the latter is discarded. */ function commitlog_operations_page($additional_constraints = array()) { $constraints = $additional_constraints + _commitlog_get_constraints(); return commitlog_operations($constraints); } /** * Return a (paged) log of version control operations rendered to HTML. */ function commitlog_operations($constraints) { drupal_add_css(drupal_get_path('module', 'commitlog') .'/commitlog.css'); return theme('commitlog_operations_page', $constraints); } /** * Page callback for the 'commitlog/feed' menu path. */ function commitlog_operations_rss() { $constraints = _commitlog_get_constraints(); drupal_set_header('Content-Type: application/rss+xml; charset=utf-8'); print theme('commitlog_rss', $constraints); exit(); } /** * Retrieve the operation constraints for the page display by dissecting the * $_REQUEST variable. * * @return * A versioncontrol_get_operations() compatible $constraints array. */ function _commitlog_get_constraints() { $constraints = array(); $constraint_request_map = module_invoke_all('commitlog_constraints'); // Transform query string into commit constraints. We use $_REQUEST because // we need to support both GET and POST requests. foreach ($constraint_request_map as $constraint => $request) { if (isset($request['multiple']) && isset($_REQUEST[$request['multiple']])) { $constraints[$constraint] = explode(',', $_REQUEST[$request['multiple']]); } if (isset($request['single']) && isset($_REQUEST[$request['single']])) { $constraints[$constraint][] = $_REQUEST[$request['single']]; } } return $constraints; } /** * Implementation of hook_commitlog_constraints(): * Provide a list of supported constraints and corresponding request attributes. */ function commitlog_commitlog_constraints() { return array( 'vc_op_ids' => array('single' => 'id', 'multiple' => 'ids'), 'usernames' => array('single' => 'username', 'multiple' => 'usernames'), 'uids' => array('single' => 'uid', 'multiple' => 'uids'), 'vcs' => array('multiple' => 'vcs'), // for both single and multiple 'repo_ids' => array('single' => 'repo', 'multiple' => 'repos'), 'branches' => array('single' => 'branch', 'multiple' => 'branches'), 'tags' => array('single' => 'tag', 'multiple' => 'tags'), 'labels' => array('single' => 'label', 'multiple' => 'labels'), 'paths' => array('single' => 'path', 'multiple' => 'paths'), 'message' => array('single' => 'message', 'multiple' => 'messages'), // TODO: 'types' constraint ); } /** * Retrieve the Commit Log URL for the given @p $constraints. Some constraints * might not be able to be reconstructed to query attributes, in which case the * resulting log will include more operations than specified. * * @param $constraints * An array of operation constraints, as passed to versioncontrol_get_operations(). * @param $which * Either 'page' for the browser-viewable HTML version of the commit log, * or 'rss' for the RSS version. */ function commitlog_get_url($constraints, $which = 'page') { $path = ($which == 'rss') ? 'commitlog/feed' : 'commitlog'; return url($path, array( 'query' => _commitlog_query_attributes_for_constraints($constraints), 'absolute' => TRUE, )); } function _commitlog_query_attributes_for_constraints($constraints) { $query_items = array(); $constraint_request_map = module_invoke_all('commitlog_constraints'); foreach ($constraints as $key => $values) { if (isset($constraint_request_map[$key])) { if (!is_array($values)) { $values = array($values); } if (isset($constraint_request_map[$key]['single']) && count($values) == 1) { $query_items[$constraint_request_map[$key]['single']] = (string) reset($values); } else { $query_items[$constraint_request_map[$key]['multiple']] = implode(',', $values); } } } return $query_items; } /** * Return a formatted date string for the creation date of the given operation. * (This will also be used for grouping log messages.) */ function commitlog_operation_date($operation) { return format_date($operation['date'], 'custom', 'F j, Y'); } /** * Return formatted output for displaying the given operations on an HTML page. */ function theme_commitlog_operations_page($constraints) { $operations = versioncontrol_get_operations($constraints, array( 'query_type' => 'pager', 'count' => variable_get('commitlog_pager', 10), )); if (empty($operations)) { return '

'. t('No log messages found.') .'

'; } $output = '
'; $operation_descriptions = array(); foreach ($operations as $operation) { $operation_items = versioncontrol_get_operation_items($operation); $date = commitlog_operation_date($operation); $operation_descriptions[$date][] = array( commitlog_operation($operation, $operation_items, array('format' => 'html')), ); } $header = array(); foreach ($operation_descriptions as $date => $rows) { $output .= '

'. $date .'

'. theme('table', $header, $rows) .'
'; } if ($pager = theme('pager', NULL, variable_get('commitlog_pager', 10), 0)) { $output .= $pager; } $output .= '
'; // The feed icon, with reconstructed query attributes. drupal_add_feed(commitlog_get_url($constraints, 'rss'), t('Commit messages')); return $output; } function theme_commitlog_rss($constraints) { global $base_url, $locale; $operations = versioncontrol_get_operations($constraints, array( 'query_type' => 'range', 'from' => 0, 'count' => variable_get('feed_default_items', 10), )); $namespaces = array('xmlns:dc="http://purl.org/dc/elements/1.1/"'); $items = ''; foreach ($operations as $operation) { $operation_items = versioncontrol_get_operation_items($operation); $variables = array(); $variables['operation'] = $operation; $variables['operation_items'] = $operation_items; $title = theme('commitlog_operation_subject', $variables, array('format' => 'plaintext')); $plain_username = theme('versioncontrol_account_username', $operation['uid'], $operation['username'], $operation['repository'], array('format' => 'plaintext') ); $link = url('commitlog', array( 'query' => 'id='. $operation['vc_op_id'], 'absolute' => TRUE, )); $extra = array( array('key' => 'pubDate', 'value' => gmdate('r', $operation['date'])), array('key' => 'dc:creator', 'value' => $plain_username), array('key' => 'guid', // This is intentionally not translated: the RSS GUID just needs to be // any sort of globally unique value and won't be shown to users. 'value' => 'Version control operation '. $operation['vc_op_id'] .' at '. $base_url, 'attributes' => array('isPermaLink' => 'false'), ), ); $text = commitlog_operation($operation, $operation_items, array('format' => 'html')); $items .= format_rss_item($title, $link, $text, $extra); } $site_name = variable_get('site_name', 'Drupal'); unset($title); if (isset($constraints['nids'])) { // TODO: ideally, versioncontrol_project should be able to do this by itself $params = array(); $placeholders = array(); foreach ($constraints['nids'] as $nid) { $params[] = $nid; $placeholders[] = 'proj.nid = %d'; } $result = db_query("SELECT node.title FROM {versioncontrol_project_projects} proj INNER JOIN {node} node ON proj.nid = node.nid WHERE ". implode(' OR ', $placeholders), $params); $project_names = array(); while ($project = db_fetch_object($result)) { $project_names[] = $project->title; } $title = t('@project commits', array('@project' => implode(', ', $project_names))); } if (!isset($title)) { $title = t('@site commits', array('@site' => $site_name)); } $channel = array( 'version' => '2.0', 'title' => $title, 'link' => commitlog_get_url($constraints), 'description' => t('Commit messages for @site', array('@site' => $site_name)), 'language' => $locale, ); $output = "\n"; $output .= "\n"; $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language']); $output .= "\n"; return $output; } /** * Add a set of default options to a given options array. By default, only the * built-in 'format' option is added, more defaults can optionally be specified * with the @p $custom_default_options parameter. */ function _commitlog_options($options, $custom_default_options = array()) { $default_options = array('format' => 'html'); return array_merge($default_options, $custom_default_options, $options); } /** * Return an operation formatted as HTML (or another supported format). * * @param $operation * The operation to format. * @param $operation_items * The items belonging to this operation. * @param $options * An array of options for customizing the output. Supported properties: * * - 'format': Either 'html' (used by default if unset) or 'plaintext'. * - 'username': If set, this value will be used instead of the standard * VCS username that would be retrieved otherwise. Might make sense * if you want to use the output in special circumstances, or as * slight performance improvement if the username has already * been fetched. */ function commitlog_operation($operation, $operation_items, $options = array()) { $options = _commitlog_options($options); $format = $options['format']; // TODO: push down the stack, and remove $variables = array(); if (isset($options['username'])) { $variables['username'] = $options['username']; } else { $variables['username'] = theme('versioncontrol_account_username', $operation['uid'], $operation['username'], $operation['repository'], array('format' => $options['format']) ); } $variables['operation'] = $operation; $variables['operation_items'] = $operation_items; $variables['id'] = theme('commitlog_revision_identifier', $operation, $format); $variables['repository_name'] = theme('commitlog_repository', $operation['repository'], $format); $variables['time'] = format_date($operation['date'], 'custom', 'H:i'); $variables['items'] = theme('commitlog_operation_items', $operation, $operation_items, $format); $variables['message'] = theme('commitlog_operation_message', $operation, $format); $variables['caption'] = theme('commitlog_operation_caption', $variables, $options); if ($format == 'html') { return theme('commitlog_operation_html', $variables); } return theme('commitlog_operation_plaintext', $variables); } function theme_commitlog_operation_html($variables) { $output = '
'; $output .= '
'. $variables['caption'] ."
\n"; if (!empty($variables['message'])) { $output .= '
'. $variables['message'] .'
'."\n"; } $output .= '
'. $variables['items'] .'
'."\n"; $output .= "
\n"; // class "commit" return $output; } function theme_commitlog_operation_plaintext($variables) { $output = $variables['caption'] ."\n\n"; if (!empty($variables['message'])) { $output .= $variables['message'] ."\n\n"; } $output .= $variables['items'] ."\n"; return $output; } /** * Return the commitlog URL for the log history of a given account. */ function commitlog_get_account_url($repository, $username) { return url('commitlog', array( 'query' => array('username' => $username, 'repo' => $repository['repo_id']), 'absolute' => TRUE, )); } function theme_commitlog_repository($repository, $format = 'html') { if ($format == 'html') { return l($repository['name'], 'commitlog', array( 'query' => 'repo='. $repository['repo_id'], )); } return check_plain($repository['name']); } /** * Retrieve the common directory item for all items that have been affected * by the given operation. (Affected items include operation items and * source items in a move or merge operation. Copied or replacement sources * don't affect the original item, so those are not included.) */ function _commitlog_affected_directory_item($operation, $operation_items) { $affected_items = $operation_items; $relevant_actions = array(VERSIONCONTROL_ACTION_MOVED, VERSIONCONTROL_ACTION_MERGED); foreach ($operation_items as $path => $item) { if (!isset($item['action']) || !in_array($item['action'], $relevant_actions)) { continue; } foreach ($item['source_items'] as $source_path => $source_item) { // If the source item is already in the affected items array, // there's no need to duplicate it there. if ($path != $source_path) { $affected_items[$source_path] = $source_item; } } } $common_directory_path = versioncontrol_get_common_directory_path($affected_items); // In order to retrieve the directory item, we ascend from any (...the first) // of the operation items so that revision, label and whatnot are preserved. // That should work just fine in most (if not all) cases. return versioncontrol_get_parent_item( $operation['repository'], reset($affected_items), $common_directory_path ); } function theme_commitlog_operation_location($operation, $operation_items, $directory_item = NULL, $format = 'html') { if (!isset($directory_item)) { $directory_item = _commitlog_affected_directory_item($operation, $operation_items); } return theme('commitlog_item', $operation['repository'], $directory_item, NULL, $format); } function theme_commitlog_revision_identifier($operation, $format = 'html') { $id = versioncontrol_format_operation_revision_identifier($operation, 'short'); if ($format == 'html') { if ($operation['type'] == VERSIONCONTROL_OPERATION_COMMIT) { $url = versioncontrol_get_url_commit_view($operation); } if (!empty($url)) { return ''. $id .''; } } return theme('commitlog_vc_op_id', $operation['vc_op_id'], $id, $format); } function theme_commitlog_vc_op_id($vc_op_id, $text = NULL, $format = 'html') { if (!isset($text)) { $text = $vc_op_id; } if ($format == 'html') { return l($text, 'commitlog', array('query' => 'id='. $vc_op_id)); } return $text; } function theme_commitlog_operation_message($operation, $format = 'html') { if (empty($operation['message'])) { return ''; } if ($format != 'html') { return $operation['message']; } // Link to issues whose numbers are given in the commit message. // TODO: rework this to utilize standard Drupal filters? return preg_replace('/#(\d+)\b/ie', "strtr('#\\1', array('!url' => versioncontrol_get_url_tracker(\$operation['repository'], \\1)))", htmlspecialchars($operation['message']) ); } function theme_commitlog_operation_caption($variables, $options = array()) { $options = _commitlog_options($options); $action_strings = array(); if (empty($variables['operation']['labels'])) { $action_strings[] = t('committed !id', array('!id' => $variables['id'])); } else { $labels_by_type = array(); foreach ($variables['operation']['labels'] as $label) { $labels_by_type[$label['type']][$label['action']][] = theme('commitlog_label', $label, $options); } ksort($labels_by_type); // first commits, then branches, then tags foreach ($labels_by_type as $type => $labels_by_action) { foreach ($labels_by_action as $action => $labels) { $replacements = array( '!id' => $variables['id'], '!labels' => implode(', ', $labels), ); if ($action == VERSIONCONTROL_ACTION_MODIFIED) { $action_strings[] = t('committed !id to !labels', $replacements); } else if ($action == VERSIONCONTROL_ACTION_ADDED) { $action_string = ($type == VERSIONCONTROL_OPERATION_BRANCH) ? format_plural(count($labels), 'created branch !labels', 'created branches !labels') : format_plural(count($labels), 'created tag !labels', 'created tags !labels'); $action_strings[] = strtr($action_string, $replacements); } else if ($action == VERSIONCONTROL_ACTION_DELETED) { $action_string = ($type == VERSIONCONTROL_OPERATION_BRANCH) ? format_plural(count($labels), 'deleted branch !labels', 'deleted branches !labels') : format_plural(count($labels), 'deleted tag !labels', 'deleted tags !labels'); $action_strings[] = strtr($action_string, $replacements); } } } } return t('!name !actions in !repo at !time:', array( '!name' => $variables['username'], '!actions' => implode(', ', $action_strings), '!repo' => $variables['repository_name'], '!time' => $variables['time'], )); } /** * A one-liner subject for notification mails and RSS item titles, * where author and date are already visible in other fields. */ function theme_commitlog_operation_subject($variables, $options = array()) { $options = _commitlog_options($options); $repo_name = theme('commitlog_repository', $variables['operation']['repository'], $options['format']); // This stuff is not quite as accurate as theme('commitlog_operation_caption'), // but there's also less space in a single line of RSS/mail subject if you // also want to include the directory ("location"). // So instead of handling everything by labels only (most accurate, but also // most flexible == less assumptions == less space optimization), // we distinguish operations by their native operation type and don't do more // than one label. That should be just fine for about 99.7% of all cases. if ($variables['operation']['type'] == VERSIONCONTROL_OPERATION_COMMIT) { $replacements = array( '!id' => theme('commitlog_revision_identifier', $variables['operation'], $options['format']), '!repo' => $repo_name, '!location' => theme('commitlog_operation_location', $variables['operation'], $variables['operation_items'], NULL, $options['format'] ), ); $labels = array(); foreach ($variables['operation']['labels'] as $label) { $labels[] = theme('commitlog_label', $label, $options); } if (empty($labels)) { return t('!id: !location (!repo)', $replacements); } $replacements['!labels'] = implode(', ', $labels); return t('!id on !labels: !location (!repo)', $replacements); } else { // it's a branch or tag $first_label = reset($variables['operation']['labels']); $label_string = theme('commitlog_label', $first_label, $options); $replacements = array('!label' => $label_string, '!repo' => $repo_name); if ($first_label['action'] == VERSIONCONTROL_ACTION_ADDED) { $action_string = ($first_label['type'] == VERSIONCONTROL_OPERATION_BRANCH) ? t('Created branch !label', $replacements) : t('Created tag !label', $replacements); } else if ($first_label['action'] == VERSIONCONTROL_ACTION_DELETED) { $action_string = ($first_label['type'] == VERSIONCONTROL_OPERATION_BRANCH) ? t('Created branch !label', $replacements) : t('Created tag !label', $replacements); } // CVS and SVN label a set of items instead of the whole repository, // let's include the operation directory for those. if (!empty($variables['operation_items'])) { $action_string = t('!action: !location', array( '!action' => $action_string, '!location' => theme('commitlog_operation_location', $variables['operation'], $variables['operation_items'], NULL, $options['format'] ), )); } return t('!action (!repo)', array( '!action' => $action_string, '!repo' => $repo_name, )); } } function theme_commitlog_label($label, $options = array()) { $options = _commitlog_options($options); if ($options['format'] == 'html') { $label_type_filter = ($label['type'] == VERSIONCONTROL_OPERATION_BRANCH) ? 'branch' : 'tag'; return l($label['name'], 'commitlog', array( 'query' => array($label_type_filter => $label['name']), )); } return $label['name']; } function theme_commitlog_operation_items($operation, $operation_items, $format = 'html') { if (empty($operation_items)) { return ''; } $directory_item = _commitlog_affected_directory_item($operation, $operation_items); if (module_exists('versioncontrol_project')) { $op_projects = versioncontrol_project_get_projects(array( 'vc_op_ids' => array($operation['vc_op_id']), )); if (count($op_projects) == 1) { $project = reset($op_projects); if ($project['repo_id'] == $operation['repository']['repo_id'] && versioncontrol_path_contains($project['directory'], $directory_item['path'])) { $directory_item = versioncontrol_get_parent_item( $operation['repository'], $directory_item, $project['directory'] ); $project_string = theme('commitlog_project', $operation['repository'], $project, $format); } } } $lines = array(); foreach ($operation_items as $path => $item) { if (module_exists('versioncontrol_project')) { // If all our items belong to the same project, don't try to find out // the project for this specific item. if (isset($project_string)) { $item_project = NULL; } else { $item_projects = versioncontrol_project_get_projects(array( 'item_revision_ids' => array($item['item_revision_id']), )); $item_project = empty($item_projects) ? NULL : reset($item_projects); } } // Determine how the item path should be displayed. $item_path = ($directory_item['path'] == '/') ? $item['path'] : drupal_substr($item['path'], drupal_strlen($directory_item['path']) + 1); if ($item['type'] == VERSIONCONTROL_ITEM_DIRECTORY) { $item_path = t('!path [directory]', array('!path' => $item_path)); } $item_string = theme('commitlog_item', $operation['repository'], $item, $item_path, $format); // See if there have been one or more previous versions of this item, and // make a displayable string out of those. if (!empty($item['source_items'])) { $oldrev_string = theme('commitlog_item_revision', $operation['repository'], reset($item['source_items']), $format ); $olditems = array(); foreach ($item['source_items'] as $source_item) { $item_path = ($directory_item['path'] == '/') ? $source_item['path'] : drupal_substr($source_item['path'], drupal_strlen($directory_item['path']) + 1); $olditems[] = theme('commitlog_item', $operation['repository'], $source_item, $item_path, $format); } $olditems_string = implode(', ', $olditems); } // If there is a diff to the previous version, show the appropriate link too. $show_diff = FALSE; if ($format == 'html') { $diff_string = theme('commitlog_diff_link', $operation, $item); if (!empty($diff_string)) { $show_diff = TRUE; } } // Depending on what happened to the items, make a readable string out of // the separate pieces of information that we've got by now. if (!isset($item['action'])) { $action_string = '!item'; } else { switch ($item['action']) { case VERSIONCONTROL_ACTION_ADDED: $action_string = t('!item (added)'); break; case VERSIONCONTROL_ACTION_MODIFIED: $action_string = $show_diff ? t('!item (modified, previous: !oldrev, !diff)') : t('!item (modified, previous: !oldrev)'); break; case VERSIONCONTROL_ACTION_MOVED: $action_string = $show_diff ? t('!item (moved from !olditems, !diff)') : t('!item (moved from !olditems)'); break; case VERSIONCONTROL_ACTION_COPIED: $action_string = $show_diff ? t('!item (copied from !olditems, !diff)') : t('!item (copied from !olditems)'); break; case VERSIONCONTROL_ACTION_MERGED: $action_string = $show_diff ? t('!item (merged from !olditems, !diff)') : t('!item (merged from !olditems)'); break; case VERSIONCONTROL_ACTION_DELETED: $action_string = t('!path (deleted: !oldrev)'); break; // case VERSIONCONTROL_ACTION_OTHER: // $action_string = ???; // break; default: $lines[] = 'Error: action type enum value not known: '. $item['action']; continue; } if (isset($item['line_changes'])) { $action_string = t('!action, lines: +!added -!removed', array( '!action' => $action_string, '!added' => $item['line_changes']['added'], '!removed' => $item['line_changes']['removed'], )); } } if ($format == 'html' && isset($item_project)) { $item_project_string = theme('commitlog_project', $operation['repository'], $item_project, $format ); $action_string = t('!project: !action', array( '!project' => $item_project_string, '!action' => $action_string, )); } $lines[] = strtr($action_string, array( '!item' => $item_string, '!path' => $item_path, '!oldrev' => isset($oldrev_string) ? $oldrev_string : '', '!olditems' => isset($olditems_string) ? $olditems_string : '', '!diff' => isset($diff_string) ? $diff_string : '', )); } $location = theme('commitlog_operation_location', $operation, $operation_items, $directory_item, $format); if (isset($project_string)) { if ($format == 'html') { $location = ''. $project_string .''; } else { $location = t('!location: (!project)', array( '!project' => $project_string, '!location' => $location, )); } } $item_header = t('in !location:', array('!location' => $location)); if ($format == 'html') { if (count($lines) > 1) { $output = '
'. $item_header .'
'."\n"; $output .= '
'. theme('item_list', $lines) ."
\n"; } else { $output = '
'. $item_header .' '. reset($lines) ."
\n"; } return $output; } // else: plaintext $output = $location ."\n"; foreach ($lines as $key => $line) { $lines[$key] = '- '. $line; } $output .= implode("\n", $lines); return $output; } function theme_commitlog_item($repository, $item, $path_override = NULL, $format = 'html') { $backends = versioncontrol_get_backends(); if (!isset($backends[$repository['vcs']])) { return ''; } $has_atomic_commits = FALSE; $backend = $backends[$repository['vcs']]; if (in_array(VERSIONCONTROL_CAPABILITY_ATOMIC_COMMITS, $backend['capabilities'])) { $has_atomic_commits = TRUE; } $path = isset($path_override) ? $path_override : $item['path']; // For version control systems without atomic commits, display the revision // next to the path. For all other ones this doesn't make sense, as there is // only one global revision for all items, and that one is already displayed // in the commit title. $output = $path; if ($format == 'html') { $log_url = versioncontrol_get_url_item_log_view($repository, $item); if (!empty($log_url)) { $output = l($path, $log_url); } } if (empty($item['revision']) || $has_atomic_commits) { if ($format == 'html') { $view_url = versioncontrol_get_url_item_view($repository, $item); if (!empty($view_url)) { $output = l($path, $view_url); } } return $output; } else { return $output .' '. theme('commitlog_item_revision', $repository, $item, $format); } } function theme_commitlog_item_revision($repository, $item, $format = 'html') { $revision = versioncontrol_format_item_revision_identifier($repository, $item, 'short'); if ($format == 'html' && !empty($revision)) { $view_url = versioncontrol_get_url_item_view($repository, $item); if (!empty($view_url)) { $revision = l($revision, $view_url); } } return $revision; } function theme_commitlog_diff_link($operation, $item) { // We can only come up with a diff link if we've got a source item. if (empty($item['source_items'])) { return ''; } // Also, a diff only makes sense if the item was actually modified. if (in_array($item['action'], array(VERSIONCONTROL_ACTION_MODIFIED, VERSIONCONTROL_ACTION_MERGED))) { // Ok, everything's alright, let's get that diff link. $source_item = reset($item['source_items']); $diff_url = versioncontrol_get_url_diff( $operation['repository'], $item, $source_item ); if (!empty($diff_url)) { return l(t('diff'), $diff_url); } } return ''; } function theme_commitlog_project($repository, $project, $format = 'html') { $project_node = node_load($project['nid']); if ($project_node) { if ($format == 'html') { return l($project_node->title, 'node/'. $project_node->nid); } return check_plain($project_node->title); } return ''; }