try {
$ps = $VC->getPatchsetObject($where);
+ $patchsets = $ps->getPatchsets();
} catch (Horde_Vcs_Exception $e) {
Chora::fatal($e);
}
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';
}
<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>
*/
abstract class Horde_Vcs_Patchset
{
+ const INITIAL = 1;
+ const DEAD = 2;
+
/**
* @var array
*/
/**
* TODO
+ *
+ * @return array TODO
+ * 'date'
+ * 'author'
+ * 'branches'
+ * 'tags'
+ * 'log'
+ * 'members' - array:
+ * 'file'
+ * 'from'
+ * 'to'
+ * 'status'
*/
public function getPatchsets()
{
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');
}
'';
$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;
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':
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";
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;
}
}
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;
$line = trim(fgets($pipe));
}
-
$log = '';
$line = fgets($pipe);
while (substr($line, 0, 1) != ':') {
return $this->_file->queryBranch($this->_rev);
}
+ /**
+ * TODO
+ */
+ public function queryFiles()
+ {
+ return $this->_files;
+ }
+
+ /**
+ * TODO
+ */
+ public function queryParent()
+ {
+ return $this->_parent;
+ }
+
}
/**
*/
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;
}
}