}
}
-
-/**
- * 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:
- * <pre>
- * 'range' - (array) The patchsets to process.
- * DEFAULT: None (all patchsets are processed).
- * </pre>
- */
- 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;
- }
-}
}
}
-
-/**
- * Horde_Vcs_Cvs directory class.
- *
- * @author Anil Madhavapeddy <anil@recoil.org>
- * @author Michael Slusarz <slusarz@horde.org>
- * @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 <anil@recoil.org>
- * @author Michael Slusarz <slusarz@horde.org>
- * @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 <anil@recoil.org>
- * @author Michael Slusarz <slusarz@horde.org>
- * @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 <anil@recoil.org>
- * @author Michael Slusarz <slusarz@horde.org>
- * @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.
- * <pre>
- * 'file' - (string) The filename to process.
- * REQUIRED for this driver.
- * 'range' - (array) The patchsets to process.
- * DEFAULT: None (all patchsets are processed).
- * </pre>
- *
- * @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;
- }
- }
- }
-
-}
--- /dev/null
+<?php
+/**
+ * Horde_Vcs_Cvs directory class.
+ *
+ * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @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();
+ }
+}
--- /dev/null
+<?php
+/**
+ * Horde_Vcs_Cvs directory class.
+ *
+ * Copyright 2000-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Anil Madhavapeddy <anil@recoil.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @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');
+ }
+
+}
--- /dev/null
+<?php
+/**
+ * Horde_Vcs_Git directory class.
+ *
+ * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @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
--- /dev/null
+<?php
+/**
+ * Horde_Vcs file class.
+ *
+ * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @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();
+ }
+}
--- /dev/null
+<?php
+/**
+ * Horde_Vcs_Cvs file class.
+ *
+ * Copyright 2000-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Anil Madhavapeddy <anil@recoil.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @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
--- /dev/null
+<?php
+/**
+ * Horde_Vcs_Git file class.
+ *
+ * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @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
return 'master';
}
-}
-
-/**
- * Horde_Vcs_Git directory class.
- *
- * @author Chuck Hagenbuch <chuck@horde.org>
- * @author Michael Slusarz <slusarz@horde.org>
- * @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 <chuck@horde.org>
- * @author Michael Slusarz <slusarz@horde.org>
- * @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 <chuck@horde.org>
- * @author Michael Slusarz <slusarz@horde.org>
- * @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 <chuck@horde.org>
- * @author Michael Slusarz <slusarz@horde.org>
- * @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.
- * <pre>
- * 'file' - (string) The filename to produce patchsets for.
- * 'range' - (array) The patchsets to process.
- * DEFAULT: None (all patchsets are processed).
- * </pre>
- */
- 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
--- /dev/null
+<?php
+/**
+ * Horde_Vcs log class.
+ *
+ * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @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());
+ }
+}
--- /dev/null
+<?php
+/**
+ * Horde_Vcs_cvs Log class.
+ *
+ * Copyright 2000-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Anil Madhavapeddy <anil@recoil.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @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
--- /dev/null
+<?php
+/**
+ * Horde_Vcs_Git log class.
+ *
+ * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @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
--- /dev/null
+<?php
+/**
+ * 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:
+ * <pre>
+ * 'range' - (array) The patchsets to process.
+ * DEFAULT: None (all patchsets are processed).
+ * </pre>
+ */
+ 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;
+ }
+}
--- /dev/null
+<?php
+/**
+ * Horde_Vcs_Cvs Patchset class.
+ *
+ * Copyright 2000-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Anil Madhavapeddy <anil@recoil.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @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.
+ * <pre>
+ * 'file' - (string) The filename to process.
+ * REQUIRED for this driver.
+ * 'range' - (array) The patchsets to process.
+ * DEFAULT: None (all patchsets are processed).
+ * </pre>
+ *
+ * @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
--- /dev/null
+<?php
+/**
+ * Horde_Vcs_Git Patchset class.
+ *
+ * Copyright 2008-2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author Chuck Hagenbuch <chuck@horde.org>
+ * @author Michael Slusarz <slusarz@horde.org>
+ * @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.
+ * <pre>
+ * 'file' - (string) The filename to produce patchsets for.
+ * 'range' - (array) The patchsets to process.
+ * DEFAULT: None (all patchsets are processed).
+ * </pre>
+ */
+ 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
<?xml version="1.0" encoding="UTF-8"?>
-<package packagerversion="1.4.9" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
-http://pear.php.net/dtd/tasks-1.0.xsd
-http://pear.php.net/dtd/package-2.0
-http://pear.php.net/dtd/package-2.0.xsd">
+<package packagerversion="1.9.1" version="2.0" xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0 http://pear.php.net/dtd/tasks-1.0.xsd http://pear.php.net/dtd/package-2.0 http://pear.php.net/dtd/package-2.0.xsd">
<name>Vcs</name>
<channel>pear.horde.org</channel>
<summary>Version Control API</summary>
<description>The Horde_VC package provides a generalized API to multiple
- version control systems.
- </description>
+ version control systems.</description>
<lead>
<name>Chuck Hagenbuch</name>
<user>chuck</user>
<email>slusarz@horde.org</email>
<active>yes</active>
</lead>
- <date>2008-12-20</date>
+ <date>2010-10-15</date>
+ <time>14:02:52</time>
<version>
<release>0.1.0</release>
<api>0.1.0</api>
<api>beta</api>
</stability>
<license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes>* Initial Horde 4 package.</notes>
+ <notes>
+* Initial Horde 4 package.
+ </notes>
<contents>
- <dir name="/">
+ <dir baseinstalldir="/" name="/">
<dir name="lib">
<dir name="Horde">
<dir name="Vcs">
+ <dir name="Directory">
+ <file name="Cvs.php" role="php" />
+ <file name="Git.php" role="php" />
+ </dir> <!-- //lib/Horde/Vcs/Directory -->
+ <dir name="File">
+ <file name="Cvs.php" role="php" />
+ <file name="Git.php" role="php" />
+ </dir> <!-- //lib/Horde/Vcs/File -->
+ <dir name="Log">
+ <file name="Cvs.php" role="php" />
+ <file name="Git.php" role="php" />
+ </dir> <!-- //lib/Horde/Vcs/Log -->
+ <dir name="Patchset">
+ <file name="Cvs.php" role="php" />
+ <file name="Git.php" role="php" />
+ </dir> <!-- //lib/Horde/Vcs/Patchset -->
<file name="Cvs.php" role="php" />
+ <file name="Directory.php" role="php" />
<file name="Exception.php" role="php" />
+ <file name="File.php" role="php" />
<file name="Git.php" role="php" />
+ <file name="Log.php" role="php" />
+ <file name="Patchset.php" role="php" />
<file name="Rcs.php" role="php" />
<file name="Svn.php" role="php" />
- </dir> <!-- /lib/Horde/VC -->
+ </dir> <!-- //lib/Horde/Vcs -->
<file name="Vcs.php" role="php" />
- </dir> <!-- /lib/Horde -->
- </dir> <!-- /lib -->
+ </dir> <!-- //lib/Horde -->
+ </dir> <!-- //lib -->
</dir> <!-- / -->
</contents>
<dependencies>
</dependencies>
<phprelease>
<filelist>
- <install name="lib/Horde/Vcs/Cvs.php" as="Horde/Vcs/Cvs.php" />
- <install name="lib/Horde/Vcs/Exception.php" as="Horde/Vcs/Exception.php" />
- <install name="lib/Horde/Vcs/Git.php" as="Horde/Vcs/Git.php" />
- <install name="lib/Horde/Vcs/Rcs.php" as="Horde/Vcs/Rcs.php" />
- <install name="lib/Horde/Vcs/Svn.php" as="Horde/Vcs/Svn.php" />
- <install name="lib/Horde/Vcs.php" as="Horde/Vcs.php" />
+ <install as="Horde/Vcs.php" name="lib/Horde/Vcs.php" />
+ <install as="Horde/Vcs/Cvs.php" name="lib/Horde/Vcs/Cvs.php" />
+ <install as="Horde/Vcs/Directory.php" name="lib/Horde/Vcs/Directory.php" />
+ <install as="Horde/Vcs/Exception.php" name="lib/Horde/Vcs/Exception.php" />
+ <install as="Horde/Vcs/File.php" name="lib/Horde/Vcs/File.php" />
+ <install as="Horde/Vcs/Git.php" name="lib/Horde/Vcs/Git.php" />
+ <install as="Horde/Vcs/Log.php" name="lib/Horde/Vcs/Log.php" />
+ <install as="Horde/Vcs/Patchset.php" name="lib/Horde/Vcs/Patchset.php" />
+ <install as="Horde/Vcs/Rcs.php" name="lib/Horde/Vcs/Rcs.php" />
+ <install as="Horde/Vcs/Svn.php" name="lib/Horde/Vcs/Svn.php" />
+ <install as="Horde/Vcs/Directory/Cvs.php" name="lib/Horde/Vcs/Directory/Cvs.php" />
+ <install as="Horde/Vcs/Directory/Git.php" name="lib/Horde/Vcs/Directory/Git.php" />
+ <install as="Horde/Vcs/File/Cvs.php" name="lib/Horde/Vcs/File/Cvs.php" />
+ <install as="Horde/Vcs/File/Git.php" name="lib/Horde/Vcs/File/Git.php" />
+ <install as="Horde/Vcs/Log/Cvs.php" name="lib/Horde/Vcs/Log/Cvs.php" />
+ <install as="Horde/Vcs/Log/Git.php" name="lib/Horde/Vcs/Log/Git.php" />
+ <install as="Horde/Vcs/Patchset/Cvs.php" name="lib/Horde/Vcs/Patchset/Cvs.php" />
+ <install as="Horde/Vcs/Patchset/Git.php" name="lib/Horde/Vcs/Patchset/Git.php" />
</filelist>
</phprelease>
<changelog>
<release>
<version>
+ <release>0.0.3</release>
+ <api>0.0.3</api>
+ </version>
+ <stability>
+ <release>beta</release>
+ <api>beta</api>
+ </stability>
+ <date>2004-01-01</date>
+ <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
+ <notes>
+- Fix caching of Patchset objects.
+- Add a VC_Patchset parent class for common Patchset functionality.
+ </notes>
+ </release>
+ <release>
+ <version>
<release>0.0.4</release>
<api>0.0.4</api>
</version>
</stability>
<date>2006-05-08</date>
<license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes>* Converted to package.xml 2.0 for pear.horde.org
+ <notes>
+* 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)
</notes>
</release>
<release>
<version>
- <release>0.0.3</release>
- <api>0.0.3</api>
+ <release>0.1.0</release>
+ <api>0.1.0</api>
</version>
<stability>
<release>beta</release>
<api>beta</api>
</stability>
- <date>2004-01-01</date>
+ <date>2010-10-15</date>
<license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes>- Fix caching of Patchset objects.
-- Add a VC_Patchset parent class for common Patchset functionality.
+ <notes>
+* Initial Horde 4 package.
</notes>
</release>
</changelog>