From 3bddf05c2cb35f3c0bbc39a91da06041f880aace Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Wed, 28 Jan 2009 13:15:06 -0700 Subject: [PATCH] Patchsets are working, at least for Git and CVS. --- chora/patchsets.php | 55 +++++++++++++------------ chora/templates/patchsets/ps.inc | 21 ++++------ framework/Vcs/lib/Horde/Vcs.php | 15 +++++++ framework/Vcs/lib/Horde/Vcs/Cvs.php | 56 +++++++++++++++---------- framework/Vcs/lib/Horde/Vcs/Git.php | 81 ++++++++++++++++++++++++++----------- 5 files changed, 143 insertions(+), 85 deletions(-) diff --git a/chora/patchsets.php b/chora/patchsets.php index 0feefa6ee..340701a1a 100644 --- a/chora/patchsets.php +++ b/chora/patchsets.php @@ -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 . ''; - } 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) . ''; + $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']) . ''; + + $file['file'] = Horde::link(Chora::url('patchsets', $member['file'])) . htmlspecialchars($member['file']) . ''; + if ($member['from'] == 'INITIAL') { $file['from'] = '' . _("New File") . ''; $file['diff'] = ''; } else { - $file['from'] = Horde::link(Chora::url('co', $mywhere, array('r' => $member['from']))) . htmlspecialchars($member['from']) . ''; - $file['diff'] = Horde::link(Chora::url('diff', $mywhere, array('r1' => $member['from'], 'r2' => $member['to'], 't' => 'unified'))) . ' ' . Horde::img('diff.png', _("Diff")) . ''; + $file['from'] = Horde::link(Chora::url('co', $member['file'], array('r' => $member['from']))) . htmlspecialchars($VC->abbrev($member['from'])) . ''; + $file['diff'] = Horde::link(Chora::url('diff', $member['file'], array('r1' => $member['from'], 'r2' => $member['to']))) . ' ' . $diff_img . ''; } + if (substr($member['to'], -6) == '(DEAD)') { $file['to'] = '' . _("Deleted") . ''; $file['diff'] = ''; } else { - $file['to'] = Horde::link(Chora::url('co', $mywhere, array('r' => $member['to']))) . htmlspecialchars($member['to']) . ''; + $file['to'] = Horde::link(Chora::url('co', $member['file'], array('r' => $member['to']))) . htmlspecialchars($VC->abbrev($member['to'])) . ''; } $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'; } diff --git a/chora/templates/patchsets/ps.inc b/chora/templates/patchsets/ps.inc index c2543b2f7..eb73615e7 100644 --- a/chora/templates/patchsets/ps.inc +++ b/chora/templates/patchsets/ps.inc @@ -4,22 +4,15 @@ -' . $logMessage . '

'; -$tags = array(); -if (isset($patchset['branch'])) { - $tags[] = htmlspecialchars($patchset['branch']); -} -if (isset($patchset['tag'])) { - $tags[] = htmlspecialchars($patchset['tag']); -} -if ($tags) echo '

' . _("Tags") . ': ' . implode(', ', $tags) . '

'; -?> +

+ +

+ diff --git a/framework/Vcs/lib/Horde/Vcs.php b/framework/Vcs/lib/Horde/Vcs.php index a3afc7753..2caaabe01 100644 --- a/framework/Vcs/lib/Horde/Vcs.php +++ b/framework/Vcs/lib/Horde/Vcs.php @@ -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() { diff --git a/framework/Vcs/lib/Horde/Vcs/Cvs.php b/framework/Vcs/lib/Horde/Vcs/Cvs.php index c2df4db7c..09a3f407f 100644 --- a/framework/Vcs/lib/Horde/Vcs/Cvs.php +++ b/framework/Vcs/lib/Horde/Vcs/Cvs.php @@ -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; } } diff --git a/framework/Vcs/lib/Horde/Vcs/Git.php b/framework/Vcs/lib/Horde/Vcs/Git.php index c7d249f06..6adb35832 100644 --- a/framework/Vcs/lib/Horde/Vcs/Git.php +++ b/framework/Vcs/lib/Horde/Vcs/Git.php @@ -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; } } -- 2.11.0