From 590615e4a17c384c81b6cf843a782d902f5161ea Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Tue, 27 Jan 2009 16:48:45 -0700 Subject: [PATCH] More work on Horde_Vcs. Remove Horde_Vcs_Diff class. Fix some issues relating to paths. Clean up patchset generation. Clean up caching/directory browsing. Various phpdoc cleanup. Make this code more correctly OO - now down to only 1 main class (Horde_Vcs) and 4 subclasses (Horde_Vcs_File, Horde_Vcs_Directory, Horde_Vcs_Log, and Horde_Vcs_Patchset). --- chora/annotate.php | 2 +- chora/browse.php | 15 +- chora/co.php | 8 +- chora/diff.php | 6 +- chora/history.php | 15 +- chora/lib/Chora.php | 18 +- chora/lib/base.php | 7 - chora/patchsets.php | 4 +- chora/stats.php | 2 +- chora/templates/log/footer.inc | 2 +- framework/Vcs/lib/Horde/Vcs.php | 905 ++++++++++++++++++++---------------- framework/Vcs/lib/Horde/Vcs/Cvs.php | 385 +++++++-------- framework/Vcs/lib/Horde/Vcs/Git.php | 270 +++++------ framework/Vcs/lib/Horde/Vcs/Rcs.php | 59 ++- framework/Vcs/lib/Horde/Vcs/Svn.php | 228 ++++----- 15 files changed, 991 insertions(+), 935 deletions(-) diff --git a/chora/annotate.php b/chora/annotate.php index 6c254b71a..258c1660f 100644 --- a/chora/annotate.php +++ b/chora/annotate.php @@ -13,7 +13,7 @@ require_once dirname(__FILE__) . '/lib/base.php'; /* Spawn the file object. */ try { - $fl = $VC->getFileObject($where, array('cache' => $cache)); + $fl = $VC->getFileObject($where); } catch (Horde_Vcs_Exception $e) { Chora::fatal($e); } diff --git a/chora/browse.php b/chora/browse.php index 9f5abf6f3..6d0e56934 100644 --- a/chora/browse.php +++ b/chora/browse.php @@ -18,9 +18,8 @@ if (!$atdir && !$VC->isFile($fullname)) { if ($atdir) { try { - $dir = $VC->queryDir($where); $atticFlags = (bool)$acts['sa']; - $dir->browseDir($cache, true, $atticFlags); + $dir = $VC->getDirObject($where, array('quicklog' => true, 'showattic' => $atticFlags)); $dir->applySort($acts['sbt'], $acts['ord']); $dirList = &$dir->queryDirList(); $fileList = $dir->queryFileList($atticFlags); @@ -125,7 +124,7 @@ if ($atdir) { /* Showing a file. */ $onb = Util::getFormData('onb'); try { - $fl = $VC->getFileObject($where, array('cache' => $cache, 'branch' => $onb)); + $fl = $VC->getFileObject($where, array('branch' => $onb)); } catch (Horde_Vcs_Exception $e) { Chora::fatal($e); } @@ -133,18 +132,19 @@ try { $title = sprintf(_("Revisions for %s"), $where); $extraLink = Chora::getFileViews(); -$first = end($fl->logs); +$logs = $fl->queryLogs(); +$first = end($logs); $diffValueLeft = $first->queryRevision(); $diffValueRight = $fl->queryRevision(); $sel = ''; -foreach ($fl->symrev as $sm => $rv) { +foreach ($fl->querySymbolicRevisions() as $sm => $rv) { $sel .= ''; } $selAllBranches = ''; if ($VC->hasFeature('branches')) { - foreach (array_keys($fl->branches) as $sym) { + foreach (array_keys($fl->queryBranches()) as $sym) { $selAllBranches .= ''; } } @@ -159,7 +159,8 @@ require CHORA_TEMPLATES . '/headerbar.inc'; require CHORA_TEMPLATES . '/log/header.inc'; $i = 0; -foreach ($fl->logs as $lg) { +reset($logs); +while (list(,$lg) = each($logs)) { $rev = $lg->queryRevision(); $branch_info = $lg->queryBranch(); diff --git a/chora/co.php b/chora/co.php index 4b97a73e8..ab30a1378 100644 --- a/chora/co.php +++ b/chora/co.php @@ -22,7 +22,7 @@ $plain = Util::getFormData('p', 0); /* Create the VC_File object and populate it. */ try { - $file = $VC->getFileObject($where, array('cache' => $cache)); + $file = $VC->getFileObject($where); } catch (Horde_Vcs_Exception $e) { Chora::fatal($e); } @@ -46,7 +46,7 @@ if (!$VC->isValidRevision($r)) { /* Retrieve the actual checkout. */ try { - $checkOut = $VC->checkout($file, $r); + $checkOut = $VC->checkout($file->queryPath(), $r); } catch (Horde_Vcs_Exception $e) { Chora::fatal($e); } @@ -62,12 +62,12 @@ if (!$plain) { $pretty = Chora::pretty($mime_type, $checkOut); /* Get this revision's attributes in printable form. */ - $log = $file->logs[$r]; + $log = $file->queryLogs($r); $title = sprintf(_("%s Revision %s (%s ago)"), basename($fullname), $r, - Chora::readableTime($log->date, true)); + Chora::readableTime($log->queryDate(), true)); $extraLink = sprintf('%s | %s', Chora::url('annotate', $where, array('rev' => $r)), _("Annotate"), Chora::url('co', $where, array('r' => $r, 'p' => 1)), _("Download")); diff --git a/chora/diff.php b/chora/diff.php index 4f29a6b58..b0d6abcb4 100644 --- a/chora/diff.php +++ b/chora/diff.php @@ -13,7 +13,7 @@ require_once dirname(__FILE__) . '/lib/base.php'; /* Spawn the repository and file objects */ try { - $fl = $VC->getFileObject($where, array('cache' => $cache)); + $fl = $VC->getFileObject($where); } catch (Horde_Vcs_Exception $e) { Chora::fatal($e); } @@ -46,7 +46,7 @@ header('Cache-Control: max-age=604800'); * the end of the file - patch requires it. */ if ($type != 'colored') { header('Content-Type: text/plain'); - echo implode("\n", $VC->getDiff($fl, $r1, $r2, $type, $num, $ws)) . "\n"; + echo implode("\n", $VC->diff($fl, $r1, $r2, array('num' => $num, 'type' => $type, 'ws' => $ws))) . "\n"; exit; } @@ -93,7 +93,7 @@ if (substr($mime_type, 0, 6) == 'image/') { "\""'; } else { /* Retrieve the tree of changes. */ - $lns = $VC->getDiff($fl, $r1, $r2, 'unified', $num, $ws, true); + $lns = $VC->diff($fl, $r1, $r2, array('human' => true, 'num' => $num, 'ws' => $ws)); if (!$lns) { /* Is the diff empty? */ require CHORA_TEMPLATES . '/diff/hr/nochange.inc'; diff --git a/chora/history.php b/chora/history.php index 8e382d6c1..adf38eb20 100644 --- a/chora/history.php +++ b/chora/history.php @@ -11,6 +11,9 @@ require_once dirname(__FILE__) . '/lib/base.php'; +// TODO - This currently doesn't work. +Chora::fatal('History display is currently broken', '500 Internal Server Error'); + /* Exit if it's not supported. */ if (!$VC->hasFeature('branches')) { header('Location: ' . Chora::url('browse', $where)); @@ -19,7 +22,7 @@ if (!$VC->hasFeature('branches')) { /* Spawn the file object. */ try { - $fl = $VC->getFileObject($where, array('cache' => $cache)); + $fl = $VC->getFileObject($where); } catch (Horde_Vcs_Exception $e) { Chora::fatal($e); } @@ -58,7 +61,8 @@ function _populateGrid($row, $col) $brrev = $brkeys[$a]; $brcont = $branches[$brrev]; /* Check to see if current point matches a branch point. */ - if (!strcmp($rev, $VC->strip($brrev, 1))) { +// if (!strcmp($rev, $VC->strip($brrev, 1))) { + if (!strcmp($rev, $brrev)) { /* If it does, figure out how many rows we have to add. */ $numRows = sizeof($brcont); /* Check rows in columns to the right, until one is @@ -144,7 +148,8 @@ foreach ($grid as $row) { /* Otherwise, this cell has content; determine what it is. */ $rev = $row[$i]; - if ($VC->isValidRevision($rev) && ($VC->sizeof($rev) % 2)) { +// if ($VC->isValidRevision($rev) && ($VC->sizeof($rev) % 2)) { + if ($VC->isValidRevision($rev)) { /* This is a branch point, so put the info out. */ $bg = isset($branch_colors[$rev]) ? $branch_colors[$rev] : '#e9e9e9'; $symname = $fl->branches[$rev]; @@ -153,13 +158,13 @@ foreach ($grid as $row) { } elseif (preg_match('|^:|', $rev)) { /* This is a continuation cell, so render it with the * branch colour. */ - $bgbr = $VC->strip(preg_replace('|^\:|', '', $rev), 1); +// $bgbr = $VC->strip(preg_replace('|^\:|', '', $rev), 1); $bg = isset($branch_colors[$bgbr]) ? $branch_colors[$bgbr] : '#e9e9e9'; require CHORA_TEMPLATES . '/history/blank.inc'; } elseif ($VC->isValidRevision($rev)) { /* This cell contains a revision, so render it. */ - $bgbr = $VC->strip($rev, 1); +// $bgbr = $VC->strip($rev, 1); $bg = isset($branch_colors[$bgbr]) ? $branch_colors[$bgbr] : '#e9e9e9'; $log = $fl->logs[$rev]; $author = Chora::showAuthorName($log->queryAuthor()); diff --git a/chora/lib/Chora.php b/chora/lib/Chora.php index e99b8f503..591ff0acf 100644 --- a/chora/lib/Chora.php +++ b/chora/lib/Chora.php @@ -126,10 +126,18 @@ class Chora $sourcerootopts = $sourceroots[$acts['rt']]; $sourceroot = $acts['rt']; + // Cache. + if (empty($conf['caching'])) { + $cache = null; + } else { + $cache = &Horde_Cache::singleton($conf['cache']['driver'], Horde::getDriverConfig('cache', $conf['cache']['driver'])); + } + $conf['paths']['temp'] = Horde::getTempDir(); try { $GLOBALS['VC'] = Horde_Vcs::factory(String::ucfirst($sourcerootopts['type']), - array('sourceroot' => $sourcerootopts['location'], + array('cache' => $cache, + 'sourceroot' => $sourcerootopts['location'], 'paths' => $conf['paths'], 'username' => isset($sourcerootopts['username']) ? $sourcerootopts['username'] : '', 'password' => isset($sourcerootopts['password']) ? $sourcerootopts['password'] : '')); @@ -219,7 +227,7 @@ class Chora * @param string $message The verbose error message to be displayed. * @param string $responseCode The HTTP error number (and optional text), * for sending 404s or other codes if - * appropriate.. + * appropriate. */ static public function fatal($message, $responseCode = null) { @@ -562,10 +570,8 @@ class Chora $tags[] = ''. htmlspecialchars($symb) . ''; } - if ($lg->tags) { - foreach ($lg->tags as $tag) { - $tags[] = htmlspecialchars($tag); - } + foreach ($lg->queryTags() as $tag) { + $tags[] = htmlspecialchars($tag); } return $tags; diff --git a/chora/lib/base.php b/chora/lib/base.php index 8689263fd..4056d6104 100644 --- a/chora/lib/base.php +++ b/chora/lib/base.php @@ -44,13 +44,6 @@ define('CHORA_TEMPLATES', $registry->get('templates')); $notification = &Notification::singleton(); $notification->attach('status'); -// Cache. -if (empty($conf['caching'])) { - $cache = null; -} else { - $cache = &Horde_Cache::singleton($conf['cache']['driver'], Horde::getDriverConfig('cache', $conf['cache']['driver'])); -} - // Horde base libraries. require_once 'Horde/Text.php'; require_once 'Horde/Help.php'; diff --git a/chora/patchsets.php b/chora/patchsets.php index 8b72b009d..0feefa6ee 100644 --- a/chora/patchsets.php +++ b/chora/patchsets.php @@ -24,7 +24,7 @@ if (!$VC->isFile($fullname)) { } try { - $ps = $VC->getPatchsetObject($where, $cache); + $ps = $VC->getPatchsetObject($where); } catch (Horde_Vcs_Exception $e) { Chora::fatal($e); } @@ -40,7 +40,7 @@ require CHORA_TEMPLATES . '/menu.inc'; require CHORA_TEMPLATES . '/headerbar.inc'; require CHORA_TEMPLATES . '/patchsets/header.inc'; -$patchsets = $ps->_patchsets; +$patchsets = $ps->getPatchsets(); krsort($patchsets); foreach ($patchsets as $id => $patchset) { $commitDate = Chora::formatDate($patchset['date']); diff --git a/chora/stats.php b/chora/stats.php index f3840ec18..6e32b28fd 100644 --- a/chora/stats.php +++ b/chora/stats.php @@ -12,7 +12,7 @@ require_once dirname(__FILE__) . '/lib/base.php'; try { - $fl = $VC->getFileObject($where, array('cache' => $cache)); + $fl = $VC->getFileObject($where); } catch (Horde_Vcs_Exception $e) { Chora::fatal($e); } diff --git a/chora/templates/log/footer.inc b/chora/templates/log/footer.inc index a4d364269..2b92f6114 100644 --- a/chora/templates/log/footer.inc +++ b/chora/templates/log/footer.inc @@ -1,7 +1,7 @@ -logs) > 100 && !Util::getFormData('all')): ?> +revisionCount() > 100 && !Util::getFormData('all')): ?>
diff --git a/framework/Vcs/lib/Horde/Vcs.php b/framework/Vcs/lib/Horde/Vcs.php index 8811f1f5b..b2f6c8257 100644 --- a/framework/Vcs/lib/Horde/Vcs.php +++ b/framework/Vcs/lib/Horde/Vcs.php @@ -44,7 +44,7 @@ class Horde_Vcs * * @var array */ - protected $_users; + protected $_users = null; /** * The current driver. @@ -54,11 +54,11 @@ class Horde_Vcs protected $_driver; /** - * Cached objects. + * If caching is desired, a Horde_Cache object. * - * @var array + * @var Horde_Cache */ - protected $_cached = array(); + protected $_cache; /** * Does driver support deleted files? @@ -87,6 +87,13 @@ class Horde_Vcs protected $_cacheVersion = 3; /** + * The available diff types. + * + * @var array + */ + protected $_diffTypes = array('column', 'context', 'ed', 'unified'); + + /** * Attempts to return a concrete Horde_Vcs instance based on $driver. * * @param mixed $driver The type of concrete Horde_Vcs subclass to return. @@ -112,6 +119,7 @@ class Horde_Vcs */ public function __construct($params = array()) { + $this->_cache = empty($params['cache']) ? null : $params['cache']; $this->_sourceroot = $params['sourceroot']; $this->_paths = $params['paths']; @@ -190,268 +198,72 @@ class Horde_Vcs * Create a range of revisions between two revision numbers. * * @param Horde_Vcs_File $file The desired file. - * @param string $r1 The initial revision. - * @param string $r2 The ending revision. + * @param string $r1 The initial revision. + * @param string $r2 The ending revision. * * @return array The revision range, or empty if there is no straight * line path between the revisions. */ public function getRevisionRange($file, $r1, $r2) { - if (!isset($this->_cache['diff'])) { - $class = 'Horde_Vcs_Diff_' . $this->_driver; - $this->_cache['diff'] = new $class(); - } - return $this->_cache['diff']->getRevisionRange($this, $file, $r1, $r2); - } - - /** - * Returns the location of the specified binary. - * - * @param string $binary An external program name. - * - * @return boolean|string The location of the external program or false if - * it wasn't specified. - */ - public function getPath($binary) - { - if (isset($this->_paths[$binary])) { - return $this->_paths[$binary]; - } - - return false; - } - - /** - * Parse the users file, if present in the source root, and return - * a hash containing the requisite information, keyed on the - * username, and with the 'desc', 'name', and 'mail' values inside. - * - * @return boolean|array False if the file is not present, otherwise - * $this->_users populated with the data - */ - public function getUsers($usersfile) - { - /* Check that we haven't already parsed users. */ - if (isset($this->_users) && is_array($this->_users)) { - return $this->_users; - } - - if (!@is_file($usersfile) || !($fl = @fopen($usersfile, VC_WINDOWS ? 'rb' : 'r'))) { - return false; - } - - $this->_users = array(); - - /* Discard the first line, since it'll be the header info. */ - fgets($fl, 4096); - - /* Parse the rest of the lines into a hash, keyed on - * username. */ - while ($line = fgets($fl, 4096)) { - if (preg_match('/^\s*$/', $line)) { - continue; - } - if (!preg_match('/^(\w+)\s+(.+)\s+([\w\.\-\_]+@[\w\.\-\_]+)\s+(.*)$/', $line, $regs)) { - continue; - } - - $this->_users[$regs[1]]['name'] = trim($regs[2]); - $this->_users[$regs[1]]['mail'] = trim($regs[3]); - $this->_users[$regs[1]]['desc'] = trim($regs[4]); - } - - return $this->_users; - } - - public function queryDir($where) - { - $class = 'Horde_Vcs_Directory_' . $this->_driver; - return new $class($this, $where); - } - - public function getDiff($file, $rev1, $rev2, $type = 'unified', $num = 3, - $ws = true, $human = false) - { - if (!isset($this->_cache['diff'])) { - $class = 'Horde_Vcs_Diff_' . $this->_driver; - $this->_cache['diff'] = new $class(); - } - $diff = $this->_cache['diff']->get($this, $file, $rev1, $rev2, $type, $num, $ws); - return $human ? $this->_cache['diff']->humanReadable($diff) : $diff; - } - - public function availableDiffTypes() - { - if (!isset($this->_cache['diff'])) { - $class = 'Horde_Vcs_Diff_' . $this->_driver; - $this->_cache['diff'] = new $class(); - } - return $this->_cache['diff']->availableDiffTypes(); - } - - /** - * Function which returns a file pointing to the head of the requested - * revision of an SVN file. - * - * @param string $fullname Fully qualified pathname of the desired file - * to checkout - * @param string $rev Revision number to check out - * - * @return resource A stream pointer to the head of the checkout. - */ - public function checkout($fullname, $rev) - { - return null; - } - - /** - * $opts: - * 'cache' - (boolean) - * 'quicklog' - (boolean) - * 'branch' - (string) - */ - public function getFileObject($filename, $opts = array()) - { - $class = 'Horde_Vcs_File_' . $this->_driver; - - /* The version of the cached data. Increment this whenever the - * internal storage format changes, such that we must - * invalidate prior cached data. */ - sort($opts); - $cacheId = implode('|', array($class, $this->sourceroot(), $filename, serialize($opts), $this->_cacheVersion)); - - $ctime = time() - filemtime($filename); - if (!empty($opts['cache']) && - $opts['cache']->exists($cacheId, $ctime)) { - $fileOb = unserialize($opts['cache']->get($cacheId, $ctime)); - $fileOb->setRepository($this); - } else { - $fileOb = new $class($this, $filename, $opts); - $fileOb->applySort(self::SORT_AGE); - - if (!empty($opts['cache'])) { - $opts['cache']->set($cacheId, serialize($fileOb)); - } - } - - return $fileOb; - } - - public function getPatchsetObject($filename, $cache = null) - { - $class = 'Horde_Vcs_Patchset_' . $this->_driver; - $vc_patchset = new $class($this, $filename, $cache); - return $vc_patchset->getPatchsetObject(); - } - - /** - * TODO - */ - public function annotate($fileob, $rev) - { return array(); } /** - * Given a revision string, remove a given number of portions from - * it. For example, if we remove 2 portions of 1.2.3.4, we are - * left with 1.2. - * - * @param string $val Input revision string. - * @param integer $amount Number of portions to strip. - * - * @return string Stripped revision string. - */ - public function strip($val, $amount = 1) - { - return $val; - } - - /** - * The size of a revision number is the number of portions it has. - * For example, 1,2.3.4 is of size 4. - * - * @param string $val Revision number to determine size of - * - * @return integer Size of revision number - */ - public function sizeof($val) - { - return 1; - } - - /** - * Given two revisions, this figures out which one is greater than the - * the other. - * - * @param string $rev1 Revision string. - * @param string $rev2 Second revision string. - * - * @return integer 1 if the first is greater, -1 if the second if greater, - * and 0 if they are equal - */ - public function cmp($rev1, $rev2) - { - return strcasecmp($rev1, $rev2); - } - - /** - * Return the logical revision before this one. - * - * @param string $rev Revision string to decrement. - * - * @return string|boolean Revision string, or false if none could be - * determined. - */ - public function prev($rev) - { - return false; - } - - /** - * Returns an abbreviated form of the revision, for display. + * Obtain the differences between two revisions of a file. * - * @param string $rev The revision string. + * @param Horde_Vcs_File $file The desired file. + * @param string $rev1 Original revision number to compare from. + * @param string $rev2 New revision number to compare against. + * @param array $opts The following optional options: + *
+     * 'human' - (boolean) DEFAULT: false
+     * 'num' - (integer) DEFAULT: 3
+     * 'type' - (string) DEFAULT: 'unified'
+     * 'ws' - (boolean) DEFAULT: true
+     * 
* - * @return string The abbreviated string. + * @return string|boolean False on failure, or a string containing the + * diff on success. + * @throws Horde_Vcs_Exception */ - public function abbrev($rev) + public function diff($file, $rev1, $rev2, $opts = array()) { - return $rev; + $opts = array_merge(array( + 'num' => 3, + 'type' => 'unified', + 'ws' => true + ), $opts); + + $this->assertValidRevision($rev1); + $this->assertValidRevision($rev2); + + $diff = $this->_diff($file, $rev1, $rev2, $opts); + return empty($opts['human']) + ? $diff + : $this->_humanReadableDiff($diff); } -} - -/** - * @package Horde_Vcs - */ -abstract class Horde_Vcs_Diff -{ - /** - * The available diff types. - * - * @var array - */ - protected $_diffTypes = array('column', 'context', 'ed', 'unified'); - /** * Obtain the differences between two revisions of a file. * - * @param Horde_Vcs $rep A repository object. * @param Horde_Vcs_File $file The desired file. * @param string $rev1 Original revision number to compare from. * @param string $rev2 New revision number to compare against. - * @param string $type The type of diff (e.g. 'unified'). - * @param integer $num Number of lines to be used in context and - * unified diffs. - * @param boolean $ws Show whitespace in the diff? + * @param array $opts The following optional options: + *
+     * 'num' - (integer) DEFAULT: 3
+     * 'type' - (string) DEFAULT: 'unified'
+     * 'ws' - (boolean) DEFAULT: true
+     * 
* * @return string|boolean False on failure, or a string containing the * diff on success. */ - abstract public function get($rep, $file, $rev1, $rev2, $type = 'context', - $num = 3, $ws = true); + protected function _diff($file, $rev1, $rev2, $opts) + { + return false; + } /** * Obtain a tree containing information about the changes between @@ -462,7 +274,7 @@ abstract class Horde_Vcs_Diff * * @return array @TODO */ - public function humanReadable($raw) + protected function _humanReadableDiff($raw) { $ret = array(); @@ -566,89 +378,273 @@ abstract class Horde_Vcs_Diff } /** - * Create a range of revisions between two revision numbers. + * Return the list of available diff types. * - * @param Horde_Vcs $rep A repository object. - * @param Horde_Vcs_File $file The desired file. - * @param string $r1 The initial revision. - * @param string $r2 The ending revision. + * @return array The list of available diff types for use with get(). + */ + public function availableDiffTypes() + { + return $this->_diffTypes; + } + + /** + * Returns the location of the specified binary. * - * @return array The revision range, or empty if there is no straight - * line path between the revisions. + * @param string $binary An external program name. + * + * @return boolean|string The location of the external program or false + * if it wasn't specified. + */ + public function getPath($binary) + { + if (isset($this->_paths[$binary])) { + return $this->_paths[$binary]; + } + + return false; + } + + /** + * Parse the users file, if present in the sourceroot, and return + * a hash containing the requisite information, keyed on the + * username, and with the 'desc', 'name', and 'mail' values inside. + * + * @return boolean|array False if the file is not present, otherwise + * $this->_users populated with the data + */ + public function getUsers($usersfile) + { + /* Check that we haven't already parsed users. */ + if (!is_null($this->_users)) { + return $this->_users; + } + + if (!@is_file($usersfile) || + !($fl = @fopen($usersfile, VC_WINDOWS ? 'rb' : 'r'))) { + return false; + } + + $this->_users = array(); + + /* Discard the first line, since it'll be the header info. */ + fgets($fl, 4096); + + /* Parse the rest of the lines into a hash, keyed on + * username. */ + while ($line = fgets($fl, 4096)) { + if (preg_match('/^\s*$/', $line) || + !preg_match('/^(\w+)\s+(.+)\s+([\w\.\-\_]+@[\w\.\-\_]+)\s+(.*)$/', $line, $regs)) { + continue; + } + + $this->_users[$regs[1]] = array( + 'name' => trim($regs[2]), + 'mail' => trim($regs[3]), + 'desc' => trim($regs[4]) + ); + } + + return $this->_users; + } + + /** + * TODO + * + * $opts: + * 'quicklog' - (boolean) + * 'showAttic' - (boolean) + */ + public function getDirObject($where, $opts = array()) + { + $class = 'Horde_Vcs_Directory_' . $this->_driver; + return new $class($this, $where, $opts); + } + + /** + * Function which returns a file pointing to the head of the requested + * revision of a file. + * + * @param string $fullname Fully qualified pathname of the desired file + * to checkout. + * @param string $rev Revision number to check out. + * + * @return resource A stream pointer to the head of the checkout. + */ + public function checkout($fullname, $rev) + { + return null; + } + + /** + * TODO + * + * $opts: + * 'quicklog' - (boolean) + * 'branch' - (string) */ - public function getRevisionRange($rep, $file, $r1, $r2) + public function getFileObject($filename, $opts = array()) { - if ($rev->cmp($r1, $r2) == 1) { - $curr = $rev->prev($r1); - $stop = $rev->prev($r2); - $flip = true; - } else { - $curr = $r2; - $stop = $r1; - $flip = false; + $class = 'Horde_Vcs_File_' . $this->_driver; + + sort($opts); + $cacheId = implode('|', array($class, $this->sourceroot(), $filename, serialize($opts), $this->_cacheVersion)); + + if (!empty($this->_cache)) { + // TODO: Can't use filemtime() - Git bare repos contain no files + $ctime = time() - filemtime($filename); + if ($this->_cache->exists($cacheId, $ctime)) { + $ob = unserialize($this->_cache->get($cacheId, $ctime)); + $ob->setRepository($this); + return $ob; + } + } + + $ob = new $class($this, $filename, $opts); + $ob->applySort(self::SORT_AGE); + + if (!empty($this->_cache)) { + $this->_cache->set($cacheId, serialize($ob)); } - $ret_array = array(); + return $ob; + } + + /** + * + */ + public function getLogObject($fl, $rev) + { + $class = 'Horde_Vcs_Log_' . $this->_driver; + + if (!is_null($rev) && !empty($this->_cache)) { + $cacheId = implode('|', array($class, $this->sourceroot(), $fl->queryPath(), $rev, $this->_cacheVersion)); - do { - $ret_array[] = $curr; - $curr = $rev->prev($curr); - if ($curr == $stop) { - return ($flip) ? array_reverse($ret_array) : $ret_array; + // Individual revisions can be cached forever + if ($this->_cache->exists($cacheId, 0)) { + $ob = unserialize($this->_cache->get($cacheId, 0)); + $ob->setRepository($this); + return $ob; } - } while ($rev->cmp($curr, $stop) != -1); + } + + $ob = new $class($this, $fl, $rev); + + if (!is_null($rev) && !empty($this->_cache)) { + $this->_cache->set($cacheId, serialize($ob)); + } + + return $ob; + } + + /** + * TODO + */ + public function getPatchsetObject($filename) + { + $class = 'Horde_Vcs_Patchset_' . $this->_driver; + + $cacheId = implode('|', array($class, $this->sourceroot(), $filename, $this->_cacheVersion)); + + if (!empty($this->_cache)) { + // TODO: Can't use filemtime() - Git bare repos contain no files + $ctime = time() - filemtime($filename); + if ($this->_cache->exists($cacheId, $ctime)) { + return unserialize($this->_cache->get($cacheId, $ctime)); + } + } + + $ob = new $class($this, $filename); + + if (!empty($this->_cache)) { + $this->_cache->set($cacheId, serialize($ob)); + } + return $ob; + } + + /** + * TODO + */ + public function annotate($fileob, $rev) + { return array(); } /** - * Return the list of available diff types. + * Returns an abbreviated form of the revision, for display. * - * @return array The list of available diff types for use with get(). + * @param string $rev The revision string. + * + * @return string The abbreviated string. */ - public function availableDiffTypes() + public function abbrev($rev) { - return $this->_diffTypes; + return $rev; } + } /** + * Horde_Vcs_Cvs directory class. + * * @package Horde_Vcs */ abstract class Horde_Vcs_Directory { + /** + * @var Horde_Vcs + */ protected $_rep; + + /** + * @var string + */ protected $_dirName; - protected $_files; - protected $_atticFiles; - protected $_mergedFiles; - protected $_dirs; - protected $_parent; + + /** + * @var array + */ + protected $_files = array(); + + /** + * @var array + */ + protected $_atticFiles = array(); + + /** + * @var array + */ + protected $_mergedFiles = array(); + + /** + * @var string + */ + protected $_dirs = array(); + + /** + * @var string + */ protected $_moduleName; /** * Create a Directory object to store information about the files in a - * single directory in the repository + * single directory in the repository. * - * @param Horde_Vcs $rp The Repository object this directory - * is part of. - * @param string $dn Path to the directory. - * @param Horde_Vcs_Directory $pn The parent Directory object to this one. + * @param Horde_Vcs $rep The Repository object this directory is part of. + * @param string $dn Path to the directory. + * @param array $opts TODO */ - public function __construct($rep, $dn, $pn = '') + public function __construct($rep, $dn, $opts = array()) { $this->_rep = $rep; - $this->_parent = $pn; $this->_moduleName = $dn; $this->_dirName = '/' . $dn; - $this->_dirs = $this->_files = array(); } /** - * Return fully qualified pathname to this directory with no - * trailing /. + * Return fully qualified pathname to this directory with no trailing /. * - * @return Pathname of this directory + * @return string Pathname of this directory. */ public function queryDir() { @@ -674,19 +670,13 @@ abstract class Horde_Vcs_Directory } /** - * TODO - */ - abstract public function browseDir($cache = null, $quicklog = true, - $showattic = false); - - /** * Sort the contents of the directory in a given fashion and * order. * - * @param integer $how Of the form SORT_* where * can be: + * @param integer $how Of the form Horde_Vcs::SORT_[*] where * can be: * NONE, NAME, AGE, REV for sorting by name, age or * revision. - * @param integer $dir Of the form SORT_* where * can be: + * @param integer $dir Of the form Horde_Vcs::SORT_[*] where * can be: * ASCENDING, DESCENDING for the order of the sort. */ public function applySort($how = Horde_Vcs::SORT_NONE, @@ -721,19 +711,19 @@ abstract class Horde_Vcs_Directory { switch ($how) { case Horde_Vcs::SORT_AGE: - usort($fileList, array($this, 'fileAgeSort')); + usort($fileList, array($this, '_fileAgeSort')); break; case Horde_Vcs::SORT_NAME: - usort($fileList, array($this, 'fileNameSort')); + usort($fileList, array($this, '_fileNameSort')); break; case Horde_Vcs::SORT_AUTHOR: - usort($fileList, array($this, 'fileAuthorSort')); + usort($fileList, array($this, '_fileAuthorSort')); break; case Horde_Vcs::SORT_REV: - usort($fileList, array($this, 'fileRevSort')); + usort($fileList, array($this, '_fileRevSort')); break; case Horde_Vcs::SORT_NONE: @@ -741,10 +731,11 @@ abstract class Horde_Vcs_Directory break; } } + /** * Sort function for ascending age. */ - public function fileAgeSort($a, $b) + public function _fileAgeSort($a, $b) { $aa = $a->queryLastLog(); $bb = $b->queryLastLog(); @@ -756,7 +747,7 @@ abstract class Horde_Vcs_Directory /** * Sort function by author name. */ - public function fileAuthorSort($a, $b) + public function _fileAuthorSort($a, $b) { $aa = $a->queryLastLog(); $bb = $b->queryLastLog(); @@ -768,15 +759,15 @@ abstract class Horde_Vcs_Directory /** * Sort function for ascending filename. */ - public function fileNameSort($a, $b) + public function _fileNameSort($a, $b) { - return strcasecmp($a->name, $b->name); + return strcasecmp($a->queryName(), $b->queryName()); } /** * Sort function for ascending revision. */ - public function fileRevSort($a, $b) + public function _fileRevSort($a, $b) { return $this->_rep->cmp($a->queryHead(), $b->queryHead()); } @@ -784,27 +775,87 @@ abstract class Horde_Vcs_Directory } /** + * Horde_Vcs file class. + * * @package Horde_Vcs */ -class Horde_Vcs_File +abstract class Horde_Vcs_File { - public $rep; - public $dir; - public $name; - public $logs = array(); - public $revs = array(); - public $head; - public $quicklog; - public $symrev = array(); - public $revsym = array(); - public $branches = array(); + /** + * TODO + */ + protected $_dir; + + /** + * TODO + */ + protected $_name; + + /** + * TODO + */ + protected $_logs = array(); + + /** + * TODO + */ + protected $_revs = array(); + + /** + * TODO + */ + protected $_rep; + + /** + * TODO + */ + protected $_symrev = array(); + + /** + * TODO + */ + protected $_branches = array(); + + /** + * TODO + */ + protected $_quicklog; + + /** + * TODO + */ + protected $_branch; + + /** + * TODO + */ + protected $_head = null; + + /** + * Create a repository file object, and give it information about + * what its parent directory and repository objects are. + * + * @param TODO $rep TODO + * @param string $fl Full path to this file. + * @param array $opts TODO + */ + public function __construct($rep, $fl, $opts = array()) + { + $this->_rep = $rep; + $this->_name = basename($fl); + $this->_dir = dirname($fl); + $this->_quicklog = !empty($opts['quicklog']); + if (!empty($opts['branch'])) { + $this->_branch = $opts['branch']; + } + } /** * TODO */ public function setRepository($rep) { - $this->rep = $rep; + $this->_rep = $rep; } /** @@ -824,7 +875,7 @@ class Horde_Vcs_File */ function queryName() { - return $this->name; + return $this->_name; } /** @@ -834,20 +885,21 @@ class Horde_Vcs_File */ public function queryRepositoryName() { - return $this->name; + return $this->_name; } /** - * Return the last revision of the current file on the HEAD branch + * Return the last revision of the current file on the HEAD branch. * - * @return Last revision of the current file + * @return string Last revision of the current file. + * @throws Horde_Vcs_Exception */ public function queryRevision() { - if (!isset($this->revs[0])) { + if (!isset($this->_revs[0])) { throw new Horde_Vcs_Exception('No revisions'); } - return $this->revs[0]; + return $this->_revs[0]; } /** @@ -855,33 +907,34 @@ class Horde_Vcs_File */ public function queryPreviousRevision($rev) { - $key = array_search($rev, $this->revs); - return (($key !== false) && isset($this->revs[$key + 1])) - ? $this->revs[$key + 1] + $key = array_search($rev, $this->_revs); + return (($key !== false) && isset($this->_revs[$key + 1])) + ? $this->_revs[$key + 1] : false; } /** * Return the HEAD (most recent) revision number for this file. * - * @return HEAD revision number + * @return string HEAD revision string. */ public function queryHead() { - return $this->queryRevision(); + return is_null($this->_head) ? $this->queryRevision() : $this->_head; } /** * Return the last Horde_Vcs_Log object in the file. * - * @return Horde_Vcs_Log of the last entry in the file + * @return Horde_Vcs_Log Log object of the last entry in the file. + * @throws Horde_Vcs_Exception */ public function queryLastLog() { - if (!isset($this->revs[0]) || !isset($this->logs[$this->revs[0]])) { + if (!isset($this->_revs[0]) || !isset($this->_logs[$this->_revs[0]])) { throw new Horde_Vcs_Exception('No revisions'); } - return $this->logs[$this->revs[0]]; + return $this->_logs[$this->_revs[0]]; } /** @@ -908,7 +961,7 @@ class Horde_Vcs_File break; } - uasort($this->logs, array($this, 'sortBy' . $func)); + uasort($this->_logs, array($this, 'sortBy' . $func)); return true; } @@ -917,37 +970,80 @@ class Horde_Vcs_File */ public function sortByRevision($a, $b) { - return $this->rep->cmp($b->rev, $a->rev); + return $this->_rep->cmp($b->queryRevision(), $a->queryRevision()); } public function sortByAge($a, $b) { - return $b->date - $a->date; + return $b->queryDate() - $a->queryDate(); } public function sortByName($a, $b) { - return strcmp($a->author, $b->author); + return strcmp($a->queryAuthor(), $b->queryAuthor()); } /** * Return the fully qualified filename of this object. * - * @return Fully qualified filename of this object + * @return string Fully qualified filename of this object. */ public function queryFullPath() { - return $this->rep->sourceroot() . '/' . $this->queryModulePath(); + return $this->_rep->sourceroot() . '/' . $this->queryModulePath(); } /** - * Return the name of this file relative to its sourceroot. + * Return the filename relative to its sourceroot. * * @return string Pathname relative to the sourceroot. */ public function queryModulePath() { - return $this->dir . '/' . $this->name; + return $this->_dir . '/' . $this->_name; + } + + /** + * Return the "base" filename (i.e. the filename needed by the various + * command line utilities). + * + * @return string A filename. + */ + public function queryPath() + { + return $this->queryFullPath(); + } + + /** + * TODO + */ + public function queryBranches() + { + return $this->_branches; + } + + /** + * TODO + */ + public function queryLogs($rev = null) + { + return is_null($rev) ? $this->_logs : (isset($this->_logs[$rev]) ? $this->_logs[$rev] : null); + } + + /** + * TODO + */ + public function revisionCount() + { + return count($this->_revs); + } + + /** + * TODO + */ + public function querySymbolicRevisions() + { + return $this->_symrev; } } @@ -957,56 +1053,91 @@ class Horde_Vcs_File * * @package Horde_Vcs */ -class Horde_Vcs_Log +abstract class Horde_Vcs_Log { - public $rep; - public $file; - public $tags; - public $rev; - public $date; - public $log; - public $author; - public $state; - public $lines; - public $branches = array(); + protected $_rep; + protected $_file; + protected $_rev; + protected $_author; + protected $_tags = array(); + protected $_date; + protected $_log; + protected $_state; + protected $_lines = ''; + protected $_branches = array(); /** * Constructor. */ - public function __construct($fl) + public function __construct($rep, $fl, $rev) { - $this->file = $fl; - $this->rep = $fl->rep; + $this->_rep = $rep; + $this->_file = $fl; + $this->_rev = $rev; } + /** + * TODO + */ + public function setRepository($rep) + { + $this->_rep = $rep; + } + + /** + * TODO + */ public function queryDate() { - return $this->date; + return $this->_date; } + /** + * TODO + */ public function queryRevision() { - return $this->rev; + return $this->_rev; } + /** + * TODO + */ public function queryAuthor() { - return $this->author; + return $this->_author; } + /** + * TODO + */ public function queryLog() { - return $this->log; + return $this->_log; } + /** + * TODO + */ public function queryBranch() { return array(); } + /** + * TODO + */ public function queryChangedLines() { - return isset($this->lines) ? $this->lines : ''; + return $this->_lines; + } + + /** + * TODO + */ + public function queryTags() + { + return $this->_tags; } /** @@ -1019,10 +1150,11 @@ class Horde_Vcs_Log public function querySymbolicBranches() { $symBranches = array(); + $branches = $this->_file->queryBranches(); - foreach ($this->branches as $branch) { - $key = array_search($branch, $this->file->branches); - if ($key !== false) { + foreach ($this->_branches as $branch) { + $key = array_search($branch, $branches); + if (($key = array_search($branch, $branches)) !== false) { $symBranches[$key] = $branch; } } @@ -1039,58 +1171,25 @@ class Horde_Vcs_Log */ abstract class Horde_Vcs_Patchset { - protected $_rep; - public $_patchsets = array(); - protected $_file; - protected $_cache; - protected $_ctime = 3600; + /** + * @var array + */ + protected $_patchsets = array(); /** - * Create a patchset object. + * Constructor * - * @param string $file The filename to get patchsets for. + * @param Horde_Vcs $rep A Horde_Vcs repository object. + * @param string $file The filename to create patchsets for. */ - public function __construct($rep, $file, $cache = null) - { - $this->_rep = $rep; - $this->_file = $file; - $this->_cache = $cache; - } + abstract public function __construct($rep, $file); - public function &getPatchsetObject() + /** + * TODO + */ + public function getPatchsets() { - /* The version of the cached data. Increment this whenever the - * internal storage format changes, such that we must - * invalidate prior cached data. */ - if ($this->_cache) { - $cacheVersion = 1; - $cacheId = $this->_rep->sourceroot() . '_n' . $this->_file . '_f_v' . $cacheVersion; - } - - if ($this->_cache && - $this->_cache->exists($cacheId, $this->_ctime)) { - $psOb = unserialize($this->_cache->get($cacheId, $this->_ctime)); - $psOb->setRepository($this->_rep); - } else { - $class_name = get_class($this); - $psOb = new $class_name($this->_rep, $this->_file, $this->_cache); - $psOb->setRepository($this->_rep); - if (is_a(($result = $psOb->getPatchsets()), 'PEAR_Error')) { - return $result; - } - - if ($this->_cache) { - $this->_cache->set($cacheId, serialize($psOb)); - } - } - - return $psOb; + return $this->_patchsets; } - abstract public function getPatchsets(); - - public function setRepository($rep) - { - $this->_rep = $rep; - } } diff --git a/framework/Vcs/lib/Horde/Vcs/Cvs.php b/framework/Vcs/lib/Horde/Vcs/Cvs.php index c29fec473..c2df4db7c 100644 --- a/framework/Vcs/lib/Horde/Vcs/Cvs.php +++ b/framework/Vcs/lib/Horde/Vcs/Cvs.php @@ -6,7 +6,7 @@ *
  * 'sourceroot': The source root for this repository
  * 'paths': Hash with the locations of all necessary binaries: 'rcsdiff',
- *          'rlog', 'cvsps', 'cvsps_home' and the temp path: 'temp'
+ *          'rlog', 'cvsps', 'cvsps_home', and 'temp' (the temp path).
  * 
* * Copyright 2000-2009 The Horde Project (http://www.horde.org/) @@ -15,11 +15,19 @@ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Anil Madhavapeddy + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Cvs extends Horde_Vcs_Rcs { /** + * Does driver support patchsets? + * + * @var boolean + */ + protected $_patchsets = true; + + /** * Does driver support deleted files? * * @var boolean @@ -53,6 +61,66 @@ class Horde_Vcs_Cvs extends Horde_Vcs_Rcs } /** + * Obtain the differences between two revisions of a file. + * + * @param Horde_Vcs_File $file The desired file. + * @param string $rev1 Original revision number to compare from. + * @param string $rev2 New revision number to compare against. + * @param array $opts The following optional options: + *
+     * 'num' - (integer) DEFAULT: 3
+     * 'type' - (string) DEFAULT: 'unified'
+     * 'ws' - (boolean) DEFAULT: true
+     * 
+ * + * @return string|boolean False on failure, or a string containing the + * diff on success. + */ + protected function _diff($file, $rev1, $rev2, $opts) + { + $fullName = $file->queryFullPath(); + $diff = array(); + $flags = '-kk '; + + if (!$opts['ws']) { + $flags .= ' -bB '; + } + + switch ($opts['type']) { + case 'context': + $flags .= '-p --context=' . (int)$opts['num']; + break; + + case 'unified': + $flags .= '-p --unified=' . (int)$opts['num']; + break; + + case 'column': + $flags .= '--side-by-side --width=120'; + break; + + case 'ed': + $flags .= '-e'; + break; + } + + // Windows versions of cvs always return $where with forwards slashes. + if (VC_WINDOWS) { + $fullName = str_replace(DIRECTORY_SEPARATOR, '/', $fullName); + } + + // TODO: add options for $hr options - however these may not be + // compatible with some diffs. + $command = $this->getPath('rcsdiff') . " $flags -r" . escapeshellarg($rev1) . ' -r' . escapeshellarg($rev2) . ' ' . escapeshellarg($fullName) . ' 2>&1'; + if (VC_WINDOWS) { + $command .= ' < "' . __FILE__ . '"'; + } + + exec($command, $diff, $retval); + return ($retval > 0) ? $diff : array(); + } + + /** * TODO */ public function getFileObject($filename, $opts = array()) @@ -74,14 +142,6 @@ class Horde_Vcs_Cvs extends Horde_Vcs_Rcs /** * TODO */ - public function getPatchsetObject($filename, $cache = null) - { - return parent::getPatchsetObject($this->sourceroot() . '/' . $filename, $cache); - } - - /** - * TODO - */ public function annotate($fileob, $rev) { $this->assertValidRevision($rev); @@ -165,7 +225,7 @@ class Horde_Vcs_Cvs extends Horde_Vcs_Rcs */ public function checkout($fullname, $rev) { - $rep->assertValidRevision($rev); + $this->assertValidRevision($rev); if (!($RCS = popen($this->getPath('co') . ' ' . escapeshellarg('-p' . $rev) . ' ' . escapeshellarg($fullname) . " 2>&1", VC_WINDOWS ? 'rb' : 'r'))) { throw new Horde_Vcs_Exception('Couldn\'t perform checkout of the requested file'); @@ -194,120 +254,30 @@ class Horde_Vcs_Cvs extends Horde_Vcs_Rcs } /** - * Horde_Vcs_Cvs diff class. - * - * Copyright Anil Madhavapeddy, - * - * @author Anil Madhavapeddy - * @package Horde_Vcs - */ -class Horde_Vcs_Diff_Cvs extends Horde_Vcs_Diff -{ - /** - * Obtain the differences between two revisions of a file. - * - * @param Horde_Vcs $rep A repository object. - * @param Horde_Vcs_File $file The desired file. - * @param string $rev1 Original revision number to compare from. - * @param string $rev2 New revision number to compare against. - * @param string $type The type of diff (e.g. 'unified'). - * @param integer $num Number of lines to be used in context and - * unified diffs. - * @param boolean $ws Show whitespace in the diff? - * - * @return string|boolean False on failure, or a string containing the - * diff on success. - */ - public function get($rep, $file, $rev1, $rev2, $type = 'context', - $num = 3, $ws = true) - { - /* Check that the revision numbers are valid. */ - $rev1 = $rep->isValidRevision($rev1) ? $rev1 : '1.1'; - $rev2 = $rep->isValidRevision($rev1) ? $rev2 : '1.1'; - - $fullName = $file->queryFullPath(); - $diff = array(); - $options = '-kk '; - if (!$ws) { - $opts = ' -bB '; - $options .= $opts; - } else { - $opts = ''; - } - - switch ($type) { - case 'context': - $options = $opts . '-p --context=' . (int)$num; - break; - - case 'unified': - $options = $opts . '-p --unified=' . (int)$num; - break; - - case 'column': - $options = $opts . '--side-by-side --width=120'; - break; - - case 'ed': - $options = $opts . '-e'; - break; - } - - // Windows versions of cvs always return $where with forwards slashes. - if (VC_WINDOWS) { - $fullName = str_replace(DIRECTORY_SEPARATOR, '/', $fullName); - } - - // TODO: add options for $hr options - however these may not be - // compatible with some diffs. - $command = $rep->getPath('rcsdiff') . " $options -r$rev1 -r$rev2 \"" . escapeshellcmd($fullName) . '" 2>&1'; - if (VC_WINDOWS) { - $command .= ' < "' . __FILE__ . '"'; - } - - exec($command, $diff, $retval); - return ($retval > 0) ? $diff : array(); - } - -} - -/** * Horde_Vcs_Cvs directory class. * - * Copyright Anil Madhavapeddy, - * * @author Anil Madhavapeddy + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Directory_Cvs extends Horde_Vcs_Directory { /** - * Creates a CVS Directory object to store information - * about the files in a single directory in the repository. + * Create a Directory object to store information about the files in a + * single directory in the repository * - * @param Horde_Vcs $rep A repository object - * @param string $dn Path to the directory. - * @param Horde_Vcs_Directory $pn The parent Directory object to this one. + * @param Horde_Vcs $rep The Repository object this directory is part of. + * @param string $dn Path to the directory. + * @param array $opts TODO */ - public function __construct($rep, $dn, $pn = '') + public function __construct($rep, $dn, $opts = array()) { - parent::__construct($rep, $dn, $pn); - $this->_dirName = $rep->sourceroot() . "/$dn"; - } + parent::__construct($rep, $dn, $opts); + $this->_dirName = $rep->sourceroot() . '/' . $dn; - /** - * Tell the object to open and browse its current directory, and - * retrieve a list of all the objects in there. It then populates - * the file/directory stack and makes it available for retrieval. - * - * @return boolean True on success. - */ - public function browseDir($cache = null, $quicklog = true, - $showattic = false) - { /* Make sure we are trying to list a directory */ if (!@is_dir($this->_dirName)) { - throw new Horde_Vcs_Exception('Unable to find directory ' . $this->_dirName); + throw new Horde_Vcs_Exception('Unable to find directory: ' . $this->_dirName); } /* Open the directory for reading its contents */ @@ -330,7 +300,7 @@ class Horde_Vcs_Directory_Cvs extends Horde_Vcs_Directory } } elseif (@is_file($path) && (substr($name, -2) == ',v')) { /* Spawn a new file object to represent this file. */ - $this->_files[] = $this->_rep->getFileObject(substr($path, strlen($this->_rep->sourceroot()), -2), array('cache' => $cache, 'quicklog' => $quicklog)); + $this->_files[] = $rep->getFileObject(substr($path, strlen($rep->sourceroot()), -2), array('quicklog' => !empty($opts['quicklog']))); } } @@ -338,9 +308,9 @@ class Horde_Vcs_Directory_Cvs extends Horde_Vcs_Directory closedir($DIR); /* If we want to merge the attic, add it in here. */ - if ($showattic) { + if (!empty($opts['showattic'])) { try { - $atticDir = new Horde_Vcs_Directory_Cvs($this->_rep, $this->_moduleName . '/Attic', $this); + $atticDir = new Horde_Vcs_Directory_Cvs($rep, $this->_moduleName . '/Attic', $opts, $this); $this->_atticFiles = $atticDir->queryFileList(); $this->_mergedFiles = array_merge($this->_files, $this->_atticFiles); } catch (Horde_Vcs_Exception $e) {} @@ -354,16 +324,21 @@ class Horde_Vcs_Directory_Cvs extends Horde_Vcs_Directory /** * Horde_Vcs_Cvs file class. * - * Copyright Anil Madhavapeddy, - * * @author Anil Madhavapeddy + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_File_Cvs extends Horde_Vcs_File { - /* @TODO */ - public $filename; - protected $_branch = ''; + /** + * TODO + */ + public $accum; + + /** + * TODO + */ + protected $_revsym = array(); /** * Create a repository file object, and give it information about @@ -375,14 +350,7 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File */ public function __construct($rep, $fl, $opts = array()) { - $this->rep = $rep; - $this->name = basename($fl); - $this->dir = dirname($fl); - $this->filename = $fl; - $this->quicklog = !empty($opts['quicklog']); - if (!empty($opts['branch'])) { - $this->_branch = $opts['branch']; - } + parent::__construct($rep, $fl, $opts); /* Check that we are actually in the filesystem. */ $file = $this->queryFullPath(); @@ -390,12 +358,9 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File throw new Horde_Vcs_Exception('File Not Found: ' . $file); } - /* Call the RCS rlog command to retrieve the file - * information. */ - $flag = $this->quicklog ? ' -r ' : ' '; - - $cmd = $this->rep->getPath('rlog') . $flag . escapeshellarg($file); - exec($cmd, $return_array, $retval); + $ret_array = array(); + $cmd = $rep->getPath('rlog') . ($this->_quicklog ? ' -r' : '') . ' ' . escapeshellarg($file); + exec($cmd, $ret_array, $retval); if ($retval) { throw new Horde_Vcs_Exception('Failed to spawn rlog to retrieve file log information for ' . $file); @@ -404,12 +369,11 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File $accum = $revsym = $symrev = array(); $state = 'init'; - foreach ($return_array as $line) { + foreach ($ret_array as $line) { switch ($state) { case 'init': if (!strncmp('head: ', $line, 6)) { - $this->head = substr($line, 6); - $this->branches['HEAD'] = $this->head; + $this->_head = $this->_branches['HEAD'] = substr($line, 6); } elseif (!strncmp('branch:', $line, 7)) { $state = 'rev'; } @@ -418,13 +382,13 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File case 'rev': if (!strncmp('----------', $line, 10)) { $state = 'info'; - $this->symrev = $symrev; - $this->revsym = $revsym; + $this->_symrev = $symrev; + $this->_revsym = $revsym; } elseif (preg_match("/^\s+([^:]+):\s+([\d\.]+)/", $line, $regs)) { // Check to see if this is a branch if (preg_match('/^(\d+(\.\d+)+)\.0\.(\d+)$/', $regs[2])) { $branchRev = $this->toBranch($regs[2]); - $this->branches[$regs[1]] = $branchRev; + $this->_branches[$regs[1]] = $branchRev; } else { $symrev[$regs[1]] = $regs[2]; if (empty($revsym[$regs[2]])) { @@ -440,21 +404,19 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File strcmp('----------------------------', $line)) { $accum[] = $line; } elseif (count($accum)) { - // Spawn a new Horde_Vcs_Log object and add it to the logs - // hash. - $log = new Horde_Vcs_Log_Cvs($this); - $err = $log->processLog($accum); + $this->accum = $accum; + $log = $rep->getLogObject($this, null); $rev = $log->queryRevision(); $branch = $log->queryBranch(); if (empty($this->_branch) || in_array($this->_branch, $log->queryBranch()) || - (($this->rep->cmp($rev, $this->branches[$this->_branch]) === -1) && + (($rep->cmp($rev, $this->_branches[$this->_branch]) === -1) && (empty($branch) || in_array('HEAD', $branch) || - (strpos($this->branches[$this->_branch], $rev) === 0)))) { + (strpos($this->_branches[$this->_branch], $rev) === 0)))) { $log->setBranch($branch); - $this->logs[$rev] = $log; - $this->revs[] = $rev; + $this->_logs[$rev] = $log; + $this->_revs[] = $rev; } $accum = array(); } @@ -471,7 +433,7 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File */ public function isDeleted() { - return (substr($this->dir, -5) == 'Attic'); + return (substr($this->_dir, -5) == 'Attic'); } /** @@ -482,17 +444,7 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File */ public function queryName() { - return preg_replace('/,v$/', '', $this->name); - } - - /** - * Return the HEAD (most recent) revision number for this file. - * - * @return string HEAD revision number - */ - public function queryHead() - { - return $this->head; + return preg_replace('/,v$/', '', $this->_name); } /** @@ -502,7 +454,7 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File */ public function queryFullPath() { - return $this->dir . '/' . $this->name; + return parent::queryModulePath(); } /** @@ -512,7 +464,7 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File */ public function queryModulePath() { - return preg_replace('|^'. $this->rep->sourceroot() . '/?(.*),v$|', '\1', $this->queryFullPath()); + return preg_replace('|^'. $this->_rep->sourceroot() . '/?(.*),v$|', '\1', $this->queryFullPath()); } /** @@ -526,7 +478,7 @@ class Horde_Vcs_File_Cvs extends Horde_Vcs_File public function toBranch($rev) { /* Check if we have a valid revision number */ - if (!$this->rep->isValidRevision($rev) || + if (!$this->_rep->isValidRevision($rev) || (($end = strrpos($rev, '.')) === false)) { return false; } @@ -587,21 +539,43 @@ foreach ($branches as $col => $rows) { */ } + /** + * TODO + */ + public function queryRevsym($rev) + { + return isset($this->_revsym[$rev]) + ? $this->_revsym[$rev] + : array(); + } + } /** * Horde_Vcs_cvs Log class. * * @author Anil Madhavapeddy + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Log_Cvs extends Horde_Vcs_Log { - /* Cached branch info. */ + /** + * Cached branch info. + * + * @var string + */ protected $_branch; - public function processLog($raw) + /** + * Constructor. + */ + public function __construct($rep, $fl, $rev) { + parent::__construct($rep, $fl, $rev); + + $raw = $fl->accum; + /* Initialise a simple state machine to parse the output of rlog */ $state = 'init'; while (!empty($raw) && $state != 'done') { @@ -610,7 +584,7 @@ class Horde_Vcs_Log_Cvs extends Horde_Vcs_Log case 'init': $line = array_shift($raw); if (preg_match("/revision (.+)$/", $line, $parts)) { - $this->rev = $parts[1]; + $this->_rev = $parts[1]; $state = 'date'; } break; @@ -619,10 +593,10 @@ class Horde_Vcs_Log_Cvs extends Horde_Vcs_Log case 'date': $line = array_shift($raw); 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)) { - $this->date = gmmktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]); - $this->author = $parts[7]; - $this->state = $parts[8]; - $this->lines = isset($parts[10]) ? $parts[10] : ''; + $this->_date = gmmktime($parts[4], $parts[5], $parts[6], $parts[2], $parts[3], $parts[1]); + $this->_author = $parts[7]; + $this->_state = $parts[8]; + $this->_lines = isset($parts[10]) ? $parts[10] : ''; $state = 'branches'; } break; @@ -638,8 +612,8 @@ class Horde_Vcs_Log_Cvs extends Horde_Vcs_Log * push valid revisions into the branches array */ $brs = preg_split('/;\s*/', $br[1]); foreach ($brs as $brpoint) { - if ($this->rep->isValidRevision($brpoint)) { - $this->branches[] = $brpoint; + if ($rep->isValidRevision($brpoint)) { + $this->_branches[] = $brpoint; } } array_shift($raw); @@ -651,26 +625,31 @@ class Horde_Vcs_Log_Cvs extends Horde_Vcs_Log } /* Assume the rest of the lines are the log message */ - $this->log = implode("\n", $raw); - $this->tags = isset($this->file->revsym[$this->rev]) ? - $this->file->revsym[$this->rev] : - array(); + $this->_log = implode("\n", $raw); + $this->_tags = $fl->queryRevsym($this->_rev); } + /** + * TODO + */ public function setBranch($branch) { $this->_branch = $branch; } + /** + * TODO + */ public function queryBranch() { if (!empty($this->_branch)) { return $this->_branch; } - $key = array_keys($this->file->branches, $this->rev); + $branches = $this->_file->queryBranches(); + $key = array_keys($branches, $this->_rev); return empty($key) - ? array_keys($this->file->branches, $this->rep->strip($this->rev, 1)) + ? array_keys($branches, $this->_rep->strip($this->_rev, 1)) : $key; } @@ -679,58 +658,44 @@ class Horde_Vcs_Log_Cvs extends Horde_Vcs_Log /** * Horde_Vcs_Cvs Patchset class. * - * Copyright Anil Madhavapeddy, - * * @author Anil Madhavapeddy + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Patchset_Cvs extends Horde_Vcs_Patchset { - protected $_dir; - protected $_name; - /** - * Create a patchset object. + * Constructor * - * @param string $file The filename to get patchsets for. + * @param Horde_Vcs $rep A Horde_Vcs repository object. + * @param string $file The filename to create a patchset for. */ - public function __construct($rep, $file, $cache = null) + public function __construct($rep, $file) { - $this->_name = basename($file); - $this->_dir = dirname($file); - $this->_ctime = time() - filemtime($file . ',v'); + $file = $rep->sourceroot() . '/' . $file; + $name = basename($file); + $dir = dirname($file); - parent::__construct($rep, $file, $cache); - } - - /** - * Populate the object with information about the patchsets that - * this file is involved in. - * - * @return boolean True on success. - */ - public function getPatchsets() - { /* Check that we are actually in the filesystem. */ - if (!is_file($this->getFullPath() . ',v')) { + if (!is_file($file . ',v')) { throw new Horde_Vcs_Exception('File Not Found'); } /* Call cvsps to retrieve all patchsets for this file. */ - $cvsps_home = $this->_rep->getPath('cvsps_home'); + $cvsps_home = $rep->getPath('cvsps_home'); $HOME = !empty($cvsps_home) ? 'HOME=' . $cvsps_home . ' ' : ''; - $cmd = $HOME . $this->_rep->getPath('cvsps') . ' -u --cvs-direct --root ' . escapeshellarg($this->_rep->sourceroot) . ' -f ' . escapeshellarg($this->_name) . ' ' . escapeshellarg($this->_dir); - exec($cmd, $return_array, $retval); + $ret_array = array(); + $cmd = $HOME . $rep->getPath('cvsps') . ' -u --cvs-direct --root ' . escapeshellarg($rep->sourceroot()) . ' -f ' . escapeshellarg($name) . ' ' . escapeshellarg($dir); + exec($cmd, $ret_array, $retval); if ($retval) { - throw new Horde_Vcs_Exception('Failed to spawn cvsps to retrieve patchset information'); + throw new Horde_Vcs_Exception('Failed to spawn cvsps to retrieve patchset information.'); } - $this->_patchsets = array(); $state = 'begin'; - foreach ($return_array as $line) { + foreach ($ret_array as $line) { $line = trim($line); if ($line == '---------------------') { $state = 'begin'; @@ -801,14 +766,4 @@ class Horde_Vcs_Patchset_Cvs extends Horde_Vcs_Patchset return true; } - /** - * Return the fully qualified filename of this object. - * - * @return string Fully qualified filename of this object - */ - function getFullPath() - { - return $this->_dir . '/' . $this->_name; - } - } diff --git a/framework/Vcs/lib/Horde/Vcs/Git.php b/framework/Vcs/lib/Horde/Vcs/Git.php index f724c82ce..2713358c9 100644 --- a/framework/Vcs/lib/Horde/Vcs/Git.php +++ b/framework/Vcs/lib/Horde/Vcs/Git.php @@ -34,6 +34,13 @@ class Horde_Vcs_Git extends Horde_Vcs protected $_branches = true; /** + * The available diff types. + * + * @var array + */ + protected $_diffTypes = array('unified'); + + /** * TODO */ public function isValidRevision($rev) @@ -59,7 +66,7 @@ class Horde_Vcs_Git extends Horde_Vcs */ public function getCommand() { - return $this->getPath('git') . ' --git-dir=' . escapeshellarg($this->_sourceroot); + return escapeshellcmd($this->getPath('git')) . ' --git-dir=' . escapeshellarg($this->_sourceroot); } /** @@ -141,129 +148,110 @@ class Horde_Vcs_Git extends Horde_Vcs } /** - * Returns an abbreviated form of the revision, for display. + * Create a range of revisions between two revision numbers. * - * @param string $rev The revision string. + * @param Horde_Vcs_File $file The desired file. + * @param string $r1 The initial revision. + * @param string $r2 The ending revision. * - * @return string The abbreviated string. + * @return array The revision range, or empty if there is no straight + * line path between the revisions. */ - public function abbrev($rev) + public function getRevisionRange($file, $r1, $r2) { - return substr($rev, 0, 7) . '[...]'; + $revs = $this->_getRevisionRange($file, $r1, $r2); + return empty($revs) + ? array_reverse($this->_getRevisionRange($file, $r2, $r1)) + : $revs; } -} - -/** - * Horde_Vcs_Git diff class. - * - * Copyright Chuck Hagenbuch - * - * @author Chuck Hagenbuch - * @package Horde_Vcs - */ -class Horde_Vcs_Diff_Git extends Horde_Vcs_Diff -{ /** - * The available diff types. - * - * @var array + * TODO */ - protected $_diffTypes = array('unified'); + protected function _getRevisionRange($file, $r1, $r2) + { + $cmd = $this->getCommand() . ' rev-list ' . escapeshellarg($r1) . '..' . escapeshellarg($r2) . ' -- ' . escapeshellarg($file->queryModulePath()); + $revs = array(); + + exec($cmd, $revs); + return array_map('trim', $revs); + } /** * Obtain the differences between two revisions of a file. * - * @param Horde_Vcs $rep A repository object. * @param Horde_Vcs_File $file The desired file. - * @param string $rev1 Original revision number to compare from. - * @param string $rev2 New revision number to compare against. - * @param string $type The type of diff (e.g. 'unified'). - * @param integer $num Number of lines to be used in context and - * unified diffs. - * @param boolean $ws Show whitespace in the diff? + * @param string $rev1 Original revision number to compare from. + * @param string $rev2 New revision number to compare against. + * @param array $opts The following optional options: + *
+     * 'num' - (integer) DEFAULT: 3
+     * 'type' - (string) DEFAULT: 'unified'
+     * 'ws' - (boolean) DEFAULT: true
+     * 
* * @return string|boolean False on failure, or a string containing the * diff on success. */ - public function get($rep, $file, $rev1, $rev2, $type = 'context', - $num = 3, $ws = true) + protected function _diff($file, $rev1, $rev2, $opts) { - /* Check that the revision numbers are valid */ - $rev1 = $rep->isValidRevision($rev1) ? $rev1 : 0; - $rev2 = $rep->isValidRevision($rev1) ? $rev2 : 0; - $diff = array(); - $options = ''; - if (!$ws) { - $options .= ' -b -w '; + $flags = ''; + + if (!$opts['ws']) { + $flags .= ' -b -w '; } - switch ($type) { + switch ($opts['type']) { case 'unified': - $options .= '--unified=' . (int)$num; + $flags .= '--unified=' . (int)$opts['num']; break; } // @TODO: add options for $hr options - however these may not // be compatible with some diffs. - $command = $rep->getCommand() . " diff -M -C $options --no-color $rev1..$rev2 -- " . escapeshellarg($file->queryModulePath()) . ' 2>&1'; + $command = $this->getCommand() . " diff -M -C $flags --no-color " . escapeshellarg($rev1 . '..' . $rev2) . ' -- ' . escapeshellarg($file->queryModulePath()) . ' 2>&1'; exec($command, $diff, $retval); return $diff; } /** - * Create a range of revisions between two revision numbers. + * Returns an abbreviated form of the revision, for display. * - * @param Horde_Vcs $rep A repository object. - * @param Horde_Vcs_File $file The desired file. - * @param string $r1 The initial revision. - * @param string $r2 The ending revision. + * @param string $rev The revision string. * - * @return array The revision range, or empty if there is no straight - * line path between the revisions. + * @return string The abbreviated string. */ - public function getRevisionRange($rep, $file, $r1, $r2) + public function abbrev($rev) { - $revs = $this->_getRevisionRange($rep, $file, $r1, $r2); - if (empty($revs)) { - $revs = array_reverse($this->_getRevisionRange($rep, $file, $r2, $r1)); - } - return $revs; + return substr($rev, 0, 7) . '[...]'; } - protected function _getRevisionRange($rep, $file, $r1, $r2) - { - $cmd = $rep->getCommand() . ' rev-list ' . escapeshellarg($r1) . '..' . escapeshellarg($r2) . ' -- ' . escapeshellarg($file->queryModulePath()); - $revs = array(); - - exec($cmd, $revs); - return array_map('trim', $revs); - } } /** * Horde_Vcs_Git directory class. * - * Copyright Chuck Hagenbuch - * * @author Chuck Hagenbuch + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Directory_Git extends Horde_Vcs_Directory { /** - * Tell the object to open and browse its current directory, and - * retrieve a list of all the objects in there. It then populates - * the file/directory stack and makes it available for retrieval. + * Create a Directory object to store information about the files in a + * single directory in the repository. * - * @return integer 1 on success. + * @param Horde_Vcs $rep The Repository object this directory is part of. + * @param string $dn Path to the directory. + * @param array $opts TODO */ - public function browseDir($cache = null, $quicklog = true, - $showattic = false) + public function __construct($rep, $dn, $opts = array()) { - // @TODO For now, we're browsing master + parent::__construct($rep, $dn, $opts); + + // @TODO For now, we're browsing HEAD //$head = trim(shell_exec($this->_rep->getCommand() . ' rev-parse --verify master')); $head = 'HEAD'; // @TODO can use this to see if we have a valid cache of the tree at this revision @@ -276,7 +264,7 @@ class Horde_Vcs_Directory_Git extends Horde_Vcs_Directory $dir .= '/'; } - $cmd = $this->_rep->getCommand() . ' ls-tree --full-name ' . $head . ' ' . escapeshellarg($dir) . ' 2>&1'; + $cmd = $rep->getCommand() . ' ls-tree --full-name ' . escapeshellarg($head) . ' ' . escapeshellarg($dir) . ' 2>&1'; $dir = popen($cmd, 'r'); if (!$dir) { @@ -295,7 +283,7 @@ class Horde_Vcs_Directory_Git extends Horde_Vcs_Directory if ($type == 'tree') { $this->_dirs[] = basename($file); } else { - $this->_files[] = $this->_rep->getFileObject($file, array('cache' => $cache, 'quicklog' => $quicklog)); + $this->_files[] = $rep->getFileObject($file, array('quicklog' => !empty($opts['quicklog']))); } } @@ -307,17 +295,12 @@ class Horde_Vcs_Directory_Git extends Horde_Vcs_Directory /** * Horde_Vcs_Git file class. * - * Copyright Chuck Hagenbuch - * * @author Chuck Hagenbuch + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_File_Git extends Horde_Vcs_File { - /* @TODO */ - protected $_branch; - public $cache; - /** * Create a repository file object, and give it information about * what its parent directory and repository objects are. @@ -328,28 +311,20 @@ class Horde_Vcs_File_Git extends Horde_Vcs_File */ public function __construct($rep, $fl, $opts = array()) { - $this->rep = $rep; - $this->fullname = $fl; - $this->name = basename($fl); - $this->dir = dirname($fl); - $this->cache = empty($opts['cache']) ? null : $opts['cache']; - $this->quicklog = !empty($opts['quicklog']); - if (!empty($opts['branch'])) { - $this->_branch = $opts['branch']; - } + parent::__construct($rep, $fl, $opts); // Get the list of revisions that touch this path - $this->revs = $this->_getRevList($this->_branch); + $this->_revs = $this->_getRevList($this->_branch); - foreach ($this->revs as $rev) { - $this->logs[$rev] = Horde_Vcs_Log_Git::factory($this, $rev); - if ($this->quicklog) { + foreach ($this->_revs as $rev) { + $this->_logs[$rev] = $rep->getLogObject($this, $rev); + if ($this->_quicklog) { break; } } // Add branch information - $cmd = $this->rep->getCommand() . ' show-ref --heads'; + $cmd = $rep->getCommand() . ' show-ref --heads'; $branch_list = shell_exec($cmd); if (empty($branch_list)) { throw new Horde_Vcs_Exception('No branches found'); @@ -357,7 +332,7 @@ class Horde_Vcs_File_Git extends Horde_Vcs_File foreach (explode("\n", trim($branch_list)) as $val) { $line = explode(' ', trim($val), 2); - $this->branches[substr($line[1], strrpos($line[1], '/') + 1)] = $line[0]; + $this->_branches[substr($line[1], strrpos($line[1], '/') + 1)] = $line[0]; } } @@ -370,7 +345,7 @@ class Horde_Vcs_File_Git extends Horde_Vcs_File */ public function getHashForRevision($rev) { - return $this->logs[$rev]->getHashForPath($this->fullname); + return $this->_logs[$rev]->getHashForPath($this->queryModulePath()); } /** @@ -380,9 +355,9 @@ class Horde_Vcs_File_Git extends Horde_Vcs_File */ public function queryModulePath() { - return ($this->dir == '.') - ? $this->name - : $this->dir . '/' . $this->name; + return ($this->_dir == '.') + ? $this->_name + : parent::queryModulePath(); } /** @@ -392,7 +367,7 @@ class Horde_Vcs_File_Git extends Horde_Vcs_File { $revs = array(); - foreach (array_keys($this->branches) as $key) { + foreach (array_keys($this->_branches) as $key) { $revs[$key] = $this->_getRevList($key); } @@ -404,7 +379,7 @@ class Horde_Vcs_File_Git extends Horde_Vcs_File */ protected function _getRevList($branch) { - $cmd = $this->rep->getCommand() . ' rev-list ' . (empty($branch) ? '--branches' : $branch) . ' -- ' . escapeshellarg($this->fullname) . ' 2>&1'; + $cmd = $this->_rep->getCommand() . ' rev-list ' . (empty($branch) ? '--branches' : $branch) . ' -- ' . escapeshellarg($this->queryModulePath()) . ' 2>&1'; $revisions = shell_exec($cmd); if (substr($revisions, 5) == 'fatal') { @@ -416,55 +391,36 @@ class Horde_Vcs_File_Git extends Horde_Vcs_File return explode("\n", trim($revisions)); } + /** + * Return the "base" filename (i.e. the filename needed by the various + * command line utilities). + * + * @return string A filename. + */ + public function queryPath() + { + return $this->queryModulePath(); + } + } /** * Horde_Vcs_Git log class. * - * Chuck Hagenbuch - * * @author Chuck Hagenbuch + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Log_Git extends Horde_Vcs_Log { - public $files = array(); - - public static function factory($file, $rev) - { - /* The version of the cached data. Increment this whenever the - * internal storage format changes, such that we must - * invalidate prior cached data. */ - $cacheVersion = 1; - $cacheId = $file->rep->sourceroot() . '_r' . $rev . '_v' . $cacheVersion; - - if ($file->cache && - // Individual revisions can be cached forever - // return array_keys( - $file->cache->exists($cacheId, 0)) { - $logOb = unserialize($file->cache->get($cacheId, 0)); - $logOb->setRepository($file->rep); - } else { - $logOb = new Horde_Vcs_Log_Git($file, $rev); - - if ($file->cache) { - $file->cache->set($cacheId, serialize($logOb)); - } - } - - return $logOb; - } - /** * Constructor. */ - public function __construct($fl, $rev) + public function __construct($rep, $fl, $rev) { - parent::__construct($fl); + parent::__construct($rep, $fl, $rev); - $this->rev = $rev; - - $cmd = $this->rep->getCommand() . ' whatchanged --no-color --pretty=format:"commit %H%nAuthor:%an <%ae>%nAuthorDate:%at%nRefs:%d%n%n%s%n%b" --no-abbrev -n 1 ' . $this->rev; + $cmd = $rep->getCommand() . ' whatchanged --no-color --pretty=format:"commit %H%nAuthor:%an <%ae>%nAuthorDate:%at%nRefs:%d%n%n%s%n%b" --no-abbrev -n 1 ' . $rev; $pipe = popen($cmd, 'r'); if (!is_resource($pipe)) { throw new Horde_Vcs_Exception('Unable to run ' . $cmd . ': ' . error_get_last()); @@ -484,11 +440,11 @@ class Horde_Vcs_Log_Git extends Horde_Vcs_Log switch (trim($key)) { case 'Author': - $this->author = $value; + $this->_author = $value; break; case 'AuthorDate': - $this->date = $value; + $this->_date = $value; break; case 'Refs': @@ -497,11 +453,11 @@ class Horde_Vcs_Log_Git extends Horde_Vcs_Log foreach (explode(',', $value) as $val) { $val = trim($val); if (strpos($val, 'refs/tags/') === 0) { - $this->tags[] = substr($val, 10); + $this->_tags[] = substr($val, 10); } } - if (!empty($this->tags)) { - sort($this->tags); + if (!empty($this->_tags)) { + sort($this->_tags); } } break; @@ -517,7 +473,7 @@ class Horde_Vcs_Log_Git extends Horde_Vcs_Log $log .= $line; $line = fgets($pipe); } - $this->log = trim($log); + $this->_log = trim($log); // @TODO internal line formatting // Build list of files in this revision. The format of these lines is @@ -525,14 +481,14 @@ class Horde_Vcs_Log_Git extends Horde_Vcs_Log // http://www.kernel.org/pub/software/scm/git/docs/git-diff-tree.html while ($line) { preg_match('/:(\d+) (\d+) (\w+) (\w+) (.+)\t(.+)(\t(.+))?/', $line, $matches); - $this->files[$matches[6]] = array( + $this->_files[$matches[6]] = array( 'srcMode' => $matches[1], 'dstMode' => $matches[2], 'srcSha1' => $matches[3], 'dstSha1' => $matches[4], 'status' => $matches[5], 'srcPath' => $matches[6], - 'dstPath' => isset($matches[7]) ? $matches[7] : '', + 'dstPath' => isset($matches[7]) ? $matches[7] : '' ); $line = fgets($pipe); @@ -541,15 +497,13 @@ class Horde_Vcs_Log_Git extends Horde_Vcs_Log fclose($pipe); } - public function setRepository($rep) - { - $this->rep = $rep; - } - + /** + * TODO + */ public function getHashForPath($path) { - // @TODO not confident yet abotu the choice of dstSha1 vs. srcSha1 - return $this->files[$path]['dstSha1']; + // @TODO Not confident yet abotu the choice of dstSha1 vs. srcSha1 + return $this->_files[$path]['dstSha1']; } /** @@ -566,8 +520,8 @@ class Horde_Vcs_Log_Git extends Horde_Vcs_Log public function queryBranch() { - $branches = $ret = array(); - $command = $this->rep->getCommand() . ' branch --contains ' . escapeshellarg($this->rev) . ' 2>&1'; + $branches = array(); + $command = $this->_rep->getCommand() . ' branch --contains ' . escapeshellarg($this->_rev) . ' 2>&1'; exec($command, $branches); return array_map('trim', $branches, array_fill(0, count($branches), '* ')); } @@ -577,21 +531,21 @@ class Horde_Vcs_Log_Git extends Horde_Vcs_Log /** * Horde_Vcs_Git Patchset class. * - * Copyright Chuck Hagenbuch - * * @author Chuck Hagenbuch + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Patchset_Git extends Horde_Vcs_Patchset { /** - * Populate the object with information about the patchsets that - * this file is involved in. + * Constructor + * + * @param Horde_Vcs $rep A Horde_Vcs repository object. + * @param string $file The filename to create patchsets for. */ - function getPatchsets() + public function __construct($rep, $file) { - $fileOb = $this->_rep->getFileObject($this->_file); - $this->_patchsets = array(); + $fileOb = $rep->getFileObject($this->file); foreach ($fileOb->logs as $rev => $log) { $this->_patchsets[$rev] = array( diff --git a/framework/Vcs/lib/Horde/Vcs/Rcs.php b/framework/Vcs/lib/Horde/Vcs/Rcs.php index ef42e142b..ff80b8720 100644 --- a/framework/Vcs/lib/Horde/Vcs/Rcs.php +++ b/framework/Vcs/lib/Horde/Vcs/Rcs.php @@ -19,6 +19,41 @@ class Horde_Vcs_Rcs extends Horde_Vcs } /** + * Create a range of revisions between two revision numbers. + * + * @param Horde_Vcs_File $file The desired file. + * @param string $r1 The initial revision. + * @param string $r2 The ending revision. + * + * @return array The revision range, or empty if there is no straight + * line path between the revisions. + */ + public function getRevisionRange($file, $r1, $r2) + { + if ($this->cmp($r1, $r2) == 1) { + $curr = $this->prev($r1); + $stop = $this->prev($r2); + $flip = true; + } else { + $curr = $r2; + $stop = $r1; + $flip = false; + } + + $ret_array = array(); + + do { + $ret_array[] = $curr; + $curr = $this->prev($curr); + if ($curr == $stop) { + return ($flip) ? array_reverse($ret_array) : $ret_array; + } + } while ($this->cmp($curr, $stop) != -1); + + return array(); + } + + /** * Checks an RCS file in with a specified change log. * * @param string $filepath Location of file to check in. @@ -30,11 +65,7 @@ class Horde_Vcs_Rcs extends Horde_Vcs */ public function ci($filepath, $message, $user = null, $newBinary = false) { - if ($user) { - putenv('LOGNAME=' . $user); - } else { - putenv('LOGNAME=guest'); - } + putenv('LOGNAME=' . ($user ? $user : 'guest')); $ci_cmd = $this->getPath('ci') . ' ' . escapeshellarg($filepath) . ' 2>&1'; $rcs_cmd = $this->getPath('rcs') . ' -i -kb ' . escapeshellarg($filepath) . ' 2>&1'; @@ -45,12 +76,7 @@ class Horde_Vcs_Rcs extends Horde_Vcs $pipe_def = array(0 => array("pipe", 'r'), 1 => array("pipe", 'w')); - if ($newBinary) { - $process = proc_open($rcs_cmd, $pipe_def, $pipes); - } else { - $process = proc_open($ci_cmd, $pipe_def, $pipes); - } - + $process = proc_open($newBinary ? $rcs_cmd : $ci_cmd, $pipe_def, $pipes); if (is_resource($process)) { foreach ($message_lines as $line) { if ($line == '.\n') { @@ -227,10 +253,10 @@ class Horde_Vcs_Rcs extends Horde_Vcs * it. For example, if we remove 2 portions of 1.2.3.4, we are * left with 1.2. * - * @param string $val Input revision - * @param integer $amount Number of portions to strip + * @param string $val Input revision. + * @param integer $amount Number of portions to strip. * - * @return string Stripped revision number + * @return string Stripped revision number. */ public function strip($val, $amount = 1) { @@ -240,6 +266,7 @@ class Horde_Vcs_Rcs extends Horde_Vcs while ($amount-- > 0 && ($pos = strrpos($val, '.')) !== false) { $val = substr($val, 0, $pos); } + return ($pos !== false) ? $val : false; } @@ -247,9 +274,9 @@ class Horde_Vcs_Rcs extends Horde_Vcs * The size of a revision number is the number of portions it has. * For example, 1,2.3.4 is of size 4. * - * @param string $val Revision number to determine size of + * @param string $val Revision number to determine size of. * - * @return integer Size of revision number + * @return integer Size of revision number. */ public function sizeof($val) { diff --git a/framework/Vcs/lib/Horde/Vcs/Svn.php b/framework/Vcs/lib/Horde/Vcs/Svn.php index 0ea28ab59..cb31ed4be 100644 --- a/framework/Vcs/lib/Horde/Vcs/Svn.php +++ b/framework/Vcs/lib/Horde/Vcs/Svn.php @@ -14,6 +14,7 @@ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html. * * @author Anil Madhavapeddy + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Svn extends Horde_Vcs @@ -125,9 +126,11 @@ class Horde_Vcs_Svn extends Horde_Vcs { $rep->assertValidRevision($rev); - return ($RCS = popen($this->getCommand() . ' cat -r ' . escapeshellarg($rev) . ' ' . escapeshellarg($fullname) . ' 2>&1', VC_WINDOWS ? 'rb' : 'r')) - ? $RCS - : throw new Horde_Vcs_Exception('Couldn\'t perform checkout of the requested file'); + if ($RCS = popen($this->getCommand() . ' cat -r ' . escapeshellarg($rev) . ' ' . escapeshellarg($fullname) . ' 2>&1', VC_WINDOWS ? 'rb' : 'r')) { + return $RCS; + } + + throw new Horde_Vcs_Exception('Couldn\'t perform checkout of the requested file'); } /** @@ -138,68 +141,68 @@ class Horde_Vcs_Svn extends Horde_Vcs return $rev && is_numeric($rev); } -} + /** + * Create a range of revisions between two revision numbers. + * + * @param Horde_Vcs_File $file The desired file. + * @param string $r1 The initial revision. + * @param string $r2 The ending revision. + * + * @return array The revision range, or empty if there is no straight + * line path between the revisions. + */ + public function getRevisionRange($file, $r1, $r2) + { + // TODO + } -/** - * Horde_Vcs_Svn diff class. - * - * Copyright Anil Madhavapeddy, - * - * @author Anil Madhavapeddy - * @package Horde_Vcs - */ -class Horde_Vcs_Diff_Svn extends Horde_Vcs_Diff -{ /** * Obtain the differences between two revisions of a file. * - * @param Horde_Vcs $rep A repository object. * @param Horde_Vcs_File $file The desired file. * @param string $rev1 Original revision number to compare from. * @param string $rev2 New revision number to compare against. - * @param string $type The type of diff (e.g. 'unified'). - * @param integer $num Number of lines to be used in context and - * unified diffs. - * @param boolean $ws Show whitespace in the diff? + * @param array $opts The following optional options: + *
+     * 'num' - (integer) DEFAULT: 3
+     * 'type' - (string) DEFAULT: 'unified'
+     * 'ws' - (boolean) DEFAULT: true
+     * 
* * @return string|boolean False on failure, or a string containing the * diff on success. */ - public function get($rep, $file, $rev1, $rev2, $type = 'context', - $num = 3, $ws = true) + protected function _diff($file, $rev1, $rev2, $opts) { - /* Check that the revision numbers are valid */ - $rev1 = $rep->isValidRevision($rev1) ? $rev1 : 0; - $rev2 = $rep->isValidRevision($rev1) ? $rev2 : 0; - $fullName = $file->queryFullPath(); $diff = array(); - $options = ''; - if (!$ws) { - $options .= ' -bB '; + $flags = ''; + + if (!$opts['ws']) { + $flags .= ' -bB '; } - switch ($type) { + switch ($opts['type']) { case 'context': - $options .= '--context=' . (int)$num; + $flags .= '--context=' . (int)$opts['num']; break; case 'unified': - $options .= '-p --unified=' . (int)$num; + $flags .= '-p --unified=' . (int)$opts['num']; break; case 'column': - $options .= '--side-by-side --width=120'; + $flags .= '--side-by-side --width=120'; break; case 'ed': - $options .= '-e'; + $flags .= '-e'; break; } // TODO: add options for $hr options - however these may not // be compatible with some diffs. - $command = $rep->getCommand() . " diff --diff-cmd " . $rep->getPath('diff') . " -r $rev1:$rev2 -x " . escapeshellarg($options) . ' ' . escapeshellarg($file->queryFullPath()) . ' 2>&1'; + $command = $this->getCommand() . " diff --diff-cmd " . $this->getPath('diff') . ' -r ' . escapeshellarg($rev1 . ':' . $rev2) . ' -x ' . escapeshellarg($flags) . ' ' . escapeshellarg($file->queryFullPath()) . ' 2>&1'; exec($command, $diff, $retval); return $diff; @@ -210,24 +213,25 @@ class Horde_Vcs_Diff_Svn extends Horde_Vcs_Diff /** * Horde_Vcs_Svn directory class. * - * Copyright Anil Madhavapeddy, - * * @author Anil Madhavapeddy + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Directory_Svn extends Horde_Vcs_Directory { /** - * Tell the object to open and browse its current directory, and - * retrieve a list of all the objects in there. It then populates - * the file/directory stack and makes it available for retrieval. + * Create a Directory object to store information about the files in a + * single directory in the repository. * - * @return integer 1 on success. + * @param Horde_Vcs $rep The Repository object this directory is part of. + * @param string $dn Path to the directory. + * @param array $opts TODO */ - public function browseDir($cache = null, $quicklog = true, - $showattic = false) + public function __construct($rep, $dn, $opts = array()) { - $cmd = $this->_rep->getCommand() . ' ls ' . escapeshellarg($this->_rep->sourceroot() . $this->queryDir()) . ' 2>&1'; + parent::__construct($rep, $dn, $opts); + + $cmd = $rep->getCommand() . ' ls ' . escapeshellarg($rep->sourceroot() . $this->queryDir()) . ' 2>&1'; $dir = popen($cmd, 'r'); if (!$dir) { @@ -248,15 +252,17 @@ class Horde_Vcs_Directory_Svn extends Horde_Vcs_Directory } elseif (substr($line, -1) == '/') { $this->_dirs[] = substr($line, 0, -1); } else { - $this->_files[] = $this->_rep->getFileObject($this->queryDir() . "/$line", array('cache' => $cache, 'quicklog' => $quicklog)); + $this->_files[] = $rep->getFileObject($this->queryDir() . '/' . $line, array('quicklog' => !empty($opts['quicklog']))); } } pclose($dir); - return $errors - ? throw new Horde_Vcs_Exception(implode("\n", $errors)) - : true; + if (empty($errors)) { + return true; + } + + throw new Horde_Vcs_Exception(implode("\n", $errors)); } } @@ -264,12 +270,16 @@ class Horde_Vcs_Directory_Svn extends Horde_Vcs_Directory /** * Horde_Vcs_Svn file class. * - * Copyright Anil Madhavapeddy, - * * @author Anil Madhavapeddy + * @author Michael Slusarz * @package Horde_Vcs */ -class Horde_Vcs_File_Svn extends Horde_Vcs_File { +class Horde_Vcs_File_Svn extends Horde_Vcs_File +{ + /** + * @var resource + */ + public $logpipe; /** * Create a repository file object, and give it information about @@ -281,18 +291,14 @@ class Horde_Vcs_File_Svn extends Horde_Vcs_File { */ public function __construct($rep, $fl, $opts = array()) { - $this->rep = $rep; - $this->name = basename($fl); - $this->dir = dirname($fl); - $this->filename = $fl; - $this->quicklog = !empty($opts['quicklog']); + parent::__construct($rep, $fl, $opts); /* This doesn't work; need to find another way to simply * request the most recent revision: * - * $flag = $this->quicklog ? '-r HEAD ' : ''; */ - $flag = ''; - $cmd = $this->rep->getCommand() . ' log -v ' . $flag . escapeshellarg($this->queryFullPath()) . ' 2>&1'; + * $flag = $this->_quicklog ? '-r HEAD ' : ''; + */ + $cmd = $rep->getCommand() . ' log -v ' . escapeshellarg($this->queryFullPath()) . ' 2>&1'; $pipe = popen($cmd, 'r'); if (!$pipe) { throw new Horde_Vcs_Exception('Failed to execute svn log: ' . $cmd); @@ -303,16 +309,17 @@ class Horde_Vcs_File_Svn extends Horde_Vcs_File { throw new Horde_Vcs_Exception('Error executing svn log: ' . $header); } + $this->logpipe = $pipe; + while (!feof($pipe)) { - $log = new Horde_Vcs_Log_Svn($this->rep, $this); - $err = $log->processLog($pipe); - if ($err) { + try { + $log = $rep->getLogObject($this, null); $rev = $log->queryRevision(); - $this->logs[$rev] = $log; - $this->revs[] = $rev; - } + $this->_logs[$rev] = $log; + $this->_revs[] = $rev; + } catch (Horde_Vcs_Exception $e) {} - if ($this->quicklog) { + if ($this->_quicklog) { break; } } @@ -322,13 +329,13 @@ class Horde_Vcs_File_Svn extends Horde_Vcs_File { /** * Returns name of the current file without the repository - * extensions (usually ,v) + * extensions (usually ,v). * - * @return Filename without repository extension + * @return string Filename without repository extension. */ - function queryName() + public function queryName() { - return preg_replace('/,v$/', '', $this->name); + return preg_replace('/,v$/', '', $this->_name); } } @@ -336,49 +343,58 @@ class Horde_Vcs_File_Svn extends Horde_Vcs_File { /** * Horde_Vcs_Svn log class. * - * Anil Madhavapeddy, - * * @author Anil Madhavapeddy * @package Horde_Vcs */ class Horde_Vcs_Log_Svn extends Horde_Vcs_Log { - public $err; - public $files; + /** + * TODO + */ + protected $_files = array(); - public function processLog($pipe) + /** + * Constructor. + */ + public function __construct($rep, $fl, $rev) { - $line = fgets($pipe); + parent::__construct($rep, $fl, $rev); - if (feof($pipe)) { - return false; + $line = fgets($fl->logpipe); + + if (feof($fl->logpipe)) { + return; } if (preg_match('/^r([0-9]*) \| (.*?) \| (.*) \(.*\) \| ([0-9]*) lines?$/', $line, $matches)) { - $this->rev = $matches[1]; - $this->author = $matches[2]; - $this->date = strtotime($matches[3]); + $this->_rev = $matches[1]; + $this->_author = $matches[2]; + $this->_date = strtotime($matches[3]); $size = $matches[4]; } else { - $this->err = $line; - return false; + throw new Horde_Vcs_Exception('SVN Error'); } - fgets($pipe); + fgets($fl->logpipe); - $this->files = array(); - while (($line = trim(fgets($pipe))) != '') { - $this->files[] = $line; + while (($line = trim(fgets($fl->logpipe))) != '') { + $this->_files[] = $line; } for ($i = 0; $i != $size; ++$i) { - $this->log = $this->log . chop(fgets($pipe)) . "\n"; + $this->_log = $this->_log . chop(fgets($fl->logpipe)) . "\n"; } - $this->log = chop($this->log); - fgets($pipe); + $this->_log = chop($this->_log); + fgets($fl->logpipe); + } - return true; + /** + * TODO + */ + public function queryFiles() + { + return $this->_files; } } @@ -386,33 +402,33 @@ class Horde_Vcs_Log_Svn extends Horde_Vcs_Log /** * Horde_Vcs_Svn Patchset class. * - * Copyright Anil Madhavapeddy, - * * @author Anil Madhavapeddy + * @author Michael Slusarz * @package Horde_Vcs */ class Horde_Vcs_Patchset_Svn extends Horde_Vcs_Patchset { /** - * Populate the object with information about the patchsets that - * this file is involved in. + * Constructor * - * @return boolean True on success. + * @param Horde_Vcs $rep A Horde_Vcs repository object. + * @param string $file The filename to create patchsets for. */ - function getPatchsets() + public function __construct($rep, $file) { - $fileOb = new Horde_Vcs_File_Svn($this->_rep, $this->_file); + $fileOb = $rep->getFileObject($file); - $this->_patchsets = array(); foreach ($fileOb->logs as $rev => $log) { - $this->_patchsets[$rev] = array(); - $this->_patchsets[$rev]['date'] = $log->queryDate(); - $this->_patchsets[$rev]['author'] = $log->queryAuthor(); - $this->_patchsets[$rev]['branch'] = ''; - $this->_patchsets[$rev]['tag'] = ''; - $this->_patchsets[$rev]['log'] = $log->queryLog(); - $this->_patchsets[$rev]['members'] = array(); - foreach ($log->files as $file) { + $this->_patchsets[$rev] = array( + 'author' => $log->queryAuthor(), + 'branch' => '', + 'date' => $log->queryDate(), + 'log' => $log->queryLog(), + 'members' => array(), + 'tag' => '' + ); + + foreach ($log->queryFiles() as $file) { $action = substr($file, 0, 1); $file = preg_replace('/.*?\s(.*?)(\s|$).*/', '\\1', $file); $to = $rev; -- 2.11.0