From a06c6d8b8ba1628167c68921fdd917b4ca48ab06 Mon Sep 17 00:00:00 2001 From: "Michael J. Rubinsky" Date: Fri, 15 Oct 2010 14:05:02 -0400 Subject: [PATCH] Break out Horde/Vcs classes into autoloadable files --- framework/Vcs/lib/Horde/Vcs.php | 678 -------------------------- framework/Vcs/lib/Horde/Vcs/Cvs.php | 569 --------------------- framework/Vcs/lib/Horde/Vcs/Directory.php | 202 ++++++++ framework/Vcs/lib/Horde/Vcs/Directory/Cvs.php | 83 ++++ framework/Vcs/lib/Horde/Vcs/Directory/Git.php | 89 ++++ framework/Vcs/lib/Horde/Vcs/File.php | 286 +++++++++++ framework/Vcs/lib/Horde/Vcs/File/Cvs.php | 249 ++++++++++ framework/Vcs/lib/Horde/Vcs/File/Git.php | 222 +++++++++ framework/Vcs/lib/Horde/Vcs/Git.php | 551 +-------------------- framework/Vcs/lib/Horde/Vcs/Log.php | 155 ++++++ framework/Vcs/lib/Horde/Vcs/Log/Cvs.php | 114 +++++ framework/Vcs/lib/Horde/Vcs/Log/Git.php | 163 +++++++ framework/Vcs/lib/Horde/Vcs/Patchset.php | 50 ++ framework/Vcs/lib/Horde/Vcs/Patchset/Cvs.php | 143 ++++++ framework/Vcs/lib/Horde/Vcs/Patchset/Git.php | 95 ++++ framework/Vcs/package.xml | 96 +++- 16 files changed, 1924 insertions(+), 1821 deletions(-) create mode 100644 framework/Vcs/lib/Horde/Vcs/Directory.php create mode 100644 framework/Vcs/lib/Horde/Vcs/Directory/Cvs.php create mode 100644 framework/Vcs/lib/Horde/Vcs/Directory/Git.php create mode 100644 framework/Vcs/lib/Horde/Vcs/File.php create mode 100644 framework/Vcs/lib/Horde/Vcs/File/Cvs.php create mode 100644 framework/Vcs/lib/Horde/Vcs/File/Git.php create mode 100644 framework/Vcs/lib/Horde/Vcs/Log.php create mode 100644 framework/Vcs/lib/Horde/Vcs/Log/Cvs.php create mode 100644 framework/Vcs/lib/Horde/Vcs/Log/Git.php create mode 100644 framework/Vcs/lib/Horde/Vcs/Patchset.php create mode 100644 framework/Vcs/lib/Horde/Vcs/Patchset/Cvs.php create mode 100644 framework/Vcs/lib/Horde/Vcs/Patchset/Git.php diff --git a/framework/Vcs/lib/Horde/Vcs.php b/framework/Vcs/lib/Horde/Vcs.php index 5acf61285..de63c3a4c 100644 --- a/framework/Vcs/lib/Horde/Vcs.php +++ b/framework/Vcs/lib/Horde/Vcs.php @@ -641,681 +641,3 @@ class Horde_Vcs } } - -/** - * Horde_Vcs_Cvs directory class. - * - * @package Horde_Vcs - */ -abstract class Horde_Vcs_Directory -{ - /** - * @var Horde_Vcs - */ - protected $_rep; - - /** - * @var string - */ - protected $_dirName; - - /** - * @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. - * - * @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, $opts = array()) - { - $this->_rep = $rep; - $this->_moduleName = $dn; - $this->_dirName = '/' . $dn; - } - - /** - * Return fully qualified pathname to this directory with no trailing /. - * - * @return string Pathname of this directory. - */ - public function queryDir() - { - return $this->_dirName; - } - - /** - * TODO - */ - public function queryDirList() - { - return $this->_dirs; - } - - /** - * TODO - */ - public function queryFileList($showattic = false) - { - return ($showattic && isset($this->_mergedFiles)) - ? $this->_mergedFiles - : $this->_files; - } - - /** - * Sort the contents of the directory in a given fashion and - * order. - * - * @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 Horde_Vcs::SORT_[*] where * can be: - * ASCENDING, DESCENDING for the order of the sort. - */ - public function applySort($how = Horde_Vcs::SORT_NONE, - $dir = Horde_Vcs::SORT_ASCENDING) - { - // Always sort directories by name. - natcasesort($this->_dirs); - - $this->_doFileSort($this->_files, $how); - - if (isset($this->_atticFiles)) { - $this->_doFileSort($this->_atticFiles, $how); - } - - if (isset($this->_mergedFiles)) { - $this->_doFileSort($this->_mergedFiles, $how); - } - - if ($dir == Horde_Vcs::SORT_DESCENDING) { - $this->_dirs = array_reverse($this->_dirs); - $this->_files = array_reverse($this->_files); - if (isset($this->_mergedFiles)) { - $this->_mergedFiles = array_reverse($this->_mergedFiles); - } - } - } - - /** - * TODO - */ - protected function _doFileSort(&$fileList, $how = Horde_Vcs::SORT_NONE) - { - switch ($how) { - case Horde_Vcs::SORT_AGE: - usort($fileList, array($this, '_fileAgeSort')); - break; - - case Horde_Vcs::SORT_NAME: - usort($fileList, array($this, '_fileNameSort')); - break; - - case Horde_Vcs::SORT_AUTHOR: - usort($fileList, array($this, '_fileAuthorSort')); - break; - - case Horde_Vcs::SORT_REV: - usort($fileList, array($this, '_fileRevSort')); - break; - - case Horde_Vcs::SORT_NONE: - default: - break; - } - } - - /** - * Sort function for ascending age. - */ - public function _fileAgeSort($a, $b) - { - $aa = $a->queryLastLog(); - $bb = $b->queryLastLog(); - return ($aa->queryDate() == $bb->queryDate()) - ? 0 - : (($aa->queryDate() < $bb->queryDate()) ? 1 : -1); - } - - /** - * Sort function by author name. - */ - public function _fileAuthorSort($a, $b) - { - $aa = $a->queryLastLog(); - $bb = $b->queryLastLog(); - return ($aa->queryAuthor() == $bb->queryAuthor()) - ? 0 - : (($aa->queryAuthor() > $bb->queryAuthor()) ? 1 : -1); - } - - /** - * Sort function for ascending filename. - */ - public function _fileNameSort($a, $b) - { - return strcasecmp($a->queryName(), $b->queryName()); - } - - /** - * Sort function for ascending revision. - */ - public function _fileRevSort($a, $b) - { - return $this->_rep->cmp($a->queryRevision(), $b->queryRevision()); - } - - /** - * TODO - */ - public function getBranches() - { - return array(); - } -} - -/** - * Horde_Vcs file class. - * - * @package Horde_Vcs - */ -abstract class Horde_Vcs_File -{ - /** - * TODO - */ - protected $_dir; - - /** - * TODO - */ - protected $_name; - - /** - * TODO - */ - public $logs = array(); - - /** - * TODO - */ - protected $_revs = array(); - - /** - * TODO - */ - protected $_rep; - - /** - * TODO - */ - protected $_quicklog; - - /** - * TODO - */ - protected $_branch = null; - - /** - * Create a repository file object, and give it information about - * what its parent directory and repository objects are. - * - * @param string $filename Full path to this file. - * @param array $opts TODO - */ - public function __construct($filename, $opts = array()) - { - $this->_name = basename($filename); - $this->_dir = dirname($filename); - - $this->_quicklog = !empty($opts['quicklog']); - if (!empty($opts['branch'])) { - $this->_branch = $opts['branch']; - } - } - - protected function _ensureRevisionsInitialized() - { - } - - protected function _ensureLogsInitialized() - { - } - - /** - * When serializing, don't return the repository object - */ - public function __sleep() - { - return array_diff(array_keys(get_object_vars($this)), array('_rep')); - } - - /** - * TODO - */ - public function setRepository($rep) - { - $this->_rep = $rep; - } - - /** - * TODO - better name, wrap an object around this? - */ - public function getBlob($revision) - { - return $this->_rep->checkout($this->queryPath(), $revision); - } - - /** - * Has the file been deleted? - * - * @return boolean Is this file deleted? - */ - public function isDeleted() - { - return false; - } - - /** - * Returns name of the current file without the repository extensions. - * - * @return string Filename without repository extension - */ - function queryName() - { - return $this->_name; - } - - /** - * Returns the name of the current file as in the repository. - * - * @return string Filename (without the path). - */ - public function queryRepositoryName() - { - return $this->_name; - } - - /** - * Return the last revision of the current file on the HEAD branch. - * - * @return string Last revision of the current file. - * @throws Horde_Vcs_Exception - */ - public function queryRevision() - { - $this->_ensureRevisionsInitialized(); - if (!isset($this->_revs[0])) { - throw new Horde_Vcs_Exception('No revisions'); - } - return $this->_revs[0]; - } - - /** - * TODO - */ - public function queryPreviousRevision($rev) - { - $this->_ensureRevisionsInitialized(); - $key = array_search($rev, $this->_revs); - return (($key !== false) && isset($this->_revs[$key + 1])) - ? $this->_revs[$key + 1] - : null; - } - - /** - * Return the last Horde_Vcs_Log object in the file. - * - * @return Horde_Vcs_Log Log object of the last entry in the file. - * @throws Horde_Vcs_Exception - */ - public function queryLastLog() - { - $this->_ensureRevisionsInitialized(); - $this->_ensureLogsInitialized(); - if (!isset($this->_revs[0]) || !isset($this->logs[$this->_revs[0]])) { - throw new Horde_Vcs_Exception('No revisions'); - } - return $this->logs[$this->_revs[0]]; - } - - /** - * Sort the list of Horde_Vcs_Log objects that this file contains. - * - * @param integer $how Horde_Vcs::SORT_REV (sort by revision), - * Horde_Vcs::SORT_NAME (sort by author name), or - * Horde_Vcs::SORT_AGE (sort by commit date). - */ - public function applySort($how = Horde_Vcs::SORT_REV) - { - $this->_ensureLogsInitialized(); - - switch ($how) { - case Horde_Vcs::SORT_NAME: - $func = 'Name'; - break; - - case Horde_Vcs::SORT_AGE: - $func = 'Age'; - break; - - case Horde_Vcs::SORT_REV: - default: - $func = 'Revision'; - break; - } - - uasort($this->logs, array($this, 'sortBy' . $func)); - return true; - } - - /** - * The sortBy*() functions are internally used by applySort. - */ - public function sortByRevision($a, $b) - { - return $this->_rep->cmp($b->queryRevision(), $a->queryRevision()); - } - - public function sortByAge($a, $b) - { - return $b->queryDate() - $a->queryDate(); - } - - public function sortByName($a, $b) - { - return strcmp($a->queryAuthor(), $b->queryAuthor()); - } - - /** - * Return the fully qualified filename of this object. - * - * @return string Fully qualified filename of this object. - */ - public function queryFullPath() - { - return $this->_rep->sourceroot() . '/' . $this->queryModulePath(); - } - - /** - * Return the filename relative to its sourceroot. - * - * @return string Pathname relative to the sourceroot. - */ - public function queryModulePath() - { - 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 array(); - } - - /** - * TODO - */ - public function queryLogs($rev = null) - { - $this->_ensureLogsInitialized(); - return is_null($rev) - ? $this->logs - : (isset($this->logs[$rev]) ? $this->logs[$rev] : null); - } - - /** - * TODO - */ - public function revisionCount() - { - $this->_ensureRevisionsInitialized(); - return count($this->_revs); - } - - /** - * TODO - */ - public function querySymbolicRevisions() - { - return array(); - } -} - -/** - * Horde_Vcs log class. - * - * @package Horde_Vcs - */ -abstract class Horde_Vcs_Log -{ - protected $_rep; - protected $_file; - protected $_files = array(); - protected $_rev; - protected $_author; - protected $_tags = array(); - protected $_date; - protected $_log; - protected $_state; - protected $_lines = ''; - protected $_branches = array(); - - /** - * Constructor. - */ - public function __construct($rev) - { - $this->_rev = $rev; - } - - protected function _ensureInitialized() - { - } - - /** - * When serializing, don't return the repository object - */ - public function __sleep() - { - return array_diff(array_keys(get_object_vars($this)), array('_file', '_rep')); - } - - /** - * TODO - */ - public function setRepository($rep) - { - $this->_rep = $rep; - } - - public function setFile(Horde_Vcs_File $file) - { - $this->_file = $file; - } - - /** - * TODO - */ - public function queryRevision() - { - $this->_ensureInitialized(); - return $this->_rev; - } - - /** - * TODO - */ - public function queryDate() - { - $this->_ensureInitialized(); - return $this->_date; - } - - /** - * TODO - */ - public function queryAuthor() - { - $this->_ensureInitialized(); - return $this->_author; - } - - /** - * TODO - */ - public function queryLog() - { - $this->_ensureInitialized(); - return $this->_log; - } - - /** - * TODO - */ - public function queryBranch() - { - $this->_ensureInitialized(); - return array(); - } - - /** - * TODO - */ - public function queryChangedLines() - { - $this->_ensureInitialized(); - return $this->_lines; - } - - /** - * TODO - */ - public function queryTags() - { - $this->_ensureInitialized(); - return $this->_tags; - } - - /** - * Given a branch revision number, this function remaps it - * accordingly, and performs a lookup on the file object to - * return the symbolic name(s) of that branch in the tree. - * - * @return array Hash of symbolic names => branch numbers. - */ - public function querySymbolicBranches() - { - $this->_ensureInitialized(); - - $symBranches = array(); - $branches = $this->_file->queryBranches(); - - foreach ($this->_branches as $branch) { - if (($key = array_search($branch, $branches)) !== false) { - $symBranches[$key] = $branch; - } - } - - return $symBranches; - } - - /** - * TODO - */ - public function queryFiles($file = null) - { - $this->_ensureInitialized(); - return is_null($file) - ? $this->_files - : (isset($this->_files[$file]) ? $this->_files[$file] : array()); - } -} - -/** - * Horde_Vcs patchset class. - * - * @package Horde_Vcs - */ -abstract class Horde_Vcs_Patchset -{ - const MODIFIED = 0; - const ADDED = 1; - const DELETED = 2; - - /** - * @var array - */ - protected $_patchsets = array(); - - /** - * Constructor - * - * @param Horde_Vcs $rep A Horde_Vcs repository object. - * @param string $file The filename to create patchsets for. - * @param array $opts Additional options: - *
-     * 'range' - (array) The patchsets to process.
-     *           DEFAULT: None (all patchsets are processed).
-     * 
- */ - abstract public function __construct($rep, $file, $opts = array()); - - /** - * TODO - * - * @return array TODO - * 'date' - * 'author' - * 'branches' - * 'tags' - * 'log' - * 'members' - array: - * 'file' - * 'from' - * 'to' - * 'status' - */ - public function getPatchsets() - { - return $this->_patchsets; - } -} diff --git a/framework/Vcs/lib/Horde/Vcs/Cvs.php b/framework/Vcs/lib/Horde/Vcs/Cvs.php index 93cec5e4a..72a454455 100644 --- a/framework/Vcs/lib/Horde/Vcs/Cvs.php +++ b/framework/Vcs/lib/Horde/Vcs/Cvs.php @@ -257,572 +257,3 @@ class Horde_Vcs_Cvs extends Horde_Vcs_Rcs } } - -/** - * Horde_Vcs_Cvs directory class. - * - * @author Anil Madhavapeddy - * @author Michael Slusarz - * @package Horde_Vcs - */ -class Horde_Vcs_Directory_Cvs extends Horde_Vcs_Directory -{ - /** - * Create a Directory object to store information about the files in a - * single directory in the repository - * - * @param Horde_Vcs $rep The Repository object this directory is part of. - * @param string $dn Path to the directory. - * @param array $opts TODO - * - * @throws Horde_Vcs_Exception - */ - public function __construct($rep, $dn, $opts = array()) - { - parent::__construct($rep, $dn, $opts); - $this->_dirName = $rep->sourceroot() . '/' . $dn; - - /* 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); - } - - /* Open the directory for reading its contents */ - if (!($DIR = @opendir($this->_dirName))) { - throw new Horde_Vcs_Exception(empty($php_errormsg) ? 'Permission denied' : $php_errormsg); - } - - /* Create two arrays - one of all the files, and the other of - * all the directories. */ - while (($name = readdir($DIR)) !== false) { - if (($name == '.') || ($name == '..')) { - continue; - } - - $path = $this->_dirName . '/' . $name; - if (@is_dir($path)) { - /* Skip Attic directory. */ - if ($name != 'Attic') { - $this->_dirs[] = $name; - } - } elseif (@is_file($path) && (substr($name, -2) == ',v')) { - /* Spawn a new file object to represent this file. */ - $this->_files[] = $rep->getFileObject(substr($path, strlen($rep->sourceroot()), -2), array('quicklog' => !empty($opts['quicklog']))); - } - } - - /* Close the filehandle; we've now got a list of dirs and files. */ - closedir($DIR); - - /* If we want to merge the attic, add it in here. */ - if (!empty($opts['showattic'])) { - try { - $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) {} - } - - return true; - } - - /** - * TODO - */ - public function getBranches() - { - return array('HEAD'); - } - -} - -/** - * Horde_Vcs_Cvs file class. - * - * @author Anil Madhavapeddy - * @author Michael Slusarz - * @package Horde_Vcs - */ -class Horde_Vcs_File_Cvs extends Horde_Vcs_File -{ - /** - * TODO - * - * @var string - */ - protected $_accum; - - /** - * TODO - * - * @var array - */ - protected $_revsym = array(); - - /** - * TODO - * - * @var array - */ - protected $_symrev = array(); - - /** - * TODO - * - * @var array - */ - protected $_revlist = array(); - - /** - * TODO - * - * @var array - */ - protected $_branches = array(); - - private $_initialized; - - protected function _init() - { - /* Check that we are actually in the filesystem. */ - $file = $this->_dir . '/' . $this->_name; - if (!is_file($file)) { - throw new Horde_Vcs_Exception('File Not Found: ' . $file); - } - - $ret_array = array(); - $cmd = escapeshellcmd($this->_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); - } - - $branches = array(); - $state = 'init'; - - foreach ($ret_array as $line) { - switch ($state) { - case 'init': - if (strpos($line, 'head: ') === 0) { - $this->_branches['HEAD'] = substr($line, 6); - $this->_revlist['HEAD'] = $this->_rep->getRevisionRange($this, '1.1', $this->_branches['HEAD']); - } elseif (strpos($line, 'branch:') === 0) { - $state = 'rev'; - } - break; - - case 'rev': - if (strpos($line, '----------') === 0) { - $state = 'info'; - } 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])) { - $rev = $regs[2]; - $end = strrpos($rev, '.'); - $rev[$end] = 0; - $branchRev = (($end2 = strrpos($rev, '.')) === false) - ? substr($rev, ++$end) - : substr_replace($rev, '.', $end2, ($end - $end2 + 1)); - - /* $branchRev is only the branching point, NOT the - * HEAD of the branch. To determine the HEAD, we need - * to parse all of the log data first. Yuck. */ - $branches[$regs[1]] = $branchRev . '.'; - } else { - $this->_symrev[$regs[1]] = $regs[2]; - if (empty($this->_revsym[$regs[2]])) { - $this->_revsym[$regs[2]] = array(); - } - $this->_revsym[$regs[2]][] = $regs[1]; - } - } - break; - - case 'info': - if ((strpos($line, '==============================') === false) && - (strpos($line, '----------------------------') === false)) { - $this->_accum[] = $line; - } elseif (count($this->_accum)) { - $log = $this->_rep->getLogObject($this, null); - $rev = $log->queryRevision(); - $onbranch = false; - $onhead = (substr_count($rev, '.') == 1); - - // Determine branch information. - if ($onhead) { - $onbranch = (empty($this->_branch) || $this->_branch == 'HEAD') || - ($this->_rep->cmp($branches[$this->_branch], $rev) === 1); - } elseif ($this->_branch != 'HEAD') { - foreach ($branches as $key => $val) { - if (strpos($rev, $val) === 0) { - $onbranch = true; - $log->setBranch($key); - if (!isset($this->_branches[$key])) { - $this->_branches[$key] = $rev; - $this->_revlist[$key] = $this->_rep->getRevisionRange($this, '1.1', $rev); - } - break; - } - } - } - - if ($onbranch) { - $this->_revs[] = $rev; - $this->logs[$rev] = $log; - } - - $this->_accum = array(); - } - break; - } - } - } - - protected function _ensureRevisionsInitialized() - { - if(!$this->_initialized) { - $this->_init(); - $this->_initialized = true; - } - } - - protected function _ensureLogsInitialized() - { - if(!$this->_initialized) { - $this->_init(); - $this->_initialized = true; - } - } - - /** - * If this file is present in an Attic directory, this indicates it. - * - * @return boolean True if file is in the Attic, and false otherwise - */ - public function isDeleted() - { - return (substr($this->_dir, -5) == 'Attic'); - } - - /** - * Returns name of the current file without the repository - * extensions (usually ,v) - * - * @return string Filename without repository extension - */ - public function queryName() - { - return preg_replace('/,v$/', '', $this->_name); - } - - /** - * Return the fully qualified filename of this object. - * - * @return Fully qualified filename of this object - */ - public function queryFullPath() - { - return parent::queryModulePath(); - } - - /** - * Return the name of this file relative to its sourceroot. - * - * @return string Pathname relative to the sourceroot. - */ - public function queryModulePath() - { - return preg_replace('|^'. $this->_rep->sourceroot() . '/?(.*),v$|', '\1', $this->queryFullPath()); - } - - /** - * TODO - */ - public function getBranchList() - { - return $this->_revlist(); - } - - /** - * TODO - */ - public function queryRevsym($rev) - { - return isset($this->_revsym[$rev]) - ? $this->_revsym[$rev] - : array(); - } - - /** - * TODO - */ - public function querySymbolicRevisions() - { - return $this->_symrev; - } - - /** - * TODO - */ - public function getAccum() - { - return $this->_accum; - } - - /** - * TODO - */ - public function queryBranches() - { - return $this->_branches; - } - -} - -/** - * 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. - * - * @var string - */ - protected $_branch; - - private $_initialized; - - protected function _init() - { - $raw = $this->_file->getAccum(); - - /* Initialise a simple state machine to parse the output of rlog */ - $state = 'init'; - while (!empty($raw) && $state != 'done') { - switch ($state) { - /* Found filename, now looking for the revision number */ - case 'init': - $line = array_shift($raw); - if (preg_match("/revision (.+)$/", $line, $parts)) { - $this->_rev = $parts[1]; - $state = 'date'; - } - break; - - /* Found revision and filename, now looking for date */ - 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] : ''; - $state = 'branches'; - } - break; - - /* Look for a branch point here - format is 'branches: - * x.y.z; a.b.c;' */ - case 'branches': - /* If we find a branch tag, process and pop it, - otherwise leave input stream untouched */ - if (!empty($raw) && - preg_match("/^branches:\s+(.*)/", $raw[0], $br)) { - /* Get the list of branches from the string, and - * 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; - } - } - array_shift($raw); - } - - $state = 'done'; - break; - } - } - - /* Assume the rest of the lines are the log message */ - $this->_log = implode("\n", $raw); - $this->_tags = $this->_file->queryRevsym($this->_rev); - } - - protected function _ensureInitialized() - { - if (!$this->_initialized) { - $this->_init(); - $this->_initialized = true; - } - } - - /** - * TODO - */ - public function setBranch($branch) - { - $this->_branch = array($branch); - } - - /** - * TODO - */ - public function queryBranch() - { - if (!empty($this->_branch)) { - return $this->_branch; - } - - $branches = $this->_file->queryBranches(); - $key = array_keys($branches, $this->_rev); - return empty($key) - ? array_keys($branches, $this->_rep->strip($this->_rev, 1)) - : $key; - } - -} - -/** - * Horde_Vcs_Cvs Patchset class. - * - * @author Anil Madhavapeddy - * @author Michael Slusarz - * @package Horde_Vcs - */ -class Horde_Vcs_Patchset_Cvs extends Horde_Vcs_Patchset -{ - /** - * Constructor - * - * @param Horde_Vcs $rep A Horde_Vcs repository object. - * @param string $file The filename to create a patchset for. - * @param array $opts Additional options. - *
-     * 'file' - (string) The filename to process.
-     *          REQUIRED for this driver.
-     * 'range' - (array) The patchsets to process.
-     *           DEFAULT: None (all patchsets are processed).
-     * 
- * - * @throws Horde_Vcs_Exception - */ - public function __construct($rep, $opts = array()) - { - $file = $rep->sourceroot() . '/' . $opts['file']; - - /* Check that we are actually in the filesystem. */ - if (!$rep->isFile($file)) { - throw new Horde_Vcs_Exception('File Not Found'); - } - - /* Call cvsps to retrieve all patchsets for this file. */ - $cvsps_home = $rep->getPath('cvsps_home'); - $HOME = !empty($cvsps_home) ? - 'HOME=' . escapeshellarg($cvsps_home) . ' ' : - ''; - - $rangecmd = empty($opts['range']) - ? '' - : ' -s ' . escapeshellarg(implode(',', $opts['range'])); - - $ret_array = array(); - $cmd = $HOME . escapeshellcmd($rep->getPath('cvsps')) . $rangecmd . ' -u --cvs-direct --root ' . escapeshellarg($rep->sourceroot()) . ' -f ' . escapeshellarg(basename($file)) . ' ' . escapeshellarg(dirname($file)); - exec($cmd, $ret_array, $retval); - if ($retval) { - throw new Horde_Vcs_Exception('Failed to spawn cvsps to retrieve patchset information.'); - } - - $state = 'begin'; - reset($ret_array); - while (list(,$line) = each($ret_array)) { - $line = trim($line); - - if ($line == '---------------------') { - $state = 'begin'; - continue; - } - - switch ($state) { - case 'begin': - $id = str_replace('PatchSet ', '', $line); - $this->_patchsets[$id] = array(); - $state = 'info'; - break; - - case 'info': - $info = explode(':', $line, 2); - $info[1] = ltrim($info[1]); - - switch ($info[0]) { - case 'Date': - $d = new DateTime($info[1]); - $this->_patchsets[$id]['date'] = $d->format('U'); - break; - - case 'Author': - $this->_patchsets[$id]['author'] = $info[1]; - break; - - case 'Branch': - $this->_patchsets[$id]['branches'] = ($info[1] == 'HEAD') - ? array() - : array($info[1]); - break; - - case 'Tag': - $this->_patchsets[$id]['tags'] = ($info[1] == '(none)') - ? array() - : array($info[1]); - break; - - case 'Log': - $state = 'log'; - $this->_patchsets[$id]['log'] = ''; - break; - } - break; - - case 'log': - if ($line == 'Members:') { - $state = 'members'; - $this->_patchsets[$id]['log'] = rtrim($this->_patchsets[$id]['log']); - $this->_patchsets[$id]['members'] = array(); - } else { - $this->_patchsets[$id]['log'] .= $line . "\n"; - } - break; - - case 'members': - if (!empty($line)) { - $parts = explode(':', $line); - list($from, $to) = explode('->', $parts[1], 2); - $status = self::MODIFIED; - - if ($from == 'INITIAL') { - $from = null; - $status = self::ADDED; - } elseif (substr($to, -6) == '(DEAD)') { - $to = null; - $status = self::DELETED; - } - - $this->_patchsets[$id]['members'][] = array( - 'file' => $parts[0], - 'from' => $from, - 'status' => $status, - 'to' => $to - ); - } - break; - } - } - } - -} diff --git a/framework/Vcs/lib/Horde/Vcs/Directory.php b/framework/Vcs/lib/Horde/Vcs/Directory.php new file mode 100644 index 000000000..8a0d696a7 --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/Directory.php @@ -0,0 +1,202 @@ +_rep = $rep; + $this->_moduleName = $dn; + $this->_dirName = '/' . $dn; + } + + /** + * Return fully qualified pathname to this directory with no trailing /. + * + * @return string Pathname of this directory. + */ + public function queryDir() + { + return $this->_dirName; + } + + /** + * TODO + */ + public function queryDirList() + { + return $this->_dirs; + } + + /** + * TODO + */ + public function queryFileList($showattic = false) + { + return ($showattic && isset($this->_mergedFiles)) + ? $this->_mergedFiles + : $this->_files; + } + + /** + * Sort the contents of the directory in a given fashion and + * order. + * + * @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 Horde_Vcs::SORT_[*] where * can be: + * ASCENDING, DESCENDING for the order of the sort. + */ + public function applySort($how = Horde_Vcs::SORT_NONE, + $dir = Horde_Vcs::SORT_ASCENDING) + { + // Always sort directories by name. + natcasesort($this->_dirs); + + $this->_doFileSort($this->_files, $how); + + if (isset($this->_atticFiles)) { + $this->_doFileSort($this->_atticFiles, $how); + } + + if (isset($this->_mergedFiles)) { + $this->_doFileSort($this->_mergedFiles, $how); + } + + if ($dir == Horde_Vcs::SORT_DESCENDING) { + $this->_dirs = array_reverse($this->_dirs); + $this->_files = array_reverse($this->_files); + if (isset($this->_mergedFiles)) { + $this->_mergedFiles = array_reverse($this->_mergedFiles); + } + } + } + + /** + * TODO + */ + protected function _doFileSort(&$fileList, $how = Horde_Vcs::SORT_NONE) + { + switch ($how) { + case Horde_Vcs::SORT_AGE: + usort($fileList, array($this, '_fileAgeSort')); + break; + + case Horde_Vcs::SORT_NAME: + usort($fileList, array($this, '_fileNameSort')); + break; + + case Horde_Vcs::SORT_AUTHOR: + usort($fileList, array($this, '_fileAuthorSort')); + break; + + case Horde_Vcs::SORT_REV: + usort($fileList, array($this, '_fileRevSort')); + break; + + case Horde_Vcs::SORT_NONE: + default: + break; + } + } + + /** + * Sort function for ascending age. + */ + public function _fileAgeSort($a, $b) + { + $aa = $a->queryLastLog(); + $bb = $b->queryLastLog(); + return ($aa->queryDate() == $bb->queryDate()) + ? 0 + : (($aa->queryDate() < $bb->queryDate()) ? 1 : -1); + } + + /** + * Sort function by author name. + */ + public function _fileAuthorSort($a, $b) + { + $aa = $a->queryLastLog(); + $bb = $b->queryLastLog(); + return ($aa->queryAuthor() == $bb->queryAuthor()) + ? 0 + : (($aa->queryAuthor() > $bb->queryAuthor()) ? 1 : -1); + } + + /** + * Sort function for ascending filename. + */ + public function _fileNameSort($a, $b) + { + return strcasecmp($a->queryName(), $b->queryName()); + } + + /** + * Sort function for ascending revision. + */ + public function _fileRevSort($a, $b) + { + return $this->_rep->cmp($a->queryRevision(), $b->queryRevision()); + } + + /** + * TODO + */ + public function getBranches() + { + return array(); + } +} diff --git a/framework/Vcs/lib/Horde/Vcs/Directory/Cvs.php b/framework/Vcs/lib/Horde/Vcs/Directory/Cvs.php new file mode 100644 index 000000000..acabe9a48 --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/Directory/Cvs.php @@ -0,0 +1,83 @@ + + * @author Michael Slusarz + * @package Horde_Vcs + */ +class Horde_Vcs_Directory_Cvs extends Horde_Vcs_Directory_Base +{ + /** + * Create a Directory object to store information about the files in a + * single directory in the repository + * + * @param Horde_Vcs $rep The Repository object this directory is part of. + * @param string $dn Path to the directory. + * @param array $opts TODO + * + * @throws Horde_Vcs_Exception + */ + public function __construct($rep, $dn, $opts = array()) + { + parent::__construct($rep, $dn, $opts); + $this->_dirName = $rep->sourceroot() . '/' . $dn; + + /* 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); + } + + /* Open the directory for reading its contents */ + if (!($DIR = @opendir($this->_dirName))) { + throw new Horde_Vcs_Exception(empty($php_errormsg) ? 'Permission denied' : $php_errormsg); + } + + /* Create two arrays - one of all the files, and the other of + * all the directories. */ + while (($name = readdir($DIR)) !== false) { + if (($name == '.') || ($name == '..')) { + continue; + } + + $path = $this->_dirName . '/' . $name; + if (@is_dir($path)) { + /* Skip Attic directory. */ + if ($name != 'Attic') { + $this->_dirs[] = $name; + } + } elseif (@is_file($path) && (substr($name, -2) == ',v')) { + /* Spawn a new file object to represent this file. */ + $this->_files[] = $rep->getFileObject(substr($path, strlen($rep->sourceroot()), -2), array('quicklog' => !empty($opts['quicklog']))); + } + } + + /* Close the filehandle; we've now got a list of dirs and files. */ + closedir($DIR); + + /* If we want to merge the attic, add it in here. */ + if (!empty($opts['showattic'])) { + try { + $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) {} + } + + return true; + } + + /** + * TODO + */ + public function getBranches() + { + return array('HEAD'); + } + +} diff --git a/framework/Vcs/lib/Horde/Vcs/Directory/Git.php b/framework/Vcs/lib/Horde/Vcs/Directory/Git.php new file mode 100644 index 000000000..92014138f --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/Directory/Git.php @@ -0,0 +1,89 @@ + + * @author Michael Slusarz + * @package Horde_Vcs + */ +class Horde_Vcs_Directory_Git extends Horde_Vcs_Directory +{ + /** + * The current branch. + * + * @var string + */ + protected $_branch; + + /** + * Create a Directory object to store information about the files in a + * single directory in the repository. + * + * @param Horde_Vcs $rep The Repository object this directory is part of. + * @param string $dn Path to the directory. + * @param array $opts TODO + * + * @throws Horde_Vcs_Exception + */ + public function __construct($rep, $dn, $opts = array()) + { + parent::__construct($rep, $dn, $opts); + + $this->_branch = empty($opts['rev']) + ? $rep->getDefaultBranch() + : $opts['rev']; + + // @TODO See if we have a valid cache of the tree at this revision + + $dir = $this->queryDir(); + if (substr($dir, 0, 1) == '/') { + $dir = (string)substr($dir, 1); + } + if (strlen($dir) && substr($dir, -1) != '/') { + $dir .= '/'; + } + + $cmd = $rep->getCommand() . ' ls-tree --full-name ' . escapeshellarg($this->_branch) . ' ' . escapeshellarg($dir) . ' 2>&1'; + $stream = popen($cmd, 'r'); + if (!$stream) { + throw new Horde_Vcs_Exception('Failed to execute git ls-tree: ' . $cmd); + } + + // Create two arrays - one of all the files, and the other of + // all the dirs. + while (!feof($stream)) { + $line = fgets($stream); + if ($line === false) { break; } + + $line = rtrim($line); + if (!strlen($line)) { continue; } + + list(, $type, , $file) = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY); + if ($type == 'tree') { + $this->_dirs[] = basename($file); + } else { + $this->_files[] = $rep->getFileObject($file, array('branch' => $this->_branch, 'quicklog' => !empty($opts['quicklog']))); + } + } + + pclose($stream); + } + + /** + * TODO + */ + public function getBranches() + { + $blist = array_keys($this->_rep->getBranchList()); + if (!in_array($this->_branch, $blist)) { + $blist[] = $this->_branch; + } + return $blist; + } + +} \ No newline at end of file diff --git a/framework/Vcs/lib/Horde/Vcs/File.php b/framework/Vcs/lib/Horde/Vcs/File.php new file mode 100644 index 000000000..5e918a2bf --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/File.php @@ -0,0 +1,286 @@ +_name = basename($filename); + $this->_dir = dirname($filename); + + $this->_quicklog = !empty($opts['quicklog']); + if (!empty($opts['branch'])) { + $this->_branch = $opts['branch']; + } + } + + protected function _ensureRevisionsInitialized() + { + } + + protected function _ensureLogsInitialized() + { + } + + /** + * When serializing, don't return the repository object + */ + public function __sleep() + { + return array_diff(array_keys(get_object_vars($this)), array('_rep')); + } + + /** + * TODO + */ + public function setRepository($rep) + { + $this->_rep = $rep; + } + + /** + * TODO - better name, wrap an object around this? + */ + public function getBlob($revision) + { + return $this->_rep->checkout($this->queryPath(), $revision); + } + + /** + * Has the file been deleted? + * + * @return boolean Is this file deleted? + */ + public function isDeleted() + { + return false; + } + + /** + * Returns name of the current file without the repository extensions. + * + * @return string Filename without repository extension + */ + function queryName() + { + return $this->_name; + } + + /** + * Returns the name of the current file as in the repository. + * + * @return string Filename (without the path). + */ + public function queryRepositoryName() + { + return $this->_name; + } + + /** + * Return the last revision of the current file on the HEAD branch. + * + * @return string Last revision of the current file. + * @throws Horde_Vcs_Exception + */ + public function queryRevision() + { + $this->_ensureRevisionsInitialized(); + if (!isset($this->_revs[0])) { + throw new Horde_Vcs_Exception('No revisions'); + } + return $this->_revs[0]; + } + + /** + * TODO + */ + public function queryPreviousRevision($rev) + { + $this->_ensureRevisionsInitialized(); + $key = array_search($rev, $this->_revs); + return (($key !== false) && isset($this->_revs[$key + 1])) + ? $this->_revs[$key + 1] + : null; + } + + /** + * Return the last Horde_Vcs_Log object in the file. + * + * @return Horde_Vcs_Log Log object of the last entry in the file. + * @throws Horde_Vcs_Exception + */ + public function queryLastLog() + { + $this->_ensureRevisionsInitialized(); + $this->_ensureLogsInitialized(); + if (!isset($this->_revs[0]) || !isset($this->logs[$this->_revs[0]])) { + throw new Horde_Vcs_Exception('No revisions'); + } + return $this->logs[$this->_revs[0]]; + } + + /** + * Sort the list of Horde_Vcs_Log objects that this file contains. + * + * @param integer $how Horde_Vcs::SORT_REV (sort by revision), + * Horde_Vcs::SORT_NAME (sort by author name), or + * Horde_Vcs::SORT_AGE (sort by commit date). + */ + public function applySort($how = Horde_Vcs::SORT_REV) + { + $this->_ensureLogsInitialized(); + + switch ($how) { + case Horde_Vcs::SORT_NAME: + $func = 'Name'; + break; + + case Horde_Vcs::SORT_AGE: + $func = 'Age'; + break; + + case Horde_Vcs::SORT_REV: + default: + $func = 'Revision'; + break; + } + + uasort($this->logs, array($this, 'sortBy' . $func)); + return true; + } + + /** + * The sortBy*() functions are internally used by applySort. + */ + public function sortByRevision($a, $b) + { + return $this->_rep->cmp($b->queryRevision(), $a->queryRevision()); + } + + public function sortByAge($a, $b) + { + return $b->queryDate() - $a->queryDate(); + } + + public function sortByName($a, $b) + { + return strcmp($a->queryAuthor(), $b->queryAuthor()); + } + + /** + * Return the fully qualified filename of this object. + * + * @return string Fully qualified filename of this object. + */ + public function queryFullPath() + { + return $this->_rep->sourceroot() . '/' . $this->queryModulePath(); + } + + /** + * Return the filename relative to its sourceroot. + * + * @return string Pathname relative to the sourceroot. + */ + public function queryModulePath() + { + 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 array(); + } + + /** + * TODO + */ + public function queryLogs($rev = null) + { + $this->_ensureLogsInitialized(); + return is_null($rev) + ? $this->logs + : (isset($this->logs[$rev]) ? $this->logs[$rev] : null); + } + + /** + * TODO + */ + public function revisionCount() + { + $this->_ensureRevisionsInitialized(); + return count($this->_revs); + } + + /** + * TODO + */ + public function querySymbolicRevisions() + { + return array(); + } +} diff --git a/framework/Vcs/lib/Horde/Vcs/File/Cvs.php b/framework/Vcs/lib/Horde/Vcs/File/Cvs.php new file mode 100644 index 000000000..0996a42eb --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/File/Cvs.php @@ -0,0 +1,249 @@ + + * @author Michael Slusarz + * @package Horde_Vcs + */ +class Horde_Vcs_File_Cvs extends Horde_Vcs_File +{ + /** + * TODO + * + * @var string + */ + protected $_accum; + + /** + * TODO + * + * @var array + */ + protected $_revsym = array(); + + /** + * TODO + * + * @var array + */ + protected $_symrev = array(); + + /** + * TODO + * + * @var array + */ + protected $_revlist = array(); + + /** + * TODO + * + * @var array + */ + protected $_branches = array(); + + private $_initialized; + + protected function _init() + { + /* Check that we are actually in the filesystem. */ + $file = $this->_dir . '/' . $this->_name; + if (!is_file($file)) { + throw new Horde_Vcs_Exception('File Not Found: ' . $file); + } + + $ret_array = array(); + $cmd = escapeshellcmd($this->_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); + } + + $branches = array(); + $state = 'init'; + + foreach ($ret_array as $line) { + switch ($state) { + case 'init': + if (strpos($line, 'head: ') === 0) { + $this->_branches['HEAD'] = substr($line, 6); + $this->_revlist['HEAD'] = $this->_rep->getRevisionRange($this, '1.1', $this->_branches['HEAD']); + } elseif (strpos($line, 'branch:') === 0) { + $state = 'rev'; + } + break; + + case 'rev': + if (strpos($line, '----------') === 0) { + $state = 'info'; + } 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])) { + $rev = $regs[2]; + $end = strrpos($rev, '.'); + $rev[$end] = 0; + $branchRev = (($end2 = strrpos($rev, '.')) === false) + ? substr($rev, ++$end) + : substr_replace($rev, '.', $end2, ($end - $end2 + 1)); + + /* $branchRev is only the branching point, NOT the + * HEAD of the branch. To determine the HEAD, we need + * to parse all of the log data first. Yuck. */ + $branches[$regs[1]] = $branchRev . '.'; + } else { + $this->_symrev[$regs[1]] = $regs[2]; + if (empty($this->_revsym[$regs[2]])) { + $this->_revsym[$regs[2]] = array(); + } + $this->_revsym[$regs[2]][] = $regs[1]; + } + } + break; + + case 'info': + if ((strpos($line, '==============================') === false) && + (strpos($line, '----------------------------') === false)) { + $this->_accum[] = $line; + } elseif (count($this->_accum)) { + $log = $this->_rep->getLogObject($this, null); + $rev = $log->queryRevision(); + $onbranch = false; + $onhead = (substr_count($rev, '.') == 1); + + // Determine branch information. + if ($onhead) { + $onbranch = (empty($this->_branch) || $this->_branch == 'HEAD') || + ($this->_rep->cmp($branches[$this->_branch], $rev) === 1); + } elseif ($this->_branch != 'HEAD') { + foreach ($branches as $key => $val) { + if (strpos($rev, $val) === 0) { + $onbranch = true; + $log->setBranch($key); + if (!isset($this->_branches[$key])) { + $this->_branches[$key] = $rev; + $this->_revlist[$key] = $this->_rep->getRevisionRange($this, '1.1', $rev); + } + break; + } + } + } + + if ($onbranch) { + $this->_revs[] = $rev; + $this->logs[$rev] = $log; + } + + $this->_accum = array(); + } + break; + } + } + } + + protected function _ensureRevisionsInitialized() + { + if(!$this->_initialized) { + $this->_init(); + $this->_initialized = true; + } + } + + protected function _ensureLogsInitialized() + { + if(!$this->_initialized) { + $this->_init(); + $this->_initialized = true; + } + } + + /** + * If this file is present in an Attic directory, this indicates it. + * + * @return boolean True if file is in the Attic, and false otherwise + */ + public function isDeleted() + { + return (substr($this->_dir, -5) == 'Attic'); + } + + /** + * Returns name of the current file without the repository + * extensions (usually ,v) + * + * @return string Filename without repository extension + */ + public function queryName() + { + return preg_replace('/,v$/', '', $this->_name); + } + + /** + * Return the fully qualified filename of this object. + * + * @return Fully qualified filename of this object + */ + public function queryFullPath() + { + return parent::queryModulePath(); + } + + /** + * Return the name of this file relative to its sourceroot. + * + * @return string Pathname relative to the sourceroot. + */ + public function queryModulePath() + { + return preg_replace('|^'. $this->_rep->sourceroot() . '/?(.*),v$|', '\1', $this->queryFullPath()); + } + + /** + * TODO + */ + public function getBranchList() + { + return $this->_revlist(); + } + + /** + * TODO + */ + public function queryRevsym($rev) + { + return isset($this->_revsym[$rev]) + ? $this->_revsym[$rev] + : array(); + } + + /** + * TODO + */ + public function querySymbolicRevisions() + { + return $this->_symrev; + } + + /** + * TODO + */ + public function getAccum() + { + return $this->_accum; + } + + /** + * TODO + */ + public function queryBranches() + { + return $this->_branches; + } + +} \ No newline at end of file diff --git a/framework/Vcs/lib/Horde/Vcs/File/Git.php b/framework/Vcs/lib/Horde/Vcs/File/Git.php new file mode 100644 index 000000000..9eb46c0cb --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/File/Git.php @@ -0,0 +1,222 @@ + + * @author Michael Slusarz + * @package Horde_Vcs + */ +class Horde_Vcs_File_Git extends Horde_Vcs_File +{ + /** + * The master list of revisions for this file. + * + * @var array + */ + protected $_revlist = array(); + + /** + * Have we initalized logs and revisions? + * + * @var boolean + */ + private $_initialized = false; + + protected function _ensureRevisionsInitialized() + { + if (!$this->_initialized) { $this->_init(); } + $this->_initialized = true; + } + + protected function _ensureLogsInitialized() + { + if (!$this->_initialized) { $this->_init(); } + $this->_initialized = true; + } + + protected function _init() + { + $log_list = null; + + /* First, grab the master list of revisions. If quicklog is specified, + * we don't need this master list - we are only concerned about the + * most recent revision for the given branch. */ + if ($this->_quicklog) { + $branchlist = empty($this->_branch) + ? array($this->_rep->getDefaultBranch()) + : array($this->_branch); + } else { + if (version_compare($this->_rep->version, '1.6.0', '>=')) { + $cmd = $this->_rep->getCommand() . ' rev-list --branches -- ' . escapeshellarg($this->queryModulePath()) . ' 2>&1'; + } else { + $cmd = $this->_rep->getCommand() . ' branch -v --no-abbrev'; + exec($cmd, $branch_heads); + if (stripos($branch_heads[0], 'fatal') === 0) { + throw new Horde_Vcs_Exception(implode(', ', $branch_heads)); + } + foreach ($branch_heads as &$hd) { + $line = explode(' ', substr($hd, 2)); + $hd = $line[1]; + } + + $cmd = $this->_rep->getCommand() . ' rev-list ' . implode(' ', $branch_heads) . ' -- ' . escapeshellarg($this->queryModulePath()) . ' 2>&1'; + } + + exec($cmd, $revs); + if (count($revs) == 0) { + if (!$this->_rep->isFile($this->queryModulePath(), isset($opts['branch']) ? $opts['branch'] : null)) { + throw new Horde_Vcs_Exception('No such file: ' . $this->queryModulePath()); + } else { + throw new Horde_Vcs_Exception('No revisions found'); + } + } + + if (stripos($revs[0], 'fatal') === 0) { + throw new Horde_Vcs_Exception(implode(', ', $revs)); + } + + $this->_revs = $revs; + + $branchlist = array_keys($this->queryBranches()); + } + + /* Get the list of revisions. Need to get all revisions, not just + * those on $this->_branch, for branch determination reasons. */ + foreach ($branchlist as $key) { + $revs = array(); + $cmd = $this->_rep->getCommand() . ' rev-list ' . ($this->_quicklog ? '-n 1' : '') . ' ' . escapeshellarg($key) . ' -- ' . escapeshellarg($this->queryModulePath()) . ' 2>&1'; + exec($cmd, $revs); + + if (!empty($revs)) { + if (stripos($revs[0], 'fatal') === 0) { + throw new Horde_Vcs_Exception(implode(', ', $revs)); + } + + $this->_revlist[$key] = $revs; + + if (!empty($this->_branch) && ($this->_branch == $key)) { + $log_list = $revs; + } + + if ($this->_quicklog) { + $this->_revs[] = reset($revs); + } + } + } + + if (is_null($log_list)) { + $log_list = ($this->_quicklog || empty($this->_branch)) + ? $this->_revs + : array(); + } + + foreach ($log_list as $val) { + $this->logs[$val] = $this->_rep->getLogObject($this, $val); + } + } + + /** + * Get the hash name for this file at a specific revision. + * + * @param string $rev Revision string. + * + * @return string Commit hash. + */ + public function getHashForRevision($rev) + { + if (!isset($this->logs[$rev])) { + throw new Horde_Vcs_Exception('This file doesn\'t exist at that revision'); + } + return $this->logs[$rev]->getHashForPath($this->queryModulePath()); + } + + /** + * Return the name of this file relative to its sourceroot. + * + * @return string Pathname relative to the sourceroot. + */ + public function queryModulePath() + { + return ($this->_dir == '.') + ? $this->_name + : parent::queryModulePath(); + } + + /** + * TODO + */ + public function getBranchList() + { + return $this->_revlist; + } + + /** + * TODO + */ + public function queryBranch($rev) + { + $branches = array(); + + foreach (array_keys($this->_revlist) as $val) { + if (array_search($rev, $this->_revlist[$val]) !== false) { + $branches[] = $val; + } + } + + return $branches; + } + + /** + * 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(); + } + + /** + * TODO + */ + public function queryBranches() + { + /* If dealing with a branch that is not explicitly named (i.e. an + * implicit branch for a given tree-ish commit ID), we need to add + * that information to the branch list. */ + $revlist = $this->_rep->getBranchList(); + if (!empty($this->_branch) && + !in_array($this->_branch, $revlist)) { + $revlist[$this->_branch] = $this->_branch; + } + return $revlist; + } + + /** + * Return the last Horde_Vcs_Log object in the file. + * + * @return Horde_Vcs_Log Log object of the last entry in the file. + * @throws Horde_Vcs_Exception + */ + public function queryLastLog() + { + if (empty($this->_branch)) { + return parent::queryLastLog(); + } + + $rev = reset($this->_revlist[$this->_branch]); + if (!is_null($rev)) { + if (isset($this->logs[$rev])) { + return $this->logs[$rev]; + } + } + + throw new Horde_Vcs_Exception('No revisions'); + } +} \ No newline at end of file diff --git a/framework/Vcs/lib/Horde/Vcs/Git.php b/framework/Vcs/lib/Horde/Vcs/Git.php index 1930e3e0f..7a3c2ecbb 100644 --- a/framework/Vcs/lib/Horde/Vcs/Git.php +++ b/framework/Vcs/lib/Horde/Vcs/Git.php @@ -313,553 +313,4 @@ class Horde_Vcs_Git extends Horde_Vcs return 'master'; } -} - -/** - * Horde_Vcs_Git directory class. - * - * @author Chuck Hagenbuch - * @author Michael Slusarz - * @package Horde_Vcs - */ -class Horde_Vcs_Directory_Git extends Horde_Vcs_Directory -{ - /** - * The current branch. - * - * @var string - */ - protected $_branch; - - /** - * Create a Directory object to store information about the files in a - * single directory in the repository. - * - * @param Horde_Vcs $rep The Repository object this directory is part of. - * @param string $dn Path to the directory. - * @param array $opts TODO - * - * @throws Horde_Vcs_Exception - */ - public function __construct($rep, $dn, $opts = array()) - { - parent::__construct($rep, $dn, $opts); - - $this->_branch = empty($opts['rev']) - ? $rep->getDefaultBranch() - : $opts['rev']; - - // @TODO See if we have a valid cache of the tree at this revision - - $dir = $this->queryDir(); - if (substr($dir, 0, 1) == '/') { - $dir = (string)substr($dir, 1); - } - if (strlen($dir) && substr($dir, -1) != '/') { - $dir .= '/'; - } - - $cmd = $rep->getCommand() . ' ls-tree --full-name ' . escapeshellarg($this->_branch) . ' ' . escapeshellarg($dir) . ' 2>&1'; - $stream = popen($cmd, 'r'); - if (!$stream) { - throw new Horde_Vcs_Exception('Failed to execute git ls-tree: ' . $cmd); - } - - // Create two arrays - one of all the files, and the other of - // all the dirs. - while (!feof($stream)) { - $line = fgets($stream); - if ($line === false) { break; } - - $line = rtrim($line); - if (!strlen($line)) { continue; } - - list(, $type, , $file) = preg_split('/\s+/', $line, -1, PREG_SPLIT_NO_EMPTY); - if ($type == 'tree') { - $this->_dirs[] = basename($file); - } else { - $this->_files[] = $rep->getFileObject($file, array('branch' => $this->_branch, 'quicklog' => !empty($opts['quicklog']))); - } - } - - pclose($stream); - } - - /** - * TODO - */ - public function getBranches() - { - $blist = array_keys($this->_rep->getBranchList()); - if (!in_array($this->_branch, $blist)) { - $blist[] = $this->_branch; - } - return $blist; - } - -} - -/** - * Horde_Vcs_Git file class. - * - * @author Chuck Hagenbuch - * @author Michael Slusarz - * @package Horde_Vcs - */ -class Horde_Vcs_File_Git extends Horde_Vcs_File -{ - /** - * The master list of revisions for this file. - * - * @var array - */ - protected $_revlist = array(); - - /** - * Have we initalized logs and revisions? - * - * @var boolean - */ - private $_initialized = false; - - protected function _ensureRevisionsInitialized() - { - if (!$this->_initialized) { $this->_init(); } - $this->_initialized = true; - } - - protected function _ensureLogsInitialized() - { - if (!$this->_initialized) { $this->_init(); } - $this->_initialized = true; - } - - protected function _init() - { - $log_list = null; - - /* First, grab the master list of revisions. If quicklog is specified, - * we don't need this master list - we are only concerned about the - * most recent revision for the given branch. */ - if ($this->_quicklog) { - $branchlist = empty($this->_branch) - ? array($this->_rep->getDefaultBranch()) - : array($this->_branch); - } else { - if (version_compare($this->_rep->version, '1.6.0', '>=')) { - $cmd = $this->_rep->getCommand() . ' rev-list --branches -- ' . escapeshellarg($this->queryModulePath()) . ' 2>&1'; - } else { - $cmd = $this->_rep->getCommand() . ' branch -v --no-abbrev'; - exec($cmd, $branch_heads); - if (stripos($branch_heads[0], 'fatal') === 0) { - throw new Horde_Vcs_Exception(implode(', ', $branch_heads)); - } - foreach ($branch_heads as &$hd) { - $line = explode(' ', substr($hd, 2)); - $hd = $line[1]; - } - - $cmd = $this->_rep->getCommand() . ' rev-list ' . implode(' ', $branch_heads) . ' -- ' . escapeshellarg($this->queryModulePath()) . ' 2>&1'; - } - - exec($cmd, $revs); - if (count($revs) == 0) { - if (!$this->_rep->isFile($this->queryModulePath(), isset($opts['branch']) ? $opts['branch'] : null)) { - throw new Horde_Vcs_Exception('No such file: ' . $this->queryModulePath()); - } else { - throw new Horde_Vcs_Exception('No revisions found'); - } - } - - if (stripos($revs[0], 'fatal') === 0) { - throw new Horde_Vcs_Exception(implode(', ', $revs)); - } - - $this->_revs = $revs; - - $branchlist = array_keys($this->queryBranches()); - } - - /* Get the list of revisions. Need to get all revisions, not just - * those on $this->_branch, for branch determination reasons. */ - foreach ($branchlist as $key) { - $revs = array(); - $cmd = $this->_rep->getCommand() . ' rev-list ' . ($this->_quicklog ? '-n 1' : '') . ' ' . escapeshellarg($key) . ' -- ' . escapeshellarg($this->queryModulePath()) . ' 2>&1'; - exec($cmd, $revs); - - if (!empty($revs)) { - if (stripos($revs[0], 'fatal') === 0) { - throw new Horde_Vcs_Exception(implode(', ', $revs)); - } - - $this->_revlist[$key] = $revs; - - if (!empty($this->_branch) && ($this->_branch == $key)) { - $log_list = $revs; - } - - if ($this->_quicklog) { - $this->_revs[] = reset($revs); - } - } - } - - if (is_null($log_list)) { - $log_list = ($this->_quicklog || empty($this->_branch)) - ? $this->_revs - : array(); - } - - foreach ($log_list as $val) { - $this->logs[$val] = $this->_rep->getLogObject($this, $val); - } - } - - /** - * Get the hash name for this file at a specific revision. - * - * @param string $rev Revision string. - * - * @return string Commit hash. - */ - public function getHashForRevision($rev) - { - if (!isset($this->logs[$rev])) { - throw new Horde_Vcs_Exception('This file doesn\'t exist at that revision'); - } - return $this->logs[$rev]->getHashForPath($this->queryModulePath()); - } - - /** - * Return the name of this file relative to its sourceroot. - * - * @return string Pathname relative to the sourceroot. - */ - public function queryModulePath() - { - return ($this->_dir == '.') - ? $this->_name - : parent::queryModulePath(); - } - - /** - * TODO - */ - public function getBranchList() - { - return $this->_revlist; - } - - /** - * TODO - */ - public function queryBranch($rev) - { - $branches = array(); - - foreach (array_keys($this->_revlist) as $val) { - if (array_search($rev, $this->_revlist[$val]) !== false) { - $branches[] = $val; - } - } - - return $branches; - } - - /** - * 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(); - } - - /** - * TODO - */ - public function queryBranches() - { - /* If dealing with a branch that is not explicitly named (i.e. an - * implicit branch for a given tree-ish commit ID), we need to add - * that information to the branch list. */ - $revlist = $this->_rep->getBranchList(); - if (!empty($this->_branch) && - !in_array($this->_branch, $revlist)) { - $revlist[$this->_branch] = $this->_branch; - } - return $revlist; - } - - /** - * Return the last Horde_Vcs_Log object in the file. - * - * @return Horde_Vcs_Log Log object of the last entry in the file. - * @throws Horde_Vcs_Exception - */ - public function queryLastLog() - { - if (empty($this->_branch)) { - return parent::queryLastLog(); - } - - $rev = reset($this->_revlist[$this->_branch]); - if (!is_null($rev)) { - if (isset($this->logs[$rev])) { - return $this->logs[$rev]; - } - } - - throw new Horde_Vcs_Exception('No revisions'); - } -} - -/** - * Horde_Vcs_Git log class. - * - * @author Chuck Hagenbuch - * @author Michael Slusarz - * @package Horde_Vcs - */ -class Horde_Vcs_Log_Git extends Horde_Vcs_Log -{ - /** - * @var string - */ - protected $_parent = null; - - /** - * @var boolean - */ - private $_initialized; - - protected function _ensureInitialized() - { - if (!$this->_initialized) { - $this->_init(); - $this->_initialized = true; - } - } - - protected function _init() - { - /* Get diff statistics. */ - $stats = array(); - $cmd = $this->_rep->getCommand() . ' diff-tree --numstat ' . escapeshellarg($this->_rev); - exec($cmd, $output); - - reset($output); - // Skip the first entry (it is the revision number) - next($output); - while (list(,$v) = each($output)) { - $tmp = explode("\t", $v); - $stats[$tmp[2]] = array_slice($tmp, 0, 2); - } - - // @TODO use Commit, CommitDate, and Merge properties - $cmd = $this->_rep->getCommand() . ' whatchanged --no-color --pretty=format:"Rev:%H%nParents:%P%nAuthor:%an <%ae>%nAuthorDate:%at%nRefs:%d%n%n%s%n%b" --no-abbrev -n 1 ' . escapeshellarg($this->_rev); - $pipe = popen($cmd, 'r'); - if (!is_resource($pipe)) { - throw new Horde_Vcs_Exception('Unable to run ' . $cmd . ': ' . error_get_last()); - } - - while (true) { - $line = trim(fgets($pipe)); - if (!strlen($line)) { break; } - if (strpos($line, ':') === false) { - throw new Horde_Vcs_Exception('Malformed log line: ' . $line); - } - - list($key, $value) = explode(':', $line, 2); - $value = trim($value); - - switch (trim($key)) { - case 'Rev': - if ($this->_rev != $value) { - fclose($pipe); - throw new Horde_Vcs_Exception('Expected ' . $this->_rev . ', got ' . $value); - } - break; - - case 'Parents': - // @TODO: More than 1 parent? - $this->_parent = $value; - break; - - case 'Author': - $this->_author = $value; - break; - - case 'AuthorDate': - $this->_date = $value; - break; - - case 'Refs': - if ($value) { - $value = substr($value, 1, -1); - foreach (explode(',', $value) as $val) { - $val = trim($val); - if (strpos($val, 'refs/tags/') === 0) { - $this->_tags[] = substr($val, 10); - } - } - if (!empty($this->_tags)) { - sort($this->_tags); - } - } - break; - } - } - - $log = ''; - $line = fgets($pipe); - while ($line !== false && substr($line, 0, 1) != ':') { - $log .= $line; - $line = fgets($pipe); - } - $this->_log = trim($log); - - // Build list of files in this revision. The format of these lines is - // documented in the git diff-tree documentation: - // 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); - - $statinfo = isset($stats[$matches[6]]) - ? array('added' => $stats[$matches[6]][0], 'deleted' => $stats[$matches[6]][1]) - : array(); - - $this->_files[$matches[6]] = array_merge(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] : '' - ), $statinfo); - - $line = fgets($pipe); - } - - fclose($pipe); - } - - /** - * TODO - */ - public function getHashForPath($path) - { - $this->_ensureInitialized(); - return $this->_files[$path]['dstSha1']; - } - - /** - * TODO - */ - public function queryBranch() - { - return $this->_file->queryBranch($this->_rev); - } - - /** - * TODO - */ - public function queryParent() - { - return $this->_parent; - } - -} - -/** - * Horde_Vcs_Git Patchset class. - * - * @author Chuck Hagenbuch - * @author Michael Slusarz - * @package Horde_Vcs - */ -class Horde_Vcs_Patchset_Git extends Horde_Vcs_Patchset -{ - /** - * Constructor - * - * @param Horde_Vcs $rep A Horde_Vcs repository object. - * @param array $opts Additional options. - *
-     * 'file' - (string) The filename to produce patchsets for.
-     * 'range' - (array) The patchsets to process.
-     *           DEFAULT: None (all patchsets are processed).
-     * 
- */ - public function __construct($rep, $opts = array()) - { - $revs = array(); - - if (isset($opts['file'])) { - $ob = $rep->getFileObject($opts['file']); - $revs = $ob->queryLogs(); - } elseif (!empty($opts['range'])) { - foreach ($opts['range'] as $val) { - /* Grab a filename in the patchset to get log info. */ - $cmd = $rep->getCommand() . ' diff-tree --name-only -r ' . escapeshellarg($val); - exec($cmd, $output); - - /* The first line is the SHA1 hash. */ - $ob = $rep->getFileObject($output[1]); - $revs[$val] = $ob->queryLogs($val); - } - } - - reset($revs); - while (list($rev, $log) = each($revs)) { - if (empty($log)) { - continue; - } - - $this->_patchsets[$rev] = array( - 'date' => $log->queryDate(), - 'author' => $log->queryAuthor(), - 'branches' => $log->queryBranch(), - 'tags' => $log->queryTags(), - 'log' => $log->queryLog(), - 'members' => array(), - ); - - foreach ($log->queryFiles() as $file) { - $to = $rev; - $status = self::MODIFIED; - - switch ($file['status']) { - case 'A': - $from = null; - $status = self::ADDED; - break; - - case 'D': - $from = $to; - $to = null; - $status = self::DELETED; - break; - - default: - $from = $log->queryParent(); - } - - $statinfo = isset($file['added']) - ? array('added' => $file['added'], 'deleted' => $file['deleted']) - : array(); - - $this->_patchsets[$rev]['members'][] = array_merge(array( - 'file' => $file['srcPath'], - 'from' => $from, - 'status' => $status, - 'to' => $to, - ), $statinfo); - } - } - } - -} +} \ No newline at end of file diff --git a/framework/Vcs/lib/Horde/Vcs/Log.php b/framework/Vcs/lib/Horde/Vcs/Log.php new file mode 100644 index 000000000..6645db71f --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/Log.php @@ -0,0 +1,155 @@ +_rev = $rev; + } + + protected function _ensureInitialized() + { + } + + /** + * When serializing, don't return the repository object + */ + public function __sleep() + { + return array_diff(array_keys(get_object_vars($this)), array('_file', '_rep')); + } + + /** + * TODO + */ + public function setRepository($rep) + { + $this->_rep = $rep; + } + + public function setFile(Horde_Vcs_File $file) + { + $this->_file = $file; + } + + /** + * TODO + */ + public function queryRevision() + { + $this->_ensureInitialized(); + return $this->_rev; + } + + /** + * TODO + */ + public function queryDate() + { + $this->_ensureInitialized(); + return $this->_date; + } + + /** + * TODO + */ + public function queryAuthor() + { + $this->_ensureInitialized(); + return $this->_author; + } + + /** + * TODO + */ + public function queryLog() + { + $this->_ensureInitialized(); + return $this->_log; + } + + /** + * TODO + */ + public function queryBranch() + { + $this->_ensureInitialized(); + return array(); + } + + /** + * TODO + */ + public function queryChangedLines() + { + $this->_ensureInitialized(); + return $this->_lines; + } + + /** + * TODO + */ + public function queryTags() + { + $this->_ensureInitialized(); + return $this->_tags; + } + + /** + * Given a branch revision number, this function remaps it + * accordingly, and performs a lookup on the file object to + * return the symbolic name(s) of that branch in the tree. + * + * @return array Hash of symbolic names => branch numbers. + */ + public function querySymbolicBranches() + { + $this->_ensureInitialized(); + + $symBranches = array(); + $branches = $this->_file->queryBranches(); + + foreach ($this->_branches as $branch) { + if (($key = array_search($branch, $branches)) !== false) { + $symBranches[$key] = $branch; + } + } + + return $symBranches; + } + + /** + * TODO + */ + public function queryFiles($file = null) + { + $this->_ensureInitialized(); + return is_null($file) + ? $this->_files + : (isset($this->_files[$file]) ? $this->_files[$file] : array()); + } +} diff --git a/framework/Vcs/lib/Horde/Vcs/Log/Cvs.php b/framework/Vcs/lib/Horde/Vcs/Log/Cvs.php new file mode 100644 index 000000000..17aa8493f --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/Log/Cvs.php @@ -0,0 +1,114 @@ + + * @author Michael Slusarz + * @package Horde_Vcs + */ +class Horde_Vcs_Log_Cvs extends Horde_Vcs_Log +{ + /** + * Cached branch info. + * + * @var string + */ + protected $_branch; + + private $_initialized; + + protected function _init() + { + $raw = $this->_file->getAccum(); + + /* Initialise a simple state machine to parse the output of rlog */ + $state = 'init'; + while (!empty($raw) && $state != 'done') { + switch ($state) { + /* Found filename, now looking for the revision number */ + case 'init': + $line = array_shift($raw); + if (preg_match("/revision (.+)$/", $line, $parts)) { + $this->_rev = $parts[1]; + $state = 'date'; + } + break; + + /* Found revision and filename, now looking for date */ + 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] : ''; + $state = 'branches'; + } + break; + + /* Look for a branch point here - format is 'branches: + * x.y.z; a.b.c;' */ + case 'branches': + /* If we find a branch tag, process and pop it, + otherwise leave input stream untouched */ + if (!empty($raw) && + preg_match("/^branches:\s+(.*)/", $raw[0], $br)) { + /* Get the list of branches from the string, and + * 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; + } + } + array_shift($raw); + } + + $state = 'done'; + break; + } + } + + /* Assume the rest of the lines are the log message */ + $this->_log = implode("\n", $raw); + $this->_tags = $this->_file->queryRevsym($this->_rev); + } + + protected function _ensureInitialized() + { + if (!$this->_initialized) { + $this->_init(); + $this->_initialized = true; + } + } + + /** + * TODO + */ + public function setBranch($branch) + { + $this->_branch = array($branch); + } + + /** + * TODO + */ + public function queryBranch() + { + if (!empty($this->_branch)) { + return $this->_branch; + } + + $branches = $this->_file->queryBranches(); + $key = array_keys($branches, $this->_rev); + return empty($key) + ? array_keys($branches, $this->_rep->strip($this->_rev, 1)) + : $key; + } + +} \ No newline at end of file diff --git a/framework/Vcs/lib/Horde/Vcs/Log/Git.php b/framework/Vcs/lib/Horde/Vcs/Log/Git.php new file mode 100644 index 000000000..4f95193d8 --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/Log/Git.php @@ -0,0 +1,163 @@ + + * @author Michael Slusarz + * @package Horde_Vcs + */ +class Horde_Vcs_Log_Git extends Horde_Vcs_Log +{ + /** + * @var string + */ + protected $_parent = null; + + /** + * @var boolean + */ + private $_initialized; + + protected function _ensureInitialized() + { + if (!$this->_initialized) { + $this->_init(); + $this->_initialized = true; + } + } + + protected function _init() + { + /* Get diff statistics. */ + $stats = array(); + $cmd = $this->_rep->getCommand() . ' diff-tree --numstat ' . escapeshellarg($this->_rev); + exec($cmd, $output); + + reset($output); + // Skip the first entry (it is the revision number) + next($output); + while (list(,$v) = each($output)) { + $tmp = explode("\t", $v); + $stats[$tmp[2]] = array_slice($tmp, 0, 2); + } + + // @TODO use Commit, CommitDate, and Merge properties + $cmd = $this->_rep->getCommand() . ' whatchanged --no-color --pretty=format:"Rev:%H%nParents:%P%nAuthor:%an <%ae>%nAuthorDate:%at%nRefs:%d%n%n%s%n%b" --no-abbrev -n 1 ' . escapeshellarg($this->_rev); + $pipe = popen($cmd, 'r'); + if (!is_resource($pipe)) { + throw new Horde_Vcs_Exception('Unable to run ' . $cmd . ': ' . error_get_last()); + } + + while (true) { + $line = trim(fgets($pipe)); + if (!strlen($line)) { break; } + if (strpos($line, ':') === false) { + throw new Horde_Vcs_Exception('Malformed log line: ' . $line); + } + + list($key, $value) = explode(':', $line, 2); + $value = trim($value); + + switch (trim($key)) { + case 'Rev': + if ($this->_rev != $value) { + fclose($pipe); + throw new Horde_Vcs_Exception('Expected ' . $this->_rev . ', got ' . $value); + } + break; + + case 'Parents': + // @TODO: More than 1 parent? + $this->_parent = $value; + break; + + case 'Author': + $this->_author = $value; + break; + + case 'AuthorDate': + $this->_date = $value; + break; + + case 'Refs': + if ($value) { + $value = substr($value, 1, -1); + foreach (explode(',', $value) as $val) { + $val = trim($val); + if (strpos($val, 'refs/tags/') === 0) { + $this->_tags[] = substr($val, 10); + } + } + if (!empty($this->_tags)) { + sort($this->_tags); + } + } + break; + } + } + + $log = ''; + $line = fgets($pipe); + while ($line !== false && substr($line, 0, 1) != ':') { + $log .= $line; + $line = fgets($pipe); + } + $this->_log = trim($log); + + // Build list of files in this revision. The format of these lines is + // documented in the git diff-tree documentation: + // 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); + + $statinfo = isset($stats[$matches[6]]) + ? array('added' => $stats[$matches[6]][0], 'deleted' => $stats[$matches[6]][1]) + : array(); + + $this->_files[$matches[6]] = array_merge(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] : '' + ), $statinfo); + + $line = fgets($pipe); + } + + fclose($pipe); + } + + /** + * TODO + */ + public function getHashForPath($path) + { + $this->_ensureInitialized(); + return $this->_files[$path]['dstSha1']; + } + + /** + * TODO + */ + public function queryBranch() + { + return $this->_file->queryBranch($this->_rev); + } + + /** + * TODO + */ + public function queryParent() + { + return $this->_parent; + } + +} \ No newline at end of file diff --git a/framework/Vcs/lib/Horde/Vcs/Patchset.php b/framework/Vcs/lib/Horde/Vcs/Patchset.php new file mode 100644 index 000000000..c0984ea0b --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/Patchset.php @@ -0,0 +1,50 @@ + + * 'range' - (array) The patchsets to process. + * DEFAULT: None (all patchsets are processed). + * + */ + abstract public function __construct($rep, $file, $opts = array()); + + /** + * TODO + * + * @return array TODO + * 'date' + * 'author' + * 'branches' + * 'tags' + * 'log' + * 'members' - array: + * 'file' + * 'from' + * 'to' + * 'status' + */ + public function getPatchsets() + { + return $this->_patchsets; + } +} diff --git a/framework/Vcs/lib/Horde/Vcs/Patchset/Cvs.php b/framework/Vcs/lib/Horde/Vcs/Patchset/Cvs.php new file mode 100644 index 000000000..4802e3f09 --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/Patchset/Cvs.php @@ -0,0 +1,143 @@ + + * @author Michael Slusarz + * @package Horde_Vcs + */ +class Horde_Vcs_Patchset_Cvs extends Horde_Vcs_Patchset +{ + /** + * Constructor + * + * @param Horde_Vcs $rep A Horde_Vcs repository object. + * @param string $file The filename to create a patchset for. + * @param array $opts Additional options. + *
+     * 'file' - (string) The filename to process.
+     *          REQUIRED for this driver.
+     * 'range' - (array) The patchsets to process.
+     *           DEFAULT: None (all patchsets are processed).
+     * 
+ * + * @throws Horde_Vcs_Exception + */ + public function __construct($rep, $opts = array()) + { + $file = $rep->sourceroot() . '/' . $opts['file']; + + /* Check that we are actually in the filesystem. */ + if (!$rep->isFile($file)) { + throw new Horde_Vcs_Exception('File Not Found'); + } + + /* Call cvsps to retrieve all patchsets for this file. */ + $cvsps_home = $rep->getPath('cvsps_home'); + $HOME = !empty($cvsps_home) ? + 'HOME=' . escapeshellarg($cvsps_home) . ' ' : + ''; + + $rangecmd = empty($opts['range']) + ? '' + : ' -s ' . escapeshellarg(implode(',', $opts['range'])); + + $ret_array = array(); + $cmd = $HOME . escapeshellcmd($rep->getPath('cvsps')) . $rangecmd . ' -u --cvs-direct --root ' . escapeshellarg($rep->sourceroot()) . ' -f ' . escapeshellarg(basename($file)) . ' ' . escapeshellarg(dirname($file)); + exec($cmd, $ret_array, $retval); + if ($retval) { + throw new Horde_Vcs_Exception('Failed to spawn cvsps to retrieve patchset information.'); + } + + $state = 'begin'; + reset($ret_array); + while (list(,$line) = each($ret_array)) { + $line = trim($line); + + if ($line == '---------------------') { + $state = 'begin'; + continue; + } + + switch ($state) { + case 'begin': + $id = str_replace('PatchSet ', '', $line); + $this->_patchsets[$id] = array(); + $state = 'info'; + break; + + case 'info': + $info = explode(':', $line, 2); + $info[1] = ltrim($info[1]); + + switch ($info[0]) { + case 'Date': + $d = new DateTime($info[1]); + $this->_patchsets[$id]['date'] = $d->format('U'); + break; + + case 'Author': + $this->_patchsets[$id]['author'] = $info[1]; + break; + + case 'Branch': + $this->_patchsets[$id]['branches'] = ($info[1] == 'HEAD') + ? array() + : array($info[1]); + break; + + case 'Tag': + $this->_patchsets[$id]['tags'] = ($info[1] == '(none)') + ? array() + : array($info[1]); + break; + + case 'Log': + $state = 'log'; + $this->_patchsets[$id]['log'] = ''; + break; + } + break; + + case 'log': + if ($line == 'Members:') { + $state = 'members'; + $this->_patchsets[$id]['log'] = rtrim($this->_patchsets[$id]['log']); + $this->_patchsets[$id]['members'] = array(); + } else { + $this->_patchsets[$id]['log'] .= $line . "\n"; + } + break; + + case 'members': + if (!empty($line)) { + $parts = explode(':', $line); + list($from, $to) = explode('->', $parts[1], 2); + $status = self::MODIFIED; + + if ($from == 'INITIAL') { + $from = null; + $status = self::ADDED; + } elseif (substr($to, -6) == '(DEAD)') { + $to = null; + $status = self::DELETED; + } + + $this->_patchsets[$id]['members'][] = array( + 'file' => $parts[0], + 'from' => $from, + 'status' => $status, + 'to' => $to + ); + } + break; + } + } + } + +} \ No newline at end of file diff --git a/framework/Vcs/lib/Horde/Vcs/Patchset/Git.php b/framework/Vcs/lib/Horde/Vcs/Patchset/Git.php new file mode 100644 index 000000000..e7a8d969d --- /dev/null +++ b/framework/Vcs/lib/Horde/Vcs/Patchset/Git.php @@ -0,0 +1,95 @@ + + * @author Michael Slusarz + * @package Horde_Vcs + */ +class Horde_Vcs_Patchset_Git extends Horde_Vcs_Patchset +{ + /** + * Constructor + * + * @param Horde_Vcs $rep A Horde_Vcs repository object. + * @param array $opts Additional options. + *
+     * 'file' - (string) The filename to produce patchsets for.
+     * 'range' - (array) The patchsets to process.
+     *           DEFAULT: None (all patchsets are processed).
+     * 
+ */ + public function __construct($rep, $opts = array()) + { + $revs = array(); + + if (isset($opts['file'])) { + $ob = $rep->getFileObject($opts['file']); + $revs = $ob->queryLogs(); + } elseif (!empty($opts['range'])) { + foreach ($opts['range'] as $val) { + /* Grab a filename in the patchset to get log info. */ + $cmd = $rep->getCommand() . ' diff-tree --name-only -r ' . escapeshellarg($val); + exec($cmd, $output); + + /* The first line is the SHA1 hash. */ + $ob = $rep->getFileObject($output[1]); + $revs[$val] = $ob->queryLogs($val); + } + } + + reset($revs); + while (list($rev, $log) = each($revs)) { + if (empty($log)) { + continue; + } + + $this->_patchsets[$rev] = array( + 'date' => $log->queryDate(), + 'author' => $log->queryAuthor(), + 'branches' => $log->queryBranch(), + 'tags' => $log->queryTags(), + 'log' => $log->queryLog(), + 'members' => array(), + ); + + foreach ($log->queryFiles() as $file) { + $to = $rev; + $status = self::MODIFIED; + + switch ($file['status']) { + case 'A': + $from = null; + $status = self::ADDED; + break; + + case 'D': + $from = $to; + $to = null; + $status = self::DELETED; + break; + + default: + $from = $log->queryParent(); + } + + $statinfo = isset($file['added']) + ? array('added' => $file['added'], 'deleted' => $file['deleted']) + : array(); + + $this->_patchsets[$rev]['members'][] = array_merge(array( + 'file' => $file['srcPath'], + 'from' => $from, + 'status' => $status, + 'to' => $to, + ), $statinfo); + } + } + } + +} \ No newline at end of file diff --git a/framework/Vcs/package.xml b/framework/Vcs/package.xml index f62e40280..b79720b4f 100644 --- a/framework/Vcs/package.xml +++ b/framework/Vcs/package.xml @@ -1,14 +1,10 @@ - + Vcs pear.horde.org Version Control API The Horde_VC package provides a generalized API to multiple - version control systems. - + version control systems. Chuck Hagenbuch chuck @@ -27,7 +23,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> slusarz@horde.org yes - 2008-12-20 + 2010-10-15 + 0.1.0 0.1.0 @@ -37,21 +34,43 @@ http://pear.php.net/dtd/package-2.0.xsd"> beta LGPL - * Initial Horde 4 package. + +* Initial Horde 4 package. + - + + + + + + + + + + + + + + + + + + + + + - + - - + + @@ -79,17 +98,45 @@ http://pear.php.net/dtd/package-2.0.xsd"> - - - - - - + + + + + + + + + + + + + + + + + + + 0.0.3 + 0.0.3 + + + beta + beta + + 2004-01-01 + LGPL + +- Fix caching of Patchset objects. +- Add a VC_Patchset parent class for common Patchset functionality. + + + + 0.0.4 0.0.4 @@ -99,24 +146,25 @@ http://pear.php.net/dtd/package-2.0.xsd"> 2006-05-08 LGPL - * Converted to package.xml 2.0 for pear.horde.org + +* Converted to package.xml 2.0 for pear.horde.org * Fix subversion diffs on files with spaces (helly@php.net, Bug #5244) * Pass username and password to the SVN client if set (duck@obala.net, Request #5958) - 0.0.3 - 0.0.3 + 0.1.0 + 0.1.0 beta beta - 2004-01-01 + 2010-10-15 LGPL - - Fix caching of Patchset objects. -- Add a VC_Patchset parent class for common Patchset functionality. + +* Initial Horde 4 package. -- 2.11.0