More work on Horde_Vcs.
authorMichael M Slusarz <slusarz@curecanti.org>
Tue, 27 Jan 2009 23:48:45 +0000 (16:48 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Wed, 28 Jan 2009 05:04:05 +0000 (22:04 -0700)
Remove Horde_Vcs_Diff class.
Fix some issues relating to paths.
Clean up patchset generation.
Clean up caching/directory browsing.
Various phpdoc cleanup.
Make this code more correctly OO - now down to only 1 main class
(Horde_Vcs) and 4 subclasses (Horde_Vcs_File, Horde_Vcs_Directory,
Horde_Vcs_Log, and Horde_Vcs_Patchset).

15 files changed:
chora/annotate.php
chora/browse.php
chora/co.php
chora/diff.php
chora/history.php
chora/lib/Chora.php
chora/lib/base.php
chora/patchsets.php
chora/stats.php
chora/templates/log/footer.inc
framework/Vcs/lib/Horde/Vcs.php
framework/Vcs/lib/Horde/Vcs/Cvs.php
framework/Vcs/lib/Horde/Vcs/Git.php
framework/Vcs/lib/Horde/Vcs/Rcs.php
framework/Vcs/lib/Horde/Vcs/Svn.php

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