| 1 |
<?php
|
| 2 |
// $Id: versioncontrol_git.log.inc,v 1.39 2009/10/19 15:24:04 marvil07 Exp $
|
| 3 |
/**
|
| 4 |
* @file
|
| 5 |
* Git backend for Version Control API - Provides Git commit information and
|
| 6 |
* account management as a pluggable backend.
|
| 7 |
*
|
| 8 |
* Copyright 2008 by Jimmy Berry ("boombatower", http://drupal.org/user/214218)
|
| 9 |
* Copyright 2009 by Cornelius Riemenschneider ("CorniI", http://drupal.org/user/136353)
|
| 10 |
*/
|
| 11 |
|
| 12 |
include_once(drupal_get_path('module', 'versioncontrol') .'/includes/VersioncontrolBranch.php');
|
| 13 |
include_once(drupal_get_path('module', 'versioncontrol') .'/includes/VersioncontrolOperation.php');
|
| 14 |
include_once(drupal_get_path('module', 'versioncontrol') .'/includes/VersioncontrolItem.php');
|
| 15 |
|
| 16 |
/**
|
| 17 |
* Actually update the repository by fetching commits and other stuff
|
| 18 |
* directly from the repository, invoking the git executable.
|
| 19 |
* @param $repository
|
| 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_git_log_update_repository(&$repository) {
|
| 25 |
$root = escapeshellcmd($repository->root);
|
| 26 |
$chdir_ok = @chdir($root); // Set working directory to root.
|
| 27 |
if ($chdir_ok === FALSE) {
|
| 28 |
return FALSE;
|
| 29 |
}
|
| 30 |
if ($repository->data['versioncontrol_git']['locked'] == TRUE) {
|
| 31 |
drupal_set_message(t('This repository is locked, there is already a fetch in progress. If this is not the case, press the clear lock button.'), 'error');
|
| 32 |
return FALSE;
|
| 33 |
}
|
| 34 |
$repository->data['versioncontrol_git']['locked'] = 1;
|
| 35 |
$repository->update();
|
| 36 |
|
| 37 |
// Get the list of current branches from Git.
|
| 38 |
$branch_list = _versioncontrol_git_log_get_branches();
|
| 39 |
$branches = array();
|
| 40 |
foreach ($branch_list as $branch_name) {
|
| 41 |
$label = new VersioncontrolBranch($branch_name,
|
| 42 |
VERSIONCONTROL_ACTION_MODIFIED, NULL, $repository);
|
| 43 |
$label->ensure();
|
| 44 |
$branches[$branch_name] = $label;
|
| 45 |
}
|
| 46 |
//jpetso and me (corni) came to the conclusion that we will not delete branches.
|
| 47 |
// TODO: revisit this!
|
| 48 |
// Record new commits.
|
| 49 |
$constraints = array(
|
| 50 |
'vcs' => array('git'),
|
| 51 |
'repo_ids' => array($repository->repo_id),
|
| 52 |
'types' => array(VERSIONCONTROL_OPERATION_COMMIT),
|
| 53 |
'branches' => array() // used in the loop
|
| 54 |
);
|
| 55 |
$branches_per_commit = array();
|
| 56 |
$existing_revs = array();
|
| 57 |
// Get the existing revisions from the cache.
|
| 58 |
$cache_object = cache_get('versioncontrol_git_rev_cache');
|
| 59 |
// Check wether the cache object exists or not.
|
| 60 |
if (is_object($cache_object)) {
|
| 61 |
$existing_revs = $cache_object->data;
|
| 62 |
}
|
| 63 |
// Get the list of current branches from Git.
|
| 64 |
// Generate the range per branch with which git shall be called.
|
| 65 |
foreach ($branches as $branch_name => $label) {
|
| 66 |
if (is_object($cache_object)) {
|
| 67 |
// We get all commits we have in this branch to not process them later.
|
| 68 |
$constraints['branches'] = array($branch_name);
|
| 69 |
$latest_commit_date = 0;
|
| 70 |
$commit_op = VersioncontrolOperationCache::getInstance()->getOperations($constraints);
|
| 71 |
$latest_commit = FALSE;
|
| 72 |
foreach ($commit_op as $vc_op_id => $c_op) {
|
| 73 |
if ($latest_commit_date < $c_op['date']) {
|
| 74 |
$latest_commit = $c_op['revision'];
|
| 75 |
$latest_commit_date = $c_op['date'];
|
| 76 |
$existing_revs[$branch_name][$latest_commit] = TRUE;
|
| 77 |
}
|
| 78 |
}
|
| 79 |
}
|
| 80 |
// No way to free the damned mysql result!!
|
| 81 |
unset($commit_op);
|
| 82 |
|
| 83 |
$commits_in_branch = _versioncontrol_git_log_get_commits_in_branch($repository, escapeshellarg($branch_name));
|
| 84 |
foreach ($commits_in_branch as $commit) {
|
| 85 |
if (!isset($existing_revs[$branch_name][$commit])) {
|
| 86 |
if (!isset($branches_per_commit[$commit]) || !is_array($branches_per_commit[$commit])) {
|
| 87 |
$branches_per_commit[$commit] = array($label);
|
| 88 |
}
|
| 89 |
else {
|
| 90 |
$branches_per_commit[$commit][] = $label;
|
| 91 |
}
|
| 92 |
}
|
| 93 |
}
|
| 94 |
}
|
| 95 |
// This uses an extra loop on purpose!
|
| 96 |
// Process all commits on a per-branch base.
|
| 97 |
foreach ($branches_per_commit as $revision => $branch) {
|
| 98 |
// Update commits from Git.
|
| 99 |
_versioncontrol_git_process_commits($repository, $revision, $branches_per_commit, $existing_revs);
|
| 100 |
}
|
| 101 |
// Check tags.
|
| 102 |
$tags = _versioncontrol_git_log_get_tags(); //Now we have the current list of tags as array of strings.
|
| 103 |
$constraints = array(
|
| 104 |
'vcs' => array('git'),
|
| 105 |
'repo_ids' => array($repository->repo_id),
|
| 106 |
'types' => array(VERSIONCONTROL_OPERATION_TAG)
|
| 107 |
);
|
| 108 |
$existing_tag_ops = VersioncontrolOperationCache::getInstance()->getOperations($constraints);
|
| 109 |
$existing_tags = array();
|
| 110 |
foreach ($existing_tag_ops as $tag_op) {
|
| 111 |
if (!in_array($tag_op->labels[0]->name, $existing_tags)) {
|
| 112 |
$existing_tags[] = $tag_op->labels[0]->name;
|
| 113 |
}
|
| 114 |
}
|
| 115 |
// Deleting tags is *not* supported. Read the manual if you want to know why...
|
| 116 |
// Check for new tags.
|
| 117 |
$new_tags = array_diff($tags, $existing_tags);
|
| 118 |
if (!empty($new_tags)) {
|
| 119 |
_versioncontrol_git_process_tags($repository, $new_tags);
|
| 120 |
}
|
| 121 |
|
| 122 |
// Update repository updated field. Displayed on administration interface for documentation purposes.
|
| 123 |
$repository->data['versioncontrol_git']['updated'] = time();
|
| 124 |
$repository->data['versioncontrol_git']['locked'] = 0;
|
| 125 |
$repository->update();
|
| 126 |
|
| 127 |
// Write back the cache.
|
| 128 |
cache_set('versioncontrol_git_rev_cache', $existing_revs);
|
| 129 |
return TRUE;
|
| 130 |
}
|
| 131 |
|
| 132 |
/**
|
| 133 |
* Execute a Git command using the root context and the command to be executed.
|
| 134 |
* @param string $command Command to execute.
|
| 135 |
* @return mixed Logged output from the command in either array of file pointer form.
|
| 136 |
*/
|
| 137 |
function _versioncontrol_git_log_exec($command) {
|
| 138 |
$logs = array();
|
| 139 |
exec($command, $logs);
|
| 140 |
array_unshift($logs, '');
|
| 141 |
reset($logs); // Reset the array pointer, so that we can use next().
|
| 142 |
return $logs;
|
| 143 |
}
|
| 144 |
|
| 145 |
/**
|
| 146 |
* Get branches from Git using 'branch -l' command.
|
| 147 |
* @return array List of branches.
|
| 148 |
*/
|
| 149 |
function _versioncontrol_git_log_get_branches() {
|
| 150 |
$logs = _versioncontrol_git_log_exec('git show-ref --heads'); // Query branches.
|
| 151 |
$branches = _versioncontrol_git_log_parse_branches($logs); // Parse output.
|
| 152 |
return $branches;
|
| 153 |
}
|
| 154 |
|
| 155 |
/**
|
| 156 |
* Parse the branch list output from Git.
|
| 157 |
*/
|
| 158 |
function _versioncontrol_git_log_parse_branches(&$logs) {
|
| 159 |
$branches = array();
|
| 160 |
while (($line = next($logs)) !== FALSE) {
|
| 161 |
$branches[] = substr(trim($line), 52);
|
| 162 |
}
|
| 163 |
return $branches;
|
| 164 |
}
|
| 165 |
|
| 166 |
/**
|
| 167 |
* Get tags from Git using 'tag -l' command.
|
| 168 |
*/
|
| 169 |
function _versioncontrol_git_log_get_tags() {
|
| 170 |
//TODO: incorporate a --dereference and parse better, saves one git call for tags
|
| 171 |
$logs = _versioncontrol_git_log_exec('git show-ref --tags'); // Query tags.
|
| 172 |
$tags = _versioncontrol_git_log_parse_tags($logs); // Parse output.
|
| 173 |
return $tags;
|
| 174 |
}
|
| 175 |
|
| 176 |
/**
|
| 177 |
* Parse the tag list output from Git.
|
| 178 |
*/
|
| 179 |
function _versioncontrol_git_log_parse_tags(&$logs) {
|
| 180 |
$tags = array();
|
| 181 |
while (($line = next($logs)) !== FALSE) {
|
| 182 |
$tags[] = $branches[] = substr(trim($line), 51);
|
| 183 |
}
|
| 184 |
return $tags;
|
| 185 |
}
|
| 186 |
|
| 187 |
/**
|
| 188 |
* Parses output of git show $tag_name provided by _versioncontrol_git_get_tag_operation() to retrieve an $operation for inserting a tag.
|
| 189 |
* @param $repository
|
| 190 |
* @param sring $tag_name The name of the parsed tag
|
| 191 |
* @param $logs The output of git show
|
| 192 |
* @return array An $operation array which contains the info for the tag.
|
| 193 |
*/
|
| 194 |
function _versioncontrol_git_log_parse_tag_info($repository, &$logs, $tag_commits) {
|
| 195 |
$line = next($logs); // Get op type
|
| 196 |
if ($line === FALSE) {
|
| 197 |
return FALSE;
|
| 198 |
}
|
| 199 |
if ($line == 'commit') {
|
| 200 |
//let's get the author and the date from the tagged commit, better than nothing.
|
| 201 |
$tagged_commit = next($logs); // Get the tagged commit
|
| 202 |
$tag_name = substr(strrchr(next($logs), '/'), 1 ); // Get the name of the tag based on %(refname)
|
| 203 |
next($logs); // Skip these two lines
|
| 204 |
next($logs);
|
| 205 |
// Get the tag/commit message
|
| 206 |
$message = '';
|
| 207 |
$i = 0;
|
| 208 |
while (($line = next($logs)) !== FALSE) {
|
| 209 |
if ($line == 'ENDOFGITTAGOUTPUTMESAGEHERE') {
|
| 210 |
break;
|
| 211 |
}
|
| 212 |
if ($i == 1) {
|
| 213 |
$message .= "\n";
|
| 214 |
}
|
| 215 |
$message .= $line ."\n";
|
| 216 |
$i++;
|
| 217 |
}
|
| 218 |
$constraints = array(
|
| 219 |
'vcs' => array('git'),
|
| 220 |
'repo_ids' => array($repository->repo_id),
|
| 221 |
'types' => array(VERSIONCONTROL_OPERATION_COMMIT),
|
| 222 |
'revisions' => array($tagged_commit)
|
| 223 |
);
|
| 224 |
$op = VersioncontrolOperationCache::getInstance()->getOperations($constraints);
|
| 225 |
$op = array_pop($op);
|
| 226 |
//FIXME get author/commiter
|
| 227 |
$o_operation = new VersioncontrolGitOperation(
|
| 228 |
VERSIONCONTROL_OPERATION_TAG,
|
| 229 |
$op->author,
|
| 230 |
$op->date+1, // We want to be displayed *after* the tagged commit.
|
| 231 |
$tagged_commit,
|
| 232 |
$message,
|
| 233 |
$op->author,
|
| 234 |
$repository
|
| 235 |
);
|
| 236 |
$o_operation->labels = array(
|
| 237 |
0 => new VersioncontrolTag($tag_name, VERSIONCONTROL_ACTION_ADDED, null, $repository)
|
| 238 |
);
|
| 239 |
return $o_operation;
|
| 240 |
}
|
| 241 |
$line = next($logs); // Skip op sha1
|
| 242 |
$tag_name = substr(strrchr(next($logs), '/'), 1 ); // Get the name of the tag based on %(refname)
|
| 243 |
$tagger = next($logs); // Get tagger
|
| 244 |
$date = strtotime(next($logs)); // Get date
|
| 245 |
// Get the tag message
|
| 246 |
$message = '';
|
| 247 |
$i = 0;
|
| 248 |
while (($line = next($logs)) !== FALSE) {
|
| 249 |
if ($line == 'ENDOFGITTAGOUTPUTMESAGEHERE') {
|
| 250 |
break;
|
| 251 |
}
|
| 252 |
if ($i == 1) {
|
| 253 |
$message .= "\n";
|
| 254 |
}
|
| 255 |
$message .= $line ."\n";
|
| 256 |
$i++;
|
| 257 |
}
|
| 258 |
$tagged_commit = $tag_commits[$tag_name];
|
| 259 |
// By now, we're done with the parsing, construct the op array
|
| 260 |
$o_operation = new VersioncontrolGitOperation(
|
| 261 |
VERSIONCONTROL_OPERATION_TAG,
|
| 262 |
$tagger,
|
| 263 |
$date,
|
| 264 |
$tagged_commit,
|
| 265 |
$message,
|
| 266 |
$tagger,
|
| 267 |
$repository
|
| 268 |
);
|
| 269 |
$o_operation->labels = array(
|
| 270 |
0 => new VersioncontrolTag($tag_name, VERSIONCONTROL_ACTION_ADDED, null, $repository)
|
| 271 |
);
|
| 272 |
return $o_operation;
|
| 273 |
}
|
| 274 |
|
| 275 |
/**
|
| 276 |
* Invokes 'git-show tag' to get information about a tag.
|
| 277 |
* It's output is later parsed by _versioncontrol_git_log_parse_tag_info().
|
| 278 |
* @param $repository
|
| 279 |
* @param string $tag The name of the tag.
|
| 280 |
* @return An $operation array which contains the info for the tag.
|
| 281 |
*/
|
| 282 |
function _versioncontrol_git_get_tag_operations($repository, $tags) {
|
| 283 |
$tag_ops = array();
|
| 284 |
$tag_string = '';
|
| 285 |
if (empty($tags)) {
|
| 286 |
return array();
|
| 287 |
}
|
| 288 |
foreach ($tags as $tag) {
|
| 289 |
$tag_string .= escapeshellarg("refs/tags/$tag") .' ';
|
| 290 |
}
|
| 291 |
$format = "%(objecttype)\n%(objectname)\n%(refname)\n%(taggername) %(taggeremail)\n%(taggerdate)\n%(contents)\nENDOFGITTAGOUTPUTMESAGEHERE";
|
| 292 |
$exec = "git for-each-ref --format=\"$format\" $tag_string";
|
| 293 |
$logs_tag_msg = _versioncontrol_git_log_exec($exec);
|
| 294 |
$exec = "git show-ref -d $tag_string";
|
| 295 |
$logs_tag_commits = _versioncontrol_git_log_exec($exec);
|
| 296 |
$tag_commits = array();
|
| 297 |
foreach ($logs_tag_commits as $line) {
|
| 298 |
if (substr($line, -3, 3) == '^{}') {
|
| 299 |
$commit = substr($line, 0, 40);
|
| 300 |
$tag = substr($line, 41);
|
| 301 |
$tag = substr(substr($line, 41, strlen($tag) -3), 10);
|
| 302 |
$tag_commits[$tag] = $commit;
|
| 303 |
}
|
| 304 |
}
|
| 305 |
do {
|
| 306 |
$ret = _versioncontrol_git_log_parse_tag_info($repository, $logs_tag_msg, $tag_commits);
|
| 307 |
if ($ret !== FALSE) {
|
| 308 |
$tag_ops[] = $ret;
|
| 309 |
}
|
| 310 |
}while ($ret !== FALSE);
|
| 311 |
return $tag_ops;
|
| 312 |
}
|
| 313 |
|
| 314 |
/**
|
| 315 |
* Does all the processing for all new tags.
|
| 316 |
* @param $repository
|
| 317 |
* @param array $new_tags An array of strings for all new tags which shall be processed
|
| 318 |
*/
|
| 319 |
function _versioncontrol_git_process_tags($repository, $new_tags) {
|
| 320 |
$tag_ops = _versioncontrol_git_get_tag_operations($repository, $new_tags);
|
| 321 |
foreach ($tag_ops as $tag_op) {
|
| 322 |
$op_items = array();
|
| 323 |
$tag_op->insert($op_items);
|
| 324 |
$constraints = array(
|
| 325 |
'vcs' => array('git'),
|
| 326 |
'repo_ids' => array($repository->repo_id),
|
| 327 |
'types' => array(VERSIONCONTROL_OPERATION_COMMIT),
|
| 328 |
'revisions' => array($tag_op->revision)
|
| 329 |
);
|
| 330 |
$tag_commits = VersioncontrolOperationCache::getInstance()->getOperations($constraints);
|
| 331 |
foreach ($tag_commits as $vc_op_id => $tag_commit_op) {
|
| 332 |
$tag_commit_op->labels[] = new VersioncontrolTag(
|
| 333 |
$tag_op['labels'][0]['name'],
|
| 334 |
VERSIONCONTROL_ACTION_MODIFIED,
|
| 335 |
null,
|
| 336 |
$repository
|
| 337 |
);
|
| 338 |
$tag_commit_op->updateLabels($tag_commit_op->labels);
|
| 339 |
}
|
| 340 |
}
|
| 341 |
}
|
| 342 |
|
| 343 |
/**
|
| 344 |
* Get all commits from Git using 'git log' command.
|
| 345 |
* @param $repository
|
| 346 |
* @param string $range the computed range for the branch we check
|
| 347 |
* @param array $branches_per_commit An array of all commits we will encounter with a list of branches they are in.
|
| 348 |
*/
|
| 349 |
function _versioncontrol_git_process_commits($repository, $revision, &$branches_per_commit, &$existing_revs) {
|
| 350 |
$rev_shell = escapeshellarg($revision);
|
| 351 |
$command = "git log $rev_shell --numstat --summary --pretty=format:\"%H%n%P%n%aN <%ae>%n%ct%n%s%n%b%nENDOFOUTPUTGITMESSAGEHERE\" -n 1 --";
|
| 352 |
$logs = _versioncontrol_git_log_exec($command);
|
| 353 |
_versioncontrol_git_log_parse_commits($repository, $logs, $branches_per_commit, $existing_revs); // Parse the info from the raw output.
|
| 354 |
}
|
| 355 |
|
| 356 |
/**
|
| 357 |
* This function returns all commits in the given range.
|
| 358 |
* It is used to get all new commits in a branch, which is specified by @p $range
|
| 359 |
* @param $repository
|
| 360 |
* @param string $range The range of the commits to retrieve
|
| 361 |
* @return array An array of strings with all commit id's in it
|
| 362 |
*/
|
| 363 |
function _versioncontrol_git_log_get_commits_in_branch($repository, $range) {
|
| 364 |
$logs = _versioncontrol_git_log_exec("git rev-list $range --reverse --"); // Query tags.
|
| 365 |
$commits = array();
|
| 366 |
while (($line = next($logs)) !== FALSE) {
|
| 367 |
$commits[] = trim($line);
|
| 368 |
}
|
| 369 |
return $commits;
|
| 370 |
}
|
| 371 |
|
| 372 |
/**
|
| 373 |
* A helper function to get the source_items.
|
| 374 |
* @param $repository
|
| 375 |
* @param $revision The revision of the parent item.
|
| 376 |
* @param $filename The filename of the parent item.
|
| 377 |
* @return array An $item array ready to use for $operation['source_items']
|
| 378 |
*/
|
| 379 |
function _versioncontrol_git_get_source_item_helper($repository, $revision, $filename, $branches) {
|
| 380 |
$branch_names = array();
|
| 381 |
foreach ($branches as $branch) {
|
| 382 |
$branch_names[] = $branch['name'];
|
| 383 |
}
|
| 384 |
$constraints = array(
|
| 385 |
'vcs' => array('git'),
|
| 386 |
'repo_ids' => array($repository->repo_id),
|
| 387 |
'types' => array(VERSIONCONTROL_OPERATION_COMMIT),
|
| 388 |
'paths' => array($filename),
|
| 389 |
'branches' => $branch_names
|
| 390 |
);
|
| 391 |
$commit_op = versioncontrol_get_operations($constraints);
|
| 392 |
ksort($commit_op);
|
| 393 |
$commit_op = array_pop($commit_op);
|
| 394 |
$op_items = versioncontrol_get_operation_items($commit_op);
|
| 395 |
$type = $op_items[$filename]['type'] ? $op_items[$filename]['type'] : VERSIONCONTROL_ITEM_FILE;
|
| 396 |
// ['action'] not needed for source items :)
|
| 397 |
return array(
|
| 398 |
'path' => $filename,
|
| 399 |
'type' => $type,
|
| 400 |
'revision' => $op_items[$filename]['revision'],
|
| 401 |
);
|
| 402 |
}
|
| 403 |
|
| 404 |
/**
|
| 405 |
* A function to a source_item for a specific file.
|
| 406 |
* @param $repository as we get it from the API
|
| 407 |
* @param string $filename the revision of the current item we shall get it's source from
|
| 408 |
* @return array $source_items array for use in an $operation.
|
| 409 |
*/
|
| 410 |
function _versioncontrol_git_get_source_item($repository, $filename, $parents, $branches, $commit_rev) {
|
| 411 |
$ret = array();
|
| 412 |
if (count($parents) == 1) {
|
| 413 |
$filenameg = substr($filename, 1);
|
| 414 |
$filenameg = escapeshellarg($filenameg);
|
| 415 |
$commit_rev = escapeshellarg($commit_rev);
|
| 416 |
$exec = "git rev-list -n 1 ". $commit_rev ."^ -- $filenameg";
|
| 417 |
$logs = _versioncontrol_git_log_exec($exec); // Query tags.
|
| 418 |
$revision = next($logs);
|
| 419 |
$ret = array(
|
| 420 |
new VersioncontrolGitItem(
|
| 421 |
VERSIONCONTROL_ITEM_FILE,
|
| 422 |
$filename,
|
| 423 |
$revision,
|
| 424 |
null,
|
| 425 |
$repository
|
| 426 |
)
|
| 427 |
);
|
| 428 |
}
|
| 429 |
else {
|
| 430 |
foreach ($parents as $rev) {
|
| 431 |
$ret[] = _versioncontrol_git_get_source_item_helper($repository, $rev, $filename, $branches);
|
| 432 |
}
|
| 433 |
}
|
| 434 |
return $ret;
|
| 435 |
}
|
| 436 |
|
| 437 |
function _versioncontrol_git_insert_commit($repository, $date, $username, $message, $revision, $branches, $op_items) {
|
| 438 |
//FIXME: get author and commiter
|
| 439 |
$op = new VersioncontrolGitOperation(VERSIONCONTROL_OPERATION_COMMIT, $username, $date, $revision, $message, $username, $repository);
|
| 440 |
$op->labels = $branches;
|
| 441 |
$op->insert($op_items);
|
| 442 |
}
|
| 443 |
|
| 444 |
function _versioncontrol_git_parse_items($repository, &$logs, &$line, $revision, &$branches_per_commit, $parents, $merge) {
|
| 445 |
$op_items = array();
|
| 446 |
$read = FALSE;
|
| 447 |
|
| 448 |
// Read file line revisions.
|
| 449 |
do {
|
| 450 |
if (preg_match('/^(\S+)'."\t".'(\S+)'."\t".'(.+)$/', $line, $matches)) { // Begins with num lines added and matches expression.
|
| 451 |
$read = TRUE;
|
| 452 |
$path = '/'. $matches[3];
|
| 453 |
$op_items[$path] = new VersioncontrolGitItem(
|
| 454 |
VERSIONCONTROL_ITEM_FILE,
|
| 455 |
$path,
|
| 456 |
$revision,
|
| 457 |
//'source_items' => array(),//filled later
|
| 458 |
($merge ? VERSIONCONTROL_ACTION_MERGED : VERSIONCONTROL_ACTION_MODIFIED),
|
| 459 |
$repository
|
| 460 |
);
|
| 461 |
if (is_numeric($matches[1]) && is_numeric($matches[2])) {
|
| 462 |
$op_items[$path]->line_changes = array(
|
| 463 |
'added' => $matches[1],
|
| 464 |
'removed' => $matches[2]
|
| 465 |
);
|
| 466 |
}
|
| 467 |
}
|
| 468 |
else {
|
| 469 |
break;
|
| 470 |
}
|
| 471 |
} while (($line = next($logs)) !== FALSE);
|
| 472 |
// Read file actions.
|
| 473 |
do {
|
| 474 |
if (preg_match('/^ (\S+) (\S+) (\S+) (.+)$/', $line, $matches)) { // Ensure that same file, they should be in same order.
|
| 475 |
$read = TRUE;
|
| 476 |
// We also can get 'mode' here if someone changes the file permissions.
|
| 477 |
if ($matches[1] == 'create') {
|
| 478 |
$op_items['/'. $matches[4]]->action = VERSIONCONTROL_ACTION_ADDED;
|
| 479 |
}
|
| 480 |
else if ($matches[1] == 'delete') {
|
| 481 |
$op_items['/'. $matches[4]]->action = VERSIONCONTROL_ACTION_DELETED;
|
| 482 |
}
|
| 483 |
}
|
| 484 |
else {
|
| 485 |
break;
|
| 486 |
}
|
| 487 |
}
|
| 488 |
while (($line = next($logs)) !== FALSE);
|
| 489 |
|
| 490 |
//This is an inconsistency in git log output...
|
| 491 |
if ($read) {
|
| 492 |
$line = next($logs);
|
| 493 |
}
|
| 494 |
foreach ($op_items as $path => $item) {
|
| 495 |
if ($item->action != VERSIONCONTROL_ACTION_ADDED) {
|
| 496 |
$op_items[$path]->source_items = _versioncontrol_git_get_source_item($repository, $path, $parents, $branches_per_commit[$revision], $revision);
|
| 497 |
}
|
| 498 |
}
|
| 499 |
return $op_items;
|
| 500 |
}
|
| 501 |
|
| 502 |
function _versioncontrol_git_check_already_parsed_commits($repository, $revision, &$branches_per_commit, &$line, &$logs) {
|
| 503 |
$constraints = array(
|
| 504 |
'types' => array(VERSIONCONTROL_OPERATION_COMMIT),
|
| 505 |
'revisions' => array($revision),
|
| 506 |
'vcs' => array('git'),
|
| 507 |
'repo_ids' => array($repository->repo_id)
|
| 508 |
);
|
| 509 |
$same_rev_commit = VersioncontrolOperationCache::getInstance()->getOperations($constraints);
|
| 510 |
$adjusted_commit = FALSE;
|
| 511 |
foreach ($same_rev_commit as $vc_op_id => $rev_commit) {
|
| 512 |
// We already have a commit with this revision recorded, so use a faster parser then.
|
| 513 |
$adjusted_commit = TRUE;
|
| 514 |
$labels = array();
|
| 515 |
foreach ( $rev_commit->labels as $label) {
|
| 516 |
if ($label['type'] == VERSIONCONTROL_OPERATION_TAG) {
|
| 517 |
$labels[] = $label;
|
| 518 |
}
|
| 519 |
}
|
| 520 |
$labels = array_merge($labels, $branches_per_commit[$revision]);
|
| 521 |
$rev_commit->updateLabels($labels);
|
| 522 |
$line = next($logs); // Get $parents
|
| 523 |
$line = next($logs); // Get Author
|
| 524 |
$line = next($logs); // Get Date as Timestamp
|
| 525 |
// Pretend message parsing
|
| 526 |
while (($line = next($logs)) !== FALSE) {
|
| 527 |
if (trim($line) == 'ENDOFOUTPUTGITMESSAGEHERE') {
|
| 528 |
break;
|
| 529 |
}
|
| 530 |
}
|
| 531 |
$line = next($logs);
|
| 532 |
// Skip everything --summary or --numstat related output
|
| 533 |
while (!(preg_match("/^([a-f0-9]{40})$/", trim($line))) && $line !== FALSE) {
|
| 534 |
$line = next($logs);
|
| 535 |
}
|
| 536 |
//$branches_per_commit[$revision] = TRUE;
|
| 537 |
}
|
| 538 |
return $adjusted_commit;
|
| 539 |
}
|
| 540 |
|
| 541 |
/**
|
| 542 |
* Parse the output of 'git log' and insert commits based on it's data.
|
| 543 |
*
|
| 544 |
* @param $repository
|
| 545 |
* The repository array, as given by the Version Control API.
|
| 546 |
* @param $logs The output of 'git log' to parse
|
| 547 |
* @param array $branches_per_commit An array which has all branches for all commits in it.
|
| 548 |
* It is used to construct $operation['labels'].
|
| 549 |
*/
|
| 550 |
function _versioncontrol_git_log_parse_commits($repository, &$logs, &$branches_per_commit, &$existing_revs) {
|
| 551 |
// If the log was retrieved by taking the return value of exec(), we've
|
| 552 |
// got an array and navigate it via next(). If we stored the log in a
|
| 553 |
// temporary file, $logs is a file handle that we need to fgets() instead.
|
| 554 |
$root_path = $repository['root'];
|
| 555 |
$line = next($logs); // Get Revision
|
| 556 |
$merge = FALSE;
|
| 557 |
// $line already points to the revision
|
| 558 |
$revision = trim($line);
|
| 559 |
foreach ($branches_per_commit[$revision] as $label) {
|
| 560 |
$existing_revs[$label['name']][$revision] = TRUE;
|
| 561 |
}
|
| 562 |
|
| 563 |
$adjusted_commit = _versioncontrol_git_check_already_parsed_commits($repository,
|
| 564 |
$revision, $branches_per_commit, $line, $logs);
|
| 565 |
if ($adjusted_commit) {
|
| 566 |
return;
|
| 567 |
}
|
| 568 |
|
| 569 |
$line = next($logs); // Get $parents
|
| 570 |
$parents = explode(" ", trim($line));
|
| 571 |
if ($parents[0] == '') {
|
| 572 |
$parents = array();
|
| 573 |
}
|
| 574 |
if (isset($parents[1])) {
|
| 575 |
$merge = TRUE;
|
| 576 |
}
|
| 577 |
$line = next($logs); // Get Author
|
| 578 |
$username = trim($line);
|
| 579 |
$line = next($logs); // Get Date as Timestamp
|
| 580 |
$date = trim($line);
|
| 581 |
// Get revision message.
|
| 582 |
$message = '';
|
| 583 |
$i = 0;
|
| 584 |
while (($line = next($logs)) !== FALSE) {
|
| 585 |
$line = trim($line);
|
| 586 |
if ($line == 'ENDOFOUTPUTGITMESSAGEHERE') {
|
| 587 |
if (substr($message, -2) === "\n\n") {
|
| 588 |
$message = substr($message, 0, strlen($message) - 1);
|
| 589 |
}
|
| 590 |
break;
|
| 591 |
}
|
| 592 |
if ($i == 1) {
|
| 593 |
$message .= "\n";
|
| 594 |
}
|
| 595 |
$message .= $line ."\n";
|
| 596 |
$i++;
|
| 597 |
}
|
| 598 |
$line = next($logs); // Points to either the next entry or the first items modified or to the file actions
|
| 599 |
// Get the items
|
| 600 |
$op_items = _versioncontrol_git_parse_items($repository, $logs, $line, $revision,
|
| 601 |
$branches_per_commit, $parents, $merge);
|
| 602 |
|
| 603 |
_versioncontrol_git_insert_commit($repository, $date, $username,
|
| 604 |
$message, $revision, $branches_per_commit[$revision], $op_items);
|
| 605 |
}
|