| [ Index ] |
|
Code source de Horde 3.1.3 |
1 <?php 2 3 require_once dirname(__FILE__) . '/rcs.php'; 4 5 /** 6 * VC_cvs implementation. 7 * 8 * Copyright 2000-2006 Anil Madhavapeddy, <anil@recoil.org> 9 * 10 * $Horde: framework/VC/VC/cvs.php,v 1.32.2.24 2006/05/12 04:47:08 chuck Exp $ 11 * 12 * @author Anil Madhavapeddy <anil@recoil.org> 13 * @package VC 14 */ 15 class VC_cvs extends VC_rcs { 16 17 /** 18 * Constructor. 19 * 20 * @param array $params Any parameter the class expects. 21 * <pre> 22 * Current parameters: 23 * 'sourceroot': The source root for this 24 * repository 25 * 'paths': Hash with the locations of all 26 * necessary binaries: 'rcsdiff', 'rlog', 27 * 'cvsps', 'cvsps_home' and the temp 28 * path: 'temp' 29 * </pre> 30 */ 31 function VC_cvs($params) 32 { 33 $this->_sourceroot = $params['sourceroot']; 34 $this->_paths = $params['paths']; 35 } 36 37 function isFile($where) 38 { 39 return @is_file($where . ',v'); 40 } 41 42 function &queryDir($where) 43 { 44 $dir = new VC_Directory_cvs($this, $where); 45 return $dir; 46 } 47 48 function getCheckout($file, $rev) 49 { 50 return VC_Checkout_cvs::get($this, $file->queryFullPath(), $rev); 51 } 52 53 function getDiff($file, $rev1, $rev2, $type = 'context', $num = 3, $ws = true) 54 { 55 return VC_Diff_cvs::get($this, $file, $rev1, $rev2, $type, $num, $ws); 56 } 57 58 function &getFileObject($filename, $cache = null, $quicklog = false) 59 { 60 if (substr($filename, 0, 1) != '/') { 61 $filename = '/' . $filename; 62 } 63 $fo = &VC_File_cvs::getFileObject($this, $this->sourceroot() . $filename, $cache, $quicklog); 64 return $fo; 65 } 66 67 function &getAnnotateObject($filename) 68 { 69 $blame = new VC_Annotate_cvs($this, $filename, Util::getTempFile('vc', true, $this->_paths['temp'])); 70 return $blame; 71 } 72 73 function &getPatchsetObject($filename, $cache = null) 74 { 75 $po = &VC_Patchset_cvs::getPatchsetObject($this, $this->sourceroot() . '/' . $filename, $cache); 76 return $po; 77 } 78 79 } 80 81 /** 82 * VC_cvs annotate class. 83 * 84 * Anil Madhavapeddy, <anil@recoil.org> 85 * 86 * @author Anil Madhavapeddy <anil@recoil.org> 87 * @package VC 88 */ 89 class VC_Annotate_cvs { 90 91 var $file; 92 var $rep; 93 var $tmpfile; 94 95 function VC_Annotate_cvs($rep, $file, $tmpfile) 96 { 97 $this->rep = $rep; 98 $this->file = $file; 99 $this->tmpfile = $tmpfile; 100 } 101 102 function doAnnotate($rev) 103 { 104 /* Make sure that the file values for this object is valid. */ 105 if (is_a($this->file, 'PEAR_Error')) { 106 return false; 107 } 108 109 /* Make sure that the cvsrep parameter is valid. */ 110 if (is_a($this->rep, 'PEAR_Error')) { 111 return false; 112 } 113 114 if (!VC_Revision::valid($rev)) { 115 return false; 116 } 117 118 $where = $this->file->queryModulePath(); 119 $sourceroot = $this->rep->sourceroot(); 120 121 $pipe = popen($this->rep->getPath('cvs') . ' -n server > ' . $this->tmpfile, VC_WINDOWS ? 'wb' : 'w'); 122 123 $out = array(); 124 $out[] = "Root $sourceroot"; 125 $out[] = 'Valid-responses ok error Valid-requests Checked-in Updated Merged Removed M E'; 126 $out[] = 'UseUnchanged'; 127 $out[] = 'Argument -r'; 128 $out[] = "Argument $rev"; 129 $out[] = "Argument $where"; 130 $dirs = explode('/', dirname($where)); 131 while (count($dirs)) { 132 $out[] = 'Directory ' . implode('/', $dirs); 133 $out[] = "$sourceroot/" . implode('/', $dirs); 134 array_pop($dirs); 135 } 136 $out[] = 'Directory .'; 137 $out[] = $sourceroot; 138 $out[] = 'annotate'; 139 140 foreach ($out as $line) { 141 fwrite($pipe, "$line\n"); 142 } 143 pclose($pipe); 144 145 if (!($fl = fopen($this->tmpfile, VC_WINDOWS ? 'rb' : 'r'))) { 146 return false; 147 } 148 149 $lines = array(); 150 $line = fgets($fl, 4096); 151 152 // Windows versions of cvs always return $where with forwards slashes. 153 if (VC_WINDOWS) { 154 $where = str_replace(DIRECTORY_SEPARATOR, '/', $where); 155 } 156 157 while ($line && !preg_match("|^E\s+Annotations for $where|", $line)) { 158 $line = fgets($fl, 4096); 159 } 160 161 if (!$line) { 162 return PEAR::raiseError('Unable to annotate; server said: ' . $line); 163 } 164 165 $lineno = 1; 166 while ($line = fgets($fl, 4096)) { 167 if (preg_match('/^M\s+([\d\.]+)\s+\((.+)\s+(\d+-\w+-\d+)\):.(.*)$/', $line, $regs)) { 168 $entry = array(); 169 $entry['rev'] = $regs[1]; 170 $entry['author'] = trim($regs[2]); 171 $entry['date'] = $regs[3]; 172 $entry['line'] = $regs[4]; 173 $entry['lineno'] = $lineno++; 174 $lines[] = $entry; 175 } 176 } 177 178 fclose($fl); 179 return $lines; 180 } 181 182 } 183 184 /** 185 * VC_cvs checkout class. 186 * 187 * See the README file that came with this library for more 188 * information, and read the inline documentation. 189 * 190 * Anil Madhavapeddy, <anil@recoil.org> 191 * 192 * @author Anil Madhavapeddy <anil@recoil.org> 193 * @package VC 194 */ 195 class VC_Checkout_cvs { 196 197 /** 198 * Static function which returns a file pointing to the head of the 199 * requested revision of an RCS file. 200 * 201 * @param VC_cvs $rep A repository object 202 * @param string $fullname Fully qualified pathname of the desired RCS 203 * file to checkout 204 * @param string $rev RCS revision number to check out 205 * 206 * @return resource|object Either a PEAR_Error object, or a stream 207 * pointer to the head of the checkout 208 */ 209 function get($rep, $fullname, $rev) 210 { 211 if (!VC_Revision::valid($rev)) { 212 return PEAR::raiseError('Invalid revision number'); 213 } 214 215 if (VC_WINDOWS) { 216 $mode = 'rb'; 217 $q_name = '"' . escapeshellcmd(str_replace('\\', '/', $fullname)) . '"'; 218 } else { 219 $mode = 'r'; 220 $q_name = escapeshellarg($fullname); 221 } 222 223 if (!($RCS = popen($rep->getPath('co') . " -p$rev $q_name 2>&1", $mode))) { 224 return PEAR::raiseError('Couldn\'t perform checkout of the requested file'); 225 } 226 227 /* First line from co should be of the form : 228 * /path/to/filename,v --> standard out 229 * and we check that this is the case and error otherwise 230 */ 231 232 $co = fgets($RCS, 1024); 233 if (!preg_match('/^([\S ]+),v\s+-->\s+st(andar)?d ?out(put)?\s*$/', $co, $regs) || $regs[1].',v' != $fullname) { 234 return PEAR::raiseError('Unexpected output from checkout: ' . $co); 235 } 236 237 /* Next line from co is of the form: 238 * revision 1.2.3 239 * TODO: compare this to $rev for consistency, atm we just 240 * discard the value to move input pointer along - avsm 241 */ 242 $co = fgets($RCS, 1024); 243 244 return $RCS; 245 } 246 247 } 248 249 /** 250 * VC_cvs diff class. 251 * 252 * Copyright Anil Madhavapeddy, <anil@recoil.org> 253 * 254 * @author Anil Madhavapeddy <anil@recoil.org> 255 * @package VC 256 */ 257 class VC_Diff_cvs { 258 259 /** 260 * Obtain the differences between two revisions of a file. 261 * 262 * @param VC_cvs $rep A repository object. 263 * @param VC_File_cvs $file The desired file. 264 * @param string $rev1 Original revision number to compare from. 265 * @param string $rev2 New revision number to compare against. 266 * @param string $type The type of diff (e.g. 'unified'). 267 * @param integer $num Number of lines to be used in context and 268 * unified diffs. 269 * @param boolean $ws Show whitespace in the diff? 270 * 271 * @return string|boolean False on failure, or a string containing the 272 * diff on success. 273 */ 274 function get($rep, $file, $rev1, $rev2, $type = 'context', $num = 3, 275 $ws = true) 276 { 277 /* Make sure that the file parameter is valid. */ 278 if (is_a($file, 'PEAR_Error')) { 279 return false; 280 } 281 282 /* Check that the revision numbers are valid. */ 283 $rev1 = VC_Revision::valid($rev1) ? $rev1 : '1.1'; 284 $rev2 = VC_Revision::valid($rev1) ? $rev2 : '1.1'; 285 286 $fullName = $file->queryFullPath(); 287 $diff = array(); 288 $options = '-kk '; 289 if (!$ws) { 290 $opts = ' -bB '; 291 $options .= $opts; 292 } else { 293 $opts = ''; 294 } 295 switch ($type) { 296 case 'context': 297 $options = $opts . '-p --context=' . (int)$num; 298 break; 299 300 case 'unified': 301 $options = $opts . '-p --unified=' . (int)$num; 302 break; 303 304 case 'column': 305 $options = $opts . '--side-by-side --width=120'; 306 break; 307 308 case 'ed': 309 $options = $opts . '-e'; 310 break; 311 } 312 313 // Windows versions of cvs always return $where with forwards slashes. 314 if (VC_WINDOWS) { 315 $fullName = str_replace(DIRECTORY_SEPARATOR, '/', $fullName); 316 } 317 318 // TODO: add options for $hr options - however these may not be 319 // compatible with some diffs. 320 $command = $rep->getPath('rcsdiff') . " $options -r$rev1 -r$rev2 \"" . escapeshellcmd($fullName) . '" 2>&1'; 321 if (VC_WINDOWS) { 322 $command .= ' < "' . __FILE__ . '"'; 323 } 324 325 exec($command, $diff, $retval); 326 return ($retval > 0) ? $diff : array(); 327 } 328 329 } 330 331 /** 332 * VC_cvs directory class. 333 * 334 * Copyright Anil Madhavapeddy, <anil@recoil.org> 335 * 336 * @author Anil Madhavapeddy <anil@recoil.org> 337 * @package VC 338 */ 339 class VC_Directory_cvs { 340 341 var $rep; 342 var $dirName; 343 var $files; 344 var $atticFiles; 345 var $mergedFiles; 346 var $dirs; 347 var $parent; 348 var $moduleName; 349 var $sortDir; 350 351 /** 352 * Creates a CVS Directory object to store information 353 * about the files in a single directory in the repository. 354 * 355 * @param VC_cvs $rep A repository object 356 * @param string $dn Path to the directory. 357 * @param VC_Directory_cvs $pn The parent VC_Directory to this one. 358 */ 359 function VC_Directory_cvs($rep, $dn, $pn = '') 360 { 361 $this->rep = $rep; 362 $this->parent = $pn; 363 $this->moduleName = $dn; 364 $this->dirName = $rep->sourceroot() . "/$dn"; 365 $this->files = array(); 366 $this->dirs = array(); 367 } 368 369 /** 370 * Return fully qualified pathname to this directory with no 371 * trailing /. 372 * 373 * @return string Pathname of this directory 374 */ 375 function queryDir() 376 { 377 return $this->dirName; 378 } 379 380 function &queryDirList() 381 { 382 reset($this->dirs); 383 return $this->dirs; 384 } 385 386 function &queryFileList($showattic = false) 387 { 388 if ($showattic && isset($this->mergedFiles)) { 389 return $this->mergedFiles; 390 } else { 391 return $this->files; 392 } 393 } 394 395 /** 396 * Tell the object to open and browse its current directory, and 397 * retrieve a list of all the objects in there. It then populates 398 * the file/directory stack and makes it available for retrieval. 399 * 400 * @return boolean|object PEAR_Error object on an error, true on success. 401 */ 402 function browseDir($cache = null, $quicklog = true, $showattic = false) 403 { 404 /* Make sure we are trying to list a directory */ 405 if (!@is_dir($this->dirName)) { 406 return PEAR::raiseError('Unable to find directory ' . $this->dirName); 407 } 408 409 /* Open the directory for reading its contents */ 410 if (!($DIR = @opendir($this->dirName))) { 411 $errmsg = (!empty($php_errormsg)) ? $php_errormsg : 'Permission denied'; 412 return PEAR::raiseError($errmsg); 413 } 414 415 /* Create two arrays - one of all the files, and the other of 416 * all the directories. */ 417 while (($name = readdir($DIR)) !== false) { 418 if ($name == '.' || $name == '..') { 419 continue; 420 } 421 422 $path = $this->dirName . '/' . $name; 423 if (@is_dir($path)) { 424 /* Skip Attic directory. */ 425 if ($name != 'Attic') { 426 $this->dirs[] = $name; 427 } 428 } elseif (@is_file($path) && substr($name, -2) == ',v') { 429 /* Spawn a new file object to represent this file. */ 430 $fl = $this->rep->getFileObject(substr($path, strlen($this->rep->sourceroot()), -2), $cache, $quicklog); 431 if (is_a($fl, 'PEAR_Error')) { 432 return $fl; 433 } else { 434 $this->files[] = $fl; 435 } 436 } 437 } 438 439 /* Close the filehandle; we've now got a list of dirs and 440 * files. */ 441 closedir($DIR); 442 443 /* If we want to merge the attic, add it in here. */ 444 if ($showattic) { 445 $atticDir = new VC_Directory_cvs($this->rep, $this->moduleName . '/Attic', $this); 446 if (!is_a($atticDir->browseDir($cache, $quicklog), 'PEAR_Error')) { 447 $this->atticFiles = $atticDir->queryFileList(); 448 $this->mergedFiles = array_merge($this->files, $this->atticFiles); 449 } 450 } 451 452 return true; 453 } 454 455 /** 456 * Sort the contents of the directory in a given fashion and 457 * order. 458 * 459 * @param integer $how Of the form VC_SORT_* where * can be: 460 * NONE, NAME, AGE, REV for sorting by name, age or 461 * revision. 462 * @param integer $dir Of the form VC_SORT_* where * can be: 463 * ASCENDING, DESCENDING for the order of the sort. 464 */ 465 function applySort($how = VC_SORT_NONE, $dir = VC_SORT_ASCENDING) 466 { 467 // Always sort directories by name. 468 natcasesort($this->dirs); 469 470 $this->doFileSort($this->files, $how, $dir); 471 if (isset($this->atticFiles)) { 472 $this->doFileSort($this->atticFiles, $how, $dir); 473 } 474 if (isset($this->mergedFiles)) { 475 $this->doFileSort($this->mergedFiles, $how, $dir); 476 } 477 } 478 479 function doFileSort(&$fileList, $how = VC_SORT_NONE, $dir = VC_SORT_ASCENDING) 480 { 481 $this->sortDir = $dir; 482 483 switch ($how) { 484 case VC_SORT_AGE: 485 usort($fileList, array($this, 'fileAgeSort')); 486 break; 487 488 case VC_SORT_NAME: 489 usort($fileList, array($this, 'fileNameSort')); 490 break; 491 492 case VC_SORT_AUTHOR: 493 usort($fileList, array($this, 'fileAuthorSort')); 494 break; 495 496 case VC_SORT_REV: 497 usort($fileList, array($this, 'fileRevSort')); 498 break; 499 } 500 501 unset($this->sortDir); 502 } 503 504 /** 505 * Sort function for file age. 506 */ 507 function fileAgeSort($a, $b) 508 { 509 $aa = $a->queryLastLog(); 510 $bb = $b->queryLastLog(); 511 if ($aa->queryDate() == $bb->queryDate()) { 512 return 0; 513 } elseif ($this->sortDir == VC_SORT_ASCENDING) { 514 return ($aa->queryDate() < $bb->queryDate()) ? 1 : -1; 515 } else { 516 return ($bb->queryDate() < $aa->queryDate()) ? 1 : -1; 517 } 518 } 519 520 /** 521 * Sort function by author name. 522 */ 523 function fileAuthorSort($a, $b) 524 { 525 $aa = $a->queryLastLog(); 526 $bb = $b->queryLastLog(); 527 if ($aa->queryAuthor() == $bb->queryAuthor()) { 528 return 0; 529 } elseif ($this->sortDir == VC_SORT_ASCENDING) { 530 return ($aa->queryAuthor() > $bb->queryAuthor()) ? 1 : -1; 531 } else { 532 return ($bb->queryAuthor() > $aa->queryAuthor()) ? 1 : -1; 533 } 534 } 535 536 /** 537 * Sort function for filename. 538 */ 539 function fileNameSort($a, $b) 540 { 541 if ($this->sortDir == VC_SORT_ASCENDING) { 542 return strcasecmp($a->name, $b->name); 543 } else { 544 return strcasecmp($b->name, $a->name); 545 } 546 } 547 548 /** 549 * Sort function for revision. 550 */ 551 function fileRevSort($a, $b) 552 { 553 if ($this->sortDir == VC_SORT_ASCENDING) { 554 return VC_Revision::cmp($a->queryHead(), $b->queryHead()); 555 } else { 556 return VC_Revision::cmp($b->queryHead(), $a->queryHead()); 557 } 558 } 559 560 } 561 562 /** 563 * VC_cvs file class. 564 * 565 * Copyright Anil Madhavapeddy, <anil@recoil.org> 566 * 567 * @author Anil Madhavapeddy <anil@recoil.org> 568 * @package VC 569 */ 570 class VC_File_cvs extends VC_File { 571 572 /** 573 * Create a repository file object, and give it information about 574 * what its parent directory and repository objects are. 575 * 576 * @param string $fl Full path to this file. 577 */ 578 function VC_File_cvs($fl, $quicklog = false) 579 { 580 $fl .= ',v'; 581 $this->name = basename($fl); 582 $this->dir = dirname($fl); 583 $this->logs = array(); 584 $this->quicklog = $quicklog; 585 $this->revs = array(); 586 $this->branches = array(); 587 } 588 589 function &getFileObject($rep, $filename, $cache = null, $quicklog = false) 590 { 591 /* The version of the cached data. Increment this whenever the 592 * internal storage format changes, such that we must 593 * invalidate prior cached data. */ 594 $cacheVersion = 2; 595 $cacheId = $rep->sourceroot() . '_n' . $filename . '_f' . (int)$quicklog . '_v' . $cacheVersion; 596 597 $ctime = time() - @filemtime($filename . ',v'); 598 if ($cache && 599 $cache->exists($cacheId, $ctime)) { 600 $fileOb = unserialize($cache->get($cacheId, $ctime)); 601 $fileOb->setRepository($rep); 602 } else { 603 $fileOb = new VC_File_cvs($filename, $quicklog); 604 $fileOb->setRepository($rep); 605 if (is_a(($result = $fileOb->getBrowseInfo()), 'PEAR_Error')) { 606 return $result; 607 } 608 $fileOb->applySort(VC_SORT_AGE); 609 610 if ($cache) { 611 $cache->set($cacheId, serialize($fileOb)); 612 } 613 } 614 615 return $fileOb; 616 } 617 618 /** 619 * If this file is present in an Attic directory, this indicates 620 * it. 621 * 622 * @return boolean True if file is in the Attic, and false otherwise 623 */ 624 function isAtticFile() 625 { 626 return substr($this->dir, -5) == 'Attic'; 627 } 628 629 /** 630 * Returns the name of the current file as in the repository 631 * 632 * @return string Filename (without the path) 633 */ 634 function queryRepositoryName() 635 { 636 return $this->name; 637 } 638 639 /** 640 * Returns name of the current file without the repository 641 * extensions (usually ,v) 642 * 643 * @return string Filename without repository extension 644 */ 645 function queryName() 646 { 647 return preg_replace('/,v$/', '', $this->name); 648 } 649 650 /** 651 * Return the last revision of the current file on the HEAD branch 652 * 653 * @return string|object Last revision of the current file or PEAR_Error 654 * on failure. 655 */ 656 function queryRevision() 657 { 658 if (!isset($this->revs[0])) { 659 return PEAR::raiseError('No revisions'); 660 } 661 return $this->revs[0]; 662 } 663 664 function queryPreviousRevision($rev) 665 { 666 return VC_Revision::prev($rev); 667 } 668 669 /** 670 * Return the HEAD (most recent) revision number for this file. 671 * 672 * @return string HEAD revision number 673 */ 674 function queryHead() 675 { 676 return $this->head; 677 } 678 679 /** 680 * Return the last VC_log object in the file. 681 * 682 * @return VC_log of the last entry in the file 683 */ 684 function queryLastLog() 685 { 686 if (!isset($this->revs[0]) || !isset($this->logs[$this->revs[0]])) { 687 return PEAR::raiseError('No revisions'); 688 } 689 return $this->logs[$this->revs[0]]; 690 } 691 692 /** 693 * Sort the list of VC_log objects that this file contains. 694 * 695 * @param integer $how VC_SORT_REV (sort by revision), 696 * VC_SORT_NAME (sort by author name), 697 * VC_SORT_AGE (sort by commit date) 698 */ 699 function applySort($how = VC_SORT_REV) 700 { 701 switch ($how) { 702 case VC_SORT_NAME: 703 $func = 'Name'; 704 break; 705 706 case VC_SORT_AGE: 707 $func = 'Age'; 708 break; 709 710 case VC_SORT_REV: 711 default: 712 $func = 'Revision'; 713 } 714 715 uasort($this->logs, array($this, "sortBy$func")); 716 return true; 717 } 718 719 /** 720 * The sortBy*() functions are internally used by applySort. 721 */ 722 function sortByRevision($a, $b) 723 { 724 return VC_Revision::cmp($b->rev, $a->rev); 725 } 726 727 function sortByAge($a, $b) 728 { 729 return $b->date - $a->date; 730 } 731 732 function sortByName($a, $b) 733 { 734 return strcmp($a->author, $b->author); 735 } 736 737 /** 738 * Populate the object with information about the revisions logs and dates 739 * of the file. 740 * 741 * @return boolean|object PEAR_Error object on error, or true on success 742 */ 743 function getBrowseInfo() 744 { 745 /* Check that we are actually in the filesystem. */ 746 $file = $this->queryFullPath(); 747 if (!is_file($file)) { 748 return PEAR::raiseError('File Not Found: ' . $file); 749 } 750 751 /* Call the RCS rlog command to retrieve the file 752 * information. */ 753 $flag = $this->quicklog ? ' -r ' : ' '; 754 $q_file = VC_WINDOWS ? '"' . escapeshellcmd($file) . '"' : escapeshellarg($file); 755 756 $cmd = $this->rep->getPath('rlog') . $flag . $q_file; 757 exec($cmd, $return_array, $retval); 758 759 if ($retval) { 760 return PEAR::raiseError('Failed to spawn rlog to retrieve file log information for ' . $file); 761 } 762 763 $accum = array(); 764 $symrev = array(); 765 $revsym = array(); 766 $state = 'init'; 767 foreach ($return_array as $line) { 768 switch ($state) { 769 case 'init': 770 if (!strncmp('head: ', $line, 6)) { 771 $this->head = substr($line, 6); 772 } elseif (!strncmp('branch:', $line, 7)) { 773 $state = 'rev'; 774 } 775 break; 776 777 case 'rev': 778 if (!strncmp('----------', $line, 10)) { 779 $state = 'info'; 780 $this->symrev = $symrev; 781 $this->revsym = $revsym; 782 } elseif (preg_match("/^\s+([^:]+):\s+([\d\.]+)/", $line, $regs)) { 783 // Check to see if this is a branch 784 if (preg_match('/^(\d+(\.\d+)+)\.0\.(\d+)$/',$regs[2])) { 785 $branchRev = VC_Revision::toBranch($regs[2]); 786 if (!isset($this->branches[$branchRev])) { 787 $this->branches[$branchRev] = $regs[1]; 788 } 789 } else { 790 $symrev[$regs[1]] = $regs[2]; 791 if (empty($revsym[$regs[2]])) $revsym[$regs[2]]=array(); 792 $revsym[$regs[2]][] = $regs[1]; 793 } 794 } 795 break; 796 797 case 'info': 798 if (strncmp('==============================', $line, 30) && 799 strcmp('----------------------------', $line)) { 800 $accum[] = $line; 801 } elseif (count($accum)) { 802 // spawn a new VC_log object and add it to the logs hash 803 $log = new VC_Log_cvs($this); 804 $err = $log->processLog($accum); 805 // TODO: error checks - avsm 806 $this->logs[$log->queryRevision()] = $log; 807 $this->revs[] = $log->queryRevision(); 808 $accum = array(); 809 } 810 break; 811 } 812 } 813 814 return true; 815 } 816 817 /** 818 * Return the fully qualified filename of this object. 819 * 820 * @return Fully qualified filename of this object 821 */ 822 function queryFullPath() 823 { 824 return $this->dir . '/' . $this->name; 825 } 826 827 /** 828 * Return the name of this file relative to its sourceroot. 829 * 830 * @return string Pathname relative to the sourceroot. 831 */ 832 function queryModulePath() 833 { 834 return preg_replace('|^'. $this->rep->sourceroot() . '/?(.*),v$|', '\1', $this->queryFullPath()); 835 } 836 837 } 838 839 /** 840 * VC_cvs log class. 841 * 842 * @author Anil Madhavapeddy <anil@recoil.org> 843 * @package VC 844 */ 845 class VC_Log_cvs { 846 847 var $rep; 848 var $file; 849 var $tags; 850 var $rev; 851 var $date; 852 var $log; 853 var $author; 854 var $state; 855 var $lines; 856 var $branches; 857 858 /** 859 * 860 */ 861 function VC_Log_cvs($fl) 862 { 863 $this->file = $fl; 864 $this->branches = array(); 865 } 866 867 function processLog($raw) 868 { 869 /* Initialise a simple state machine to parse the output of rlog */ 870 $state = 'init'; 871 while (!empty($raw) && $state != 'done') { 872 switch ($state) { 873 /* Found filename, now looking for the revision number */ 874 case 'init': 875 $line = array_shift($raw); 876 if (preg_match("/revision (.+)$/", $line, $parts)) { 877 $this->rev = $parts[1]; 878 $state = 'date'; 879 } 880 break; 881 882 /* Found revision and filename, now looking for date */ 883 case 'date': 884 $line = array_shift($raw); 885 if (preg_match("|^date:\s+(\d+)[-/](\d+)[-/](\d+)\s+(\d+):(\d+):(\d+).*?;\s+author:\s+(.+);\s+state:\s+(\S+);(\s+lines:\s+([0-9\s+-]+))?|", $line, $parts)) { 886 $this->date = gmmktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]); 887 $this->author = $parts[7]; 888 $this->state = $parts[8]; 889 $this->lines = isset($parts[10]) ? $parts[10] : ''; 890 $state = 'branches'; 891 } 892 break; 893 894 /* Look for a branch point here - format is 'branches: x.y.z; a.b.c;' */ 895 case 'branches': 896 /* If we find a branch tag, process and pop it, 897 otherwise leave input stream untouched */ 898 if (!empty($raw) && preg_match("/^branches:\s+(.*)/", $raw[0], $br)) { 899 /* Get the list of branches from the string, and 900 * push valid revisions into the branches array */ 901 $brs = preg_split('/;\s*/', $br[1]); 902 foreach ($brs as $brpoint) { 903 if (VC_Revision::valid($brpoint)) { 904 $this->branches[] = $brpoint; 905 } 906 } 907 array_shift($raw); 908 909 } 910 911 $state = 'done'; 912 break; 913 } 914 } 915 916 /* Assume the rest of the lines are the log message */ 917 $this->log = implode("\n", $raw); 918 $this->tags = @$this->file->revsym[$this->rev]; 919 if (empty($this->tags)) { 920 $this->tags = array(); 921 } 922 } 923 924 function queryDate() 925 { 926 return $this->date; 927 } 928 929 function queryRevision() 930 { 931 return $this->rev; 932 } 933 934 function queryAuthor() 935 { 936 return $this->author; 937 } 938 939 function queryLog() 940 { 941 return $this->log; 942 } 943 944 function queryChangedLines() 945 { 946 return isset($this->lines) ? ($this->lines) : ''; 947 } 948 949 /** 950 * Given a branch revision number, this function remaps it 951 * accordingly, and performs a lookup on the file object to 952 * return the symbolic name(s) of that branch in the tree. 953 * 954 * @return array Hash of symbolic names => branch numbers 955 */ 956 function querySymbolicBranches() 957 { 958 $symBranches = array(); 959 foreach ($this->branches as $branch) { 960 $parts = explode('.', $branch); 961 $last = array_pop($parts); 962 $parts[] = '0'; 963 $parts[] = $last; 964 $rev = implode('.', $parts); 965 if (isset($this->file->branches[$branch])) { 966 $symBranches[$this->file->branches[$branch]] = $branch; 967 } 968 } 969 return $symBranches; 970 } 971 972 } 973 974 /** 975 * VC_cvs Patchset class. 976 * 977 * Copyright Anil Madhavapeddy, <anil@recoil.org> 978 * 979 * @author Anil Madhavapeddy <anil@recoil.org> 980 * @package VC 981 */ 982 class VC_Patchset_cvs extends VC_Patchset { 983 984 var $_dir; 985 var $_name; 986 987 /** 988 * Create a patchset object. 989 * 990 * @param string $file The filename to get patchsets for. 991 */ 992 function VC_Patchset_cvs($file) 993 { 994 $this->_name = basename($file); 995 $this->_dir = dirname($file); 996 } 997 998 function &getPatchsetObject($rep, $filename, $cache = null) 999 { 1000 /* The version of the cached data. Increment this whenever the 1001 * internal storage format changes, such that we must 1002 * invalidate prior cached data. */ 1003 $cacheVersion = 1; 1004 $cacheId = $rep->sourceroot() . '_n' . $filename . '_f_v' . $cacheVersion; 1005 1006 $ctime = time() - @filemtime($filename . ',v'); 1007 if ($cache && 1008 $cache->exists($cacheId, $ctime)) { 1009 $psOb = unserialize($cache->get($cacheId, $ctime)); 1010 $psOb->setRepository($rep); 1011 } else { 1012 $psOb = new VC_Patchset_cvs($filename); 1013 $psOb->setRepository($rep); 1014 if (is_a(($result = $psOb->getPatchsets()), 'PEAR_Error')) { 1015 return $result; 1016 } 1017 1018 if ($cache) { 1019 $cache->set($cacheId, serialize($psOb)); 1020 } 1021 } 1022 1023 return $psOb; 1024 } 1025 1026 /** 1027 * Populate the object with information about the patchsets that 1028 * this file is involved in. 1029 * 1030 * @return boolean|object PEAR_Error object on error, or true on success. 1031 */ 1032 function getPatchsets() 1033 { 1034 /* Check that we are actually in the filesystem. */ 1035 if (!is_file($this->getFullPath() . ',v')) { 1036 return PEAR::raiseError('File Not Found'); 1037 } 1038 1039 /* Call cvsps to retrieve all patchsets for this file. */ 1040 $q_root = $this->_rep->sourceroot(); 1041 $q_root = VC_WINDOWS ? '"' . escapeshellcmd($q_root) . '"' : escapeshellarg($q_root); 1042 1043 $cvsps_home = $this->_rep->getPath('cvsps_home'); 1044 $HOME = !empty($cvsps_home) ? 1045 'HOME=' . $cvsps_home . ' ' : 1046 ''; 1047 1048 $cmd = $HOME . $this->_rep->getPath('cvsps') . ' -u --cvs-direct --root ' . $q_root . ' -f ' . escapeshellarg($this->_name) . ' ' . escapeshellarg($this->_dir); 1049 exec($cmd, $return_array, $retval); 1050 if ($retval) { 1051 return PEAR::raiseError('Failed to spawn cvsps to retrieve patchset information'); 1052 } 1053 1054 $this->_patchsets = array(); 1055 $state = 'begin'; 1056 foreach ($return_array as $line) { 1057 $line = trim($line); 1058 if ($line == '---------------------') { 1059 $state = 'begin'; 1060 continue; 1061 } 1062 1063 switch ($state) { 1064 case 'begin': 1065 $id = str_replace('PatchSet ', '', $line); 1066 $this->_patchsets[$id] = array(); 1067 $state = 'info'; 1068 break; 1069 1070 case 'info': 1071 $info = explode(':', $line, 2); 1072 switch ($info[0]) { 1073 case 'Date': 1074 if (preg_match('|(\d{4})/(\d{2})/(\d{2}) (\d{2}):(\d{2}):(\d{2})|', $info[1], $date)) { 1075 $this->_patchsets[$id]['date'] = gmmktime($date[4], $date[5], $date[6], $date[2], $date[3], $date[1]); 1076 } 1077 break; 1078 1079 case 'Author': 1080 $this->_patchsets[$id]['author'] = trim($info[1]); 1081 break; 1082 1083 case 'Branch': 1084 if (trim($info[1]) != 'HEAD') { 1085 $this->_patchsets[$id]['branch'] = trim($info[1]); 1086 } 1087 break; 1088 1089 case 'Tag': 1090 if (trim($info[1]) != '(none)') { 1091 $this->_patchsets[$id]['tag'] = trim($info[1]); 1092 } 1093 break; 1094 1095 case 'Log': 1096 $state = 'log'; 1097 $this->_patchsets[$id]['log'] = ''; 1098 break; 1099 } 1100 break; 1101 1102 case 'log': 1103 if ($line == 'Members:') { 1104 $state = 'members'; 1105 $this->_patchsets[$id]['log'] = trim($this->_patchsets[$id]['log']); 1106 $this->_patchsets[$id]['members'] = array(); 1107 } else { 1108 $this->_patchsets[$id]['log'] .= $line . "\n"; 1109 } 1110 break; 1111 1112 case 'members': 1113 if (!empty($line)) { 1114 $parts = explode(':', $line); 1115 $revs = explode('->', $parts[1]); 1116 $this->_patchsets[$id]['members'][] = array('file' => $parts[0], 1117 'from' => $revs[0], 1118 'to' => $revs[1]); 1119 } 1120 break; 1121 } 1122 } 1123 1124 return true; 1125 } 1126 1127 /** 1128 * Return the fully qualified filename of this object. 1129 * 1130 * @return string Fully qualified filename of this object 1131 */ 1132 function getFullPath() 1133 { 1134 return $this->_dir . '/' . $this->_name; 1135 } 1136 1137 }
titre
Description
Corps
titre
Description
Corps
titre
Description
Corps
titre
Corps
| Généré le : Sun Feb 25 18:01:28 2007 | par Balluche grâce à PHPXref 0.7 |