Patchsets are working, at least for Git and CVS.
authorMichael M Slusarz <slusarz@curecanti.org>
Wed, 28 Jan 2009 20:15:06 +0000 (13:15 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Thu, 29 Jan 2009 08:12:34 +0000 (01:12 -0700)
chora/patchsets.php
chora/templates/patchsets/ps.inc
framework/Vcs/lib/Horde/Vcs.php
framework/Vcs/lib/Horde/Vcs/Cvs.php
framework/Vcs/lib/Horde/Vcs/Git.php

index 0feefa6..340701a 100644 (file)
@@ -25,6 +25,7 @@ if (!$VC->isFile($fullname)) {
 
 try {
     $ps = $VC->getPatchsetObject($where);
+    $patchsets = $ps->getPatchsets();
 } catch (Horde_Vcs_Exception $e) {
     Chora::fatal($e);
 }
@@ -40,49 +41,53 @@ require CHORA_TEMPLATES . '/menu.inc';
 require CHORA_TEMPLATES . '/headerbar.inc';
 require CHORA_TEMPLATES . '/patchsets/header.inc';
 
-$patchsets = $ps->getPatchsets();
-krsort($patchsets);
-foreach ($patchsets as $id => $patchset) {
-    $commitDate = Chora::formatDate($patchset['date']);
-    $readableDate = Chora::readableTime($patchset['date'], true);
-    $author = Chora::showAuthorName($patchset['author'], true);
-    if ($VC->hasFeature('patchsets')) {
-        // The diff should be from the top of the source tree so as to
-        // get all files.
-        $topDir = substr($where, 0, strpos($where, '/', 1));
-
-        // Subversion supports patchset diffs natively.
-        $patchset_link = Horde::link(Chora::url('diff', $topDir, array('r1' => $id - 1, 'r2' => $id, 't' => 'unified'))) .
-            $id . '</a>';
-    } else {
-        // Not supported in any other VC systems yet.
-        $patchset_link = $id;
-    }
+reset($patchsets);
+while (list($id, $patchset) = each($patchsets)) {
+    // The diff should be from the top of the source tree so as to
+    // get all files.
+    // @TODO: Fix this (support needs to be written in diff page)
+    // $patchset_link = Horde::link(Chora::url('diff', substr($where, 0, strpos($where, '/', 1)), array('r1' => $id - 1, 'r2' => $id, 't' => 'unified'))) . $VC->abbrev($id) . '</a>';
+    $patchset_link = htmlspecialchars($VC->abbrev($id));
+
+    $files = $tags = array();
+    $diff_img = Horde::img('diff.png', _("Diff"));
 
-    $files = array();
-    $dir = dirname($where);
     foreach ($patchset['members'] as $member) {
         $file = array();
-        $mywhere = ($VC->hasFeature('patchsets')) ? $member['file'] : $dir . '/' . $member['file'];
-        $file['file'] = Horde::link(Chora::url('patchsets', $mywhere)) . htmlspecialchars($member['file']) . '</a>';
+
+        $file['file'] = Horde::link(Chora::url('patchsets', $member['file'])) . htmlspecialchars($member['file']) . '</a>';
+
         if ($member['from'] == 'INITIAL') {
             $file['from'] = '<ins>' . _("New File") . '</ins>';
             $file['diff'] = '';
         } else {
-            $file['from'] = Horde::link(Chora::url('co', $mywhere, array('r' => $member['from']))) . htmlspecialchars($member['from']) . '</a>';
-            $file['diff'] = Horde::link(Chora::url('diff', $mywhere, array('r1' => $member['from'], 'r2' => $member['to'], 't' => 'unified'))) . ' ' . Horde::img('diff.png', _("Diff")) . '</a>';
+            $file['from'] = Horde::link(Chora::url('co', $member['file'], array('r' => $member['from']))) . htmlspecialchars($VC->abbrev($member['from'])) . '</a>';
+            $file['diff'] = Horde::link(Chora::url('diff', $member['file'], array('r1' => $member['from'], 'r2' => $member['to']))) . ' ' . $diff_img . '</a>';
         }
+
         if (substr($member['to'], -6) == '(DEAD)') {
             $file['to'] = '<del>' . _("Deleted") . '</del>';
             $file['diff'] = '';
         } else {
-            $file['to'] = Horde::link(Chora::url('co', $mywhere, array('r' => $member['to']))) . htmlspecialchars($member['to']) . '</a>';
+            $file['to'] = Horde::link(Chora::url('co', $member['file'], array('r' => $member['to']))) . htmlspecialchars($VC->abbrev($member['to'])) . '</a>';
         }
 
         $files[] = $file;
     }
 
+    $commitDate = Chora::formatDate($patchset['date']);
+    $readableDate = Chora::readableTime($patchset['date'], true);
+    $author = Chora::showAuthorName($patchset['author'], true);
     $logMessage = Chora::formatLogMessage($patchset['log']);
+
+    if (!empty($patchset['branch'])) {
+        $tags = $patchset['branch'];
+    }
+
+    if (!empty($patchset['tag'])) {
+        $tags = array_merge($tags, $patchset['tag']);
+    }
+
     require CHORA_TEMPLATES . '/patchsets/ps.inc';
 }
 
index c2543b2..eb73615 100644 (file)
@@ -4,22 +4,15 @@
  <td class="author"><?php echo $author ?></td>
  <td class="files">
   <ul>
-   <?php foreach ($files as $file): ?>
-   <li><?php echo $file['file'] . ': ' . $file['from'] . ' -> ' . $file['to'] . ' ' . $file['diff'] ?>
-   <?php endforeach; ?>
+<?php foreach ($files as $file): ?>
+   <li><?php echo $file['file'] . ': ' . $file['from'] . ' -> ' . $file['to'] . ' ' . $file['diff'] ?></li>
+<?php endforeach; ?>
   </ul>
  </td>
  <td class="log">
-<?php
-echo '<p class="message">' . $logMessage . '</p>';
-$tags = array();
-if (isset($patchset['branch'])) {
-    $tags[] = htmlspecialchars($patchset['branch']);
-}
-if (isset($patchset['tag'])) {
-    $tags[] = htmlspecialchars($patchset['tag']);
-}
-if ($tags) echo '<p class="tags">' . _("Tags") . ': ' . implode(', ', $tags) . '</p>';
-?>
+  <p class="message"><?php echo $logMessage ?></p>
+<?php if (!empty($tags)): ?>
+  <p class="tags"><?php echo _("Tags") . ': ' . implode(', ', array_map('htmlspecialchars', $tags)) ?></p>
+<?php endif; ?>
  </td>
 </tr>
index a3afc77..2caaabe 100644 (file)
@@ -1171,6 +1171,9 @@ abstract class Horde_Vcs_Log
  */
 abstract class Horde_Vcs_Patchset
 {
+    const INITIAL = 1;
+    const DEAD = 2;
+
     /**
      * @var array
      */
@@ -1186,6 +1189,18 @@ abstract class Horde_Vcs_Patchset
 
     /**
      * TODO
+     *
+     * @return array  TODO
+     * 'date'
+     * 'author'
+     * 'branches'
+     * 'tags'
+     * 'log'
+     * 'members' - array:
+     *     'file'
+     *     'from'
+     *     'to'
+     *     'status'
      */
     public function getPatchsets()
     {
index c2df4db..09a3f40 100644 (file)
@@ -673,11 +673,9 @@ class Horde_Vcs_Patchset_Cvs extends Horde_Vcs_Patchset
     public function __construct($rep, $file)
     {
         $file = $rep->sourceroot() . '/' . $file;
-        $name = basename($file);
-        $dir = dirname($file);
 
         /* Check that we are actually in the filesystem. */
-        if (!is_file($file . ',v')) {
+        if (!$rep->isFile($file)) {
             throw new Horde_Vcs_Exception('File Not Found');
         }
 
@@ -688,15 +686,17 @@ class Horde_Vcs_Patchset_Cvs extends Horde_Vcs_Patchset
             '';
 
         $ret_array = array();
-        $cmd = $HOME . $rep->getPath('cvsps') . ' -u --cvs-direct --root ' . escapeshellarg($rep->sourceroot()) . ' -f ' . escapeshellarg($name) . ' ' . escapeshellarg($dir);
+        $cmd = $HOME . $rep->getPath('cvsps') . ' -u --cvs-direct --root ' . escapeshellarg($rep->sourceroot()) . ' -f ' . escapeshellarg(basename($file)) . ' ' . escapeshellarg(dirname($file));
         exec($cmd, $ret_array, $retval);
         if ($retval) {
             throw new Horde_Vcs_Exception('Failed to spawn cvsps to retrieve patchset information.');
         }
 
         $state = 'begin';
-        foreach ($ret_array as $line) {
+        reset($ret_array);
+        while (list(,$line) = each($ret_array)) {
             $line = trim($line);
+
             if ($line == '---------------------') {
                 $state = 'begin';
                 continue;
@@ -711,27 +711,28 @@ class Horde_Vcs_Patchset_Cvs extends Horde_Vcs_Patchset
 
             case 'info':
                 $info = explode(':', $line, 2);
+                $info[1] = ltrim($info[1]);
+
                 switch ($info[0]) {
                 case 'Date':
-                    if (preg_match('|(\d{4})/(\d{2})/(\d{2}) (\d{2}):(\d{2}):(\d{2})|', $info[1], $date)) {
-                        $this->_patchsets[$id]['date'] = gmmktime($date[4], $date[5], $date[6], $date[2], $date[3], $date[1]);
-                    }
+                    $d = new DateTime($info[1]);
+                    $this->_patchsets[$id]['date'] = $d->format('U');
                     break;
 
                 case 'Author':
-                    $this->_patchsets[$id]['author'] = trim($info[1]);
+                    $this->_patchsets[$id]['author'] = $info[1];
                     break;
 
                 case 'Branch':
-                    if (trim($info[1]) != 'HEAD') {
-                        $this->_patchsets[$id]['branch'] = trim($info[1]);
-                    }
+                    $this->_patchsets[$id]['branches'] = ($info[1] == 'HEAD')
+                        ? array()
+                        : array($info[1]);
                     break;
 
                 case 'Tag':
-                    if (trim($info[1]) != '(none)') {
-                        $this->_patchsets[$id]['tag'] = trim($info[1]);
-                    }
+                    $this->_patchsets[$id]['tags'] = ($info[1] == '(none)')
+                        ? array()
+                        : array($info[1]);
                     break;
 
                 case 'Log':
@@ -744,7 +745,7 @@ class Horde_Vcs_Patchset_Cvs extends Horde_Vcs_Patchset
             case 'log':
                 if ($line == 'Members:') {
                     $state = 'members';
-                    $this->_patchsets[$id]['log'] = trim($this->_patchsets[$id]['log']);
+                    $this->_patchsets[$id]['log'] = rtrim($this->_patchsets[$id]['log']);
                     $this->_patchsets[$id]['members'] = array();
                 } else {
                     $this->_patchsets[$id]['log'] .= $line . "\n";
@@ -754,16 +755,27 @@ class Horde_Vcs_Patchset_Cvs extends Horde_Vcs_Patchset
             case 'members':
                 if (!empty($line)) {
                     $parts = explode(':', $line);
-                    $revs = explode('->', $parts[1]);
-                    $this->_patchsets[$id]['members'][] = array('file' => $parts[0],
-                                                                'from' => $revs[0],
-                                                                'to' => $revs[1]);
+                    list($from, $to) = explode('->', $parts[1], 2);
+                    $status = 0;
+
+                    if ($from == 'INITIAL') {
+                        $from = null;
+                        $status = self::INITIAL;
+                    } elseif (substr($to, -6) == '(DEAD)') {
+                        $to = null;
+                        $status = self::DEAD;
+                    }
+
+                    $this->_patchsets[$id]['members'][] = array(
+                        'file' => $parts[0],
+                        'from' => $from,
+                        'status' => $status,
+                        'to' => $to
+                    );
                 }
                 break;
             }
         }
-
-        return true;
     }
 
 }
index c7d249f..6adb358 100644 (file)
@@ -438,31 +438,47 @@ class Horde_Vcs_File_Git extends Horde_Vcs_File
 class Horde_Vcs_Log_Git extends Horde_Vcs_Log
 {
     /**
+     * @var string
+     */
+    protected $_parent = null;
+
+    /**
+     * @var array
+     */
+    protected $_files = array();
+
+    /**
      * Constructor.
      */
     public function __construct($rep, $fl, $rev)
     {
         parent::__construct($rep, $fl, $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;
+        // @TODO use Commit, CommitDate, and Merge properties
+        $cmd = $rep->getCommand() . ' whatchanged --no-color --pretty=format:"Rev:%H%nParents:%P%nAuthor:%an <%ae>%nAuthorDate:%at%nRefs:%d%n%n%s%n%b" --no-abbrev -n 1 ' . $rev;
         $pipe = popen($cmd, 'r');
         if (!is_resource($pipe)) {
             throw new Horde_Vcs_Exception('Unable to run ' . $cmd . ': ' . error_get_last());
         }
 
-        $commit = trim(array_pop(explode(' ', fgets($pipe))));
-        if ($commit != $rev) {
-            fclose($pipe);
-            throw new Horde_Vcs_Exception('Expected ' . $rev . ', got ' . $commit);
-        }
-
-        // @TODO use Commit, CommitDate, and Merge properties
         $line = trim(fgets($pipe));
         while ($line != '') {
             list($key, $value) = explode(':', $line, 2);
             $value = trim($value);
 
             switch (trim($key)) {
+            case 'Rev':
+                if ($rev != $value) {
+                    fclose($pipe);
+                    throw new Horde_Vcs_Exception('Expected ' . $rev . ', got ' . $value);
+                }
+                break;
+
+            case 'Parents':
+                // @TODO: More than 1 parent?
+                $this->_parent = $value;
+                break;
+
             case 'Author':
                 $this->_author = $value;
                 break;
@@ -490,7 +506,6 @@ class Horde_Vcs_Log_Git extends Horde_Vcs_Log
             $line = trim(fgets($pipe));
         }
 
-
         $log = '';
         $line = fgets($pipe);
         while (substr($line, 0, 1) != ':') {
@@ -550,6 +565,22 @@ class Horde_Vcs_Log_Git extends Horde_Vcs_Log
         return $this->_file->queryBranch($this->_rev);
     }
 
+    /**
+     * TODO
+     */
+    public function queryFiles()
+    {
+        return $this->_files;
+    }
+
+    /**
+     * TODO
+     */
+    public function queryParent()
+    {
+        return $this->_parent;
+    }
+
 }
 
 /**
@@ -569,47 +600,49 @@ class Horde_Vcs_Patchset_Git extends Horde_Vcs_Patchset
      */
     public function __construct($rep, $file)
     {
-        $fileOb = $rep->getFileObject($this->file);
+        $fileOb = $rep->getFileObject($file);
 
-        foreach ($fileOb->logs as $rev => $log) {
+        foreach ($fileOb->queryLogs() as $rev => $log) {
             $this->_patchsets[$rev] = array(
                 'date' => $log->queryDate(),
                 'author' => $log->queryAuthor(),
-                'branch' => '',
-                'tag' => '',
+                'branches' => $log->queryBranch(),
+                'tags' => $log->queryTags(),
                 'log' => $log->queryLog(),
                 'members' => array()
             );
 
-            foreach ($log->files as $file) {
-                $file = preg_replace('/.*?\s(.*?)(\s|$).*/', '\\1', $file);
+            $ps = &$this->_patchsets[$rev];
+
+            foreach ($log->queryFiles() as $file) {
                 $to = $rev;
+                $status = 0;
 
                 switch ($file['status']) {
                 case 'A':
-                    $from = 'INITIAL';
+                    $from = null;
+                    $status = self::INITIAL;
                     break;
 
                 case 'D':
                     $from = $to;
-                    $to = '(DEAD)';
+                    $to = null;
+                    $to = self::DEAD;
                     break;
 
                 default:
-                    // This technically isn't the previous revision,
-                    // but it works for diffing purposes.
-                    $from = $to - 1;
+                    $from = $log->queryParent();
+                    break;
                 }
 
-                $this->_patchsets[$rev]['members'][] = array(
-                    'file' => $file,
+                $ps['members'][] = array(
+                    'file' => $file['srcPath'],
                     'from' => $from,
+                    'status' => $status,
                     'to' => $to
                 );
             }
         }
-
-        return true;
     }
 
 }