| 1 |
<?php
|
| 2 |
// $Id: versioncontrol_cvs.log.inc,v 1.11 2009/01/02 21:28:40 jpetso Exp $
|
| 3 |
/**
|
| 4 |
* @file
|
| 5 |
* CVS backend for Version Control API - Provides CVS commit information and
|
| 6 |
* account management as a pluggable backend.
|
| 7 |
*
|
| 8 |
* This file provides functionality to parse the output of 'cvs rlog'
|
| 9 |
* and transform it into Version Control API commits.
|
| 10 |
*
|
| 11 |
* Copyright 2005 by Kjartan Mannes ("Kjartan", http://drupal.org/user/2)
|
| 12 |
* Copyright 2006, 2007 by Derek Wright ("dww", http://drupal.org/user/46549)
|
| 13 |
* Copyright 2007, 2008, 2009 by Jakob Petsovits ("jpetso", http://drupal.org/user/56020)
|
| 14 |
*/
|
| 15 |
|
| 16 |
/**
|
| 17 |
* Actually update the repository by fetching commits and other stuff
|
| 18 |
* directly from the repository, invoking the cvs executable.
|
| 19 |
*
|
| 20 |
* @return
|
| 21 |
* TRUE if the logs were updated, or FALSE if fetching and updating the logs
|
| 22 |
* failed for whatever reason.
|
| 23 |
*/
|
| 24 |
function _versioncontrol_cvs_log_update_repository(&$repository) {
|
| 25 |
_versioncontrol_cvs_init_cvslib();
|
| 26 |
|
| 27 |
// Remember the current time for the "updated" value that is stored later on.
|
| 28 |
$date_updated = time();
|
| 29 |
|
| 30 |
$file_revisions = cvslib_log(
|
| 31 |
$repository['root'], $repository['cvs_specific']['modules'],
|
| 32 |
array('date_lower' => $repository['cvs_specific']['updated'])
|
| 33 |
);
|
| 34 |
if ($file_revisions === FALSE) {
|
| 35 |
drupal_set_message(cvslib_last_error_message(), 'error');
|
| 36 |
return FALSE;
|
| 37 |
}
|
| 38 |
|
| 39 |
// Having retrieved the file revisions, insert those into the database
|
| 40 |
// as Version Control API commits.
|
| 41 |
_versioncontrol_cvs_log_process($repository, $file_revisions);
|
| 42 |
$repository['cvs_specific']['updated'] = $date_updated;
|
| 43 |
|
| 44 |
// Everything's done, remember the time when we updated the log (= now).
|
| 45 |
db_query('UPDATE {versioncontrol_cvs_repositories}
|
| 46 |
SET updated = %d WHERE repo_id = %d',
|
| 47 |
$repository['cvs_specific']['updated'], $repository['repo_id']);
|
| 48 |
|
| 49 |
return TRUE;
|
| 50 |
}
|
| 51 |
|
| 52 |
/**
|
| 53 |
* Update the database by processing and inserting the previously retrieved
|
| 54 |
* file revision objects.
|
| 55 |
*
|
| 56 |
* @param $repository
|
| 57 |
* The repository array, as given by the Version Control API.
|
| 58 |
* @param $file_revisions
|
| 59 |
* A simple, flat list of file revision objects, as returned by cvslib_log().
|
| 60 |
*/
|
| 61 |
function _versioncontrol_cvs_log_process($repository, $file_revisions) {
|
| 62 |
$operation_items_by_commitid = array();
|
| 63 |
$operation_items_by_user = array();
|
| 64 |
|
| 65 |
foreach ($file_revisions as $file_revision) {
|
| 66 |
// Don't insert the same revision twice.
|
| 67 |
$count = db_result(db_query(
|
| 68 |
"SELECT COUNT(*)
|
| 69 |
FROM {versioncontrol_item_revisions} ir
|
| 70 |
INNER JOIN {versioncontrol_operation_items} opitem
|
| 71 |
ON ir.item_revision_id = opitem.item_revision_id
|
| 72 |
INNER JOIN {versioncontrol_operations} op
|
| 73 |
ON opitem.vc_op_id = op.vc_op_id
|
| 74 |
WHERE op.repo_id = %d AND op.type = %d
|
| 75 |
AND ir.path = '%s' AND ir.revision = '%s'",
|
| 76 |
$repository['repo_id'], VERSIONCONTROL_OPERATION_COMMIT,
|
| 77 |
$file_revision->path, $file_revision->revision
|
| 78 |
));
|
| 79 |
if ($count > 0) { // Item revision has been recorded already.
|
| 80 |
continue;
|
| 81 |
}
|
| 82 |
|
| 83 |
// We might only pick one of those (depending if the file
|
| 84 |
// has been added, modified or deleted) but let's add both
|
| 85 |
// current and source items for now.
|
| 86 |
$operation_item = array(
|
| 87 |
'type' => VERSIONCONTROL_ITEM_FILE,
|
| 88 |
'path' => $file_revision->path,
|
| 89 |
'revision' => $file_revision->revision,
|
| 90 |
'action' => VERSIONCONTROL_ACTION_MODIFIED, // default, might be changed
|
| 91 |
'source_items' => array(
|
| 92 |
array(
|
| 93 |
'type' => VERSIONCONTROL_ITEM_FILE,
|
| 94 |
'path' => $file_revision->path,
|
| 95 |
'revision' => versioncontrol_cvs_get_previous_revision_number($file_revision->revision),
|
| 96 |
),
|
| 97 |
),
|
| 98 |
'line_changes' => array(
|
| 99 |
'added' => $file_revision->lines_added,
|
| 100 |
'removed' => $file_revision->lines_removed,
|
| 101 |
),
|
| 102 |
'cvs_specific' => array(
|
| 103 |
'file_revision' => $file_revision, // temporary
|
| 104 |
),
|
| 105 |
);
|
| 106 |
|
| 107 |
if ($file_revision->dead) {
|
| 108 |
$operation_item['action'] = VERSIONCONTROL_ACTION_DELETED;
|
| 109 |
$operation_item['type'] = VERSIONCONTROL_ITEM_FILE_DELETED;
|
| 110 |
}
|
| 111 |
else {
|
| 112 |
if ($file_revision->revision === '1.1') {
|
| 113 |
$operation_item['action'] = VERSIONCONTROL_ACTION_ADDED;
|
| 114 |
$operation_item['source_items'] = array();
|
| 115 |
unset($operation_item['line_changes']);
|
| 116 |
}
|
| 117 |
}
|
| 118 |
|
| 119 |
if (isset($file_revision->commitid)) {
|
| 120 |
$operation_items_by_commitid[$file_revision->commitid][$file_revision->path] = $operation_item;
|
| 121 |
}
|
| 122 |
else {
|
| 123 |
$operation_items_by_user[$file_revision->username]
|
| 124 |
[$file_revision->date][$file_revision->path] = $operation_item;
|
| 125 |
}
|
| 126 |
}
|
| 127 |
|
| 128 |
$commit_infos_by_date = array();
|
| 129 |
|
| 130 |
// Part one: revisions with commitid - these are cool & easy.
|
| 131 |
foreach ($operation_items_by_commitid as $commitid => $operation_items) {
|
| 132 |
_versioncontrol_cvs_log_construct_commit_operation(
|
| 133 |
$repository, $operation_items, $commit_infos_by_date
|
| 134 |
);
|
| 135 |
}
|
| 136 |
|
| 137 |
// Part two: revisions without commitid - need to apply heuristics
|
| 138 |
// in order to get whole commits instead of separate file-by-file stuff.
|
| 139 |
foreach ($operation_items_by_user as $username => $operation_items_by_date) {
|
| 140 |
// Iterating through the date sorted array is a bit complicated
|
| 141 |
// as we need to delete file revision elements that are determined to be
|
| 142 |
// in the same commit as the reference item.
|
| 143 |
while ($date = key($operation_items_by_date)) {
|
| 144 |
while ($path = key($operation_items_by_date[$date])) {
|
| 145 |
$reference_item = array_shift($operation_items_by_date[$date]);
|
| 146 |
|
| 147 |
$operation_items = _versioncontrol_cvs_log_group_items(
|
| 148 |
$operation_items_by_date, $reference_item
|
| 149 |
);
|
| 150 |
_versioncontrol_cvs_log_construct_commit_operation(
|
| 151 |
$repository, $operation_items, $commit_infos_by_date
|
| 152 |
);
|
| 153 |
}
|
| 154 |
unset($operation_items_by_date[$date]); // Done with this date, next one.
|
| 155 |
reset($operation_items_by_date); // Set the array pointer to the start.
|
| 156 |
}
|
| 157 |
}
|
| 158 |
|
| 159 |
// Ok, we've got all commit operations gathered and in a nice array with
|
| 160 |
// the commit date as key. So the only thing that's left is to sort them
|
| 161 |
// and then send each commit to the API function for inserting into the db.
|
| 162 |
ksort($commit_infos_by_date);
|
| 163 |
foreach ($commit_infos_by_date as $date => $date_commit_infos) {
|
| 164 |
foreach ($date_commit_infos as $commit_info) {
|
| 165 |
_versioncontrol_cvs_fix_commit_operation_items(
|
| 166 |
$commit_info->operation, $commit_info->operation_items
|
| 167 |
);
|
| 168 |
versioncontrol_insert_operation(
|
| 169 |
$commit_info->operation, $commit_info->operation_items
|
| 170 |
);
|
| 171 |
}
|
| 172 |
}
|
| 173 |
}
|
| 174 |
|
| 175 |
/**
|
| 176 |
* Extract (and delete) items from the given $operation_items_by_date array
|
| 177 |
* that belong to the same commit as the $reference_action.
|
| 178 |
* This function is what provides heuristics for grouping file revisions
|
| 179 |
* (that lack a commitid) together into one commit operation.
|
| 180 |
*
|
| 181 |
* @return
|
| 182 |
* One or more items grouped into an $operation_items array,
|
| 183 |
* complete with file paths as keys as required by the Version Control API.
|
| 184 |
*/
|
| 185 |
function _versioncontrol_cvs_log_group_items(&$operation_items_by_date, $reference_item) {
|
| 186 |
$file_revision = $reference_action['cvs_specific']['file_revision'];
|
| 187 |
|
| 188 |
$operation_items = array();
|
| 189 |
$operation_items[$file_revision->path] = $reference_item;
|
| 190 |
|
| 191 |
// Try all file revisions in the near future (next 30 seconds) to see if
|
| 192 |
// they belong to the same commit or not. If they do, extract and delete.
|
| 193 |
// Commits that take longer than half a minute are unlikely enough to be
|
| 194 |
// disregarded here.
|
| 195 |
for ($date = $file_revision->date; $date < ($file_revision->date + 30); $date++) {
|
| 196 |
if (!isset($operation_items_by_date[$date])) {
|
| 197 |
continue;
|
| 198 |
}
|
| 199 |
|
| 200 |
foreach ($operation_items_by_date[$date] as $path => $current_item) {
|
| 201 |
$current_file_revision = $current_item['cvs_specific']['file_revision'];
|
| 202 |
|
| 203 |
// Check for message and branch to be similar. We know that the username
|
| 204 |
// is similar because we sorted by that one, and the date is near enough
|
| 205 |
// to be regarded as roughly the same time.
|
| 206 |
if ($current_file_revision->message == $file_revision->message
|
| 207 |
&& $current_file_revision->branch == $file_revision->branch) {
|
| 208 |
// So, sure enough, we have a file from the same commit here.
|
| 209 |
$operation_items[$path] = $current_item;
|
| 210 |
unset($operation_items_by_date[$date][$path]); // Don't process this revision twice.
|
| 211 |
}
|
| 212 |
}
|
| 213 |
}
|
| 214 |
return $operation_items;
|
| 215 |
}
|
| 216 |
|
| 217 |
/**
|
| 218 |
* Use the additional file revision information that has been stored in each
|
| 219 |
* operation item array in order to assemble the associated commit operation.
|
| 220 |
* That commit information is then stored as a list item in the given
|
| 221 |
* $commit_operations array as an object with 'operation' and 'operation_items'
|
| 222 |
* properties.
|
| 223 |
*/
|
| 224 |
function _versioncontrol_cvs_log_construct_commit_operation($repository, $operation_items, &$commit_infos_by_date) {
|
| 225 |
$date = 0;
|
| 226 |
|
| 227 |
// Get any of those commit properties, they should all be the same anyways
|
| 228 |
// (apart from the date which may vary in large commits).
|
| 229 |
foreach ($operation_items as $path => $item) {
|
| 230 |
$file_revision = $item['cvs_specific']['file_revision'];
|
| 231 |
unset($operation_items[$path]['cvs_specific']['file_revision']);
|
| 232 |
|
| 233 |
if ($file_revision->date > $date) {
|
| 234 |
$date = $file_revision->date;
|
| 235 |
}
|
| 236 |
$username = $file_revision->username;
|
| 237 |
$message = $file_revision->message;
|
| 238 |
$branch_name = $file_revision->branch;
|
| 239 |
}
|
| 240 |
|
| 241 |
// Yay, we have all operation items and all information. Ready to go!
|
| 242 |
$operation = array(
|
| 243 |
'type' => VERSIONCONTROL_OPERATION_COMMIT,
|
| 244 |
'repo_id' => $repository['repo_id'],
|
| 245 |
'date' => $date,
|
| 246 |
'username' => $username,
|
| 247 |
'message' => $message,
|
| 248 |
'revision' => '',
|
| 249 |
'labels' => array(
|
| 250 |
array(
|
| 251 |
'name' => $branch_name,
|
| 252 |
'type' => VERSIONCONTROL_OPERATION_BRANCH,
|
| 253 |
'action' => VERSIONCONTROL_ACTION_MODIFIED,
|
| 254 |
),
|
| 255 |
),
|
| 256 |
);
|
| 257 |
|
| 258 |
$commit_info = new stdClass();
|
| 259 |
$commit_info->operation = $operation;
|
| 260 |
$commit_info->operation_items = $operation_items;
|
| 261 |
$commit_infos_by_date[$date][] = $commit_info;
|
| 262 |
}
|