Skip to content

Add Client::fetchLog() #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Apr 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions src/Client.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,20 @@ public function fetchPatch($path, $r1, $r2)
return $this->fetch($url);
}

public function fetchLog($path, $revision = null)
{
$url = $path . '?view=log';

// TODO: invalid revision shows error page, but HTTP 200 OK

if ($revision !== null) {
$url .= (strpos($url, '?') === false) ? '?' : '&';
$url .= 'pathrev=' . $revision;
}

return $this->fetchXml($url)->then(array($this->parser, 'parseLogEntries'));
}

public function fetchRevisionPrevious($path, $revision)
{
return $this->fetchAllPreviousRevisions($path)->then(function ($revisions) use ($revision) {
Expand Down
99 changes: 99 additions & 0 deletions src/Io/Parser.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,105 @@ public function parseLogRevisions(SimpleXMLElement $xml)
return $revisions;
}

/**
* Parse log entries from given XML document
*
* @param SimpleXMLElement $xml
* @throws \UnexpectedValueException
* @return array
* @link https://gforge.inria.fr/scm/viewvc/viewvc.org/template-authoring-guide.html#variables-log
*/
public function parseLogEntries(SimpleXMLElement $xml)
{
$entries = array();

foreach ($xml->xpath('//div[pre]') as $div) {
/* @var $div SimpleXMLElement */

// skip "(vendor branch)" "em" tag if found
$off = ((string)$div->em[0] === '(vendor branch)') ? 1 : 0;

$entry = array(
// revision is first "strong" element (subversion wraps this in "a" element)
'revision' => (string)$this->first($div->xpath('.//strong[1]')),
// date is in first "em" element
'date' => new \DateTime((string)$div->em[0 + $off]),
// author is in second "em" element
'author' => (string)$div->em[1 + $off],
// message is in only "pre" element
'message' => (string)$div->pre
);

// ease parsing each line by splitting on "br" element, skip static rows for revision/date
$parts = explode('<br />', substr($div->asXML(), 5, -6));
unset($parts[0], $parts[1]);

foreach ($parts as $part) {
$part = new SimpleXMLElement('<body>' . $part . '</body>');
$str = (string)$part;

if (substr($str, 0, 7) === 'Diff to') {
$value = array();

foreach ($part->xpath('.//a') as $a) {
$text = (string)$a;
$pos = strrpos($text, ' ');

// text should be "previous X.Y", otherwise ignore "(colored)" with no blank
if ($pos !== false) {
$value[substr($text, 0, $pos)] = substr($text, $pos + 1);
}
}

$entry['diff'] = $value;
} elseif (substr($str, 0, 7) === 'Branch:' || substr($str, 0, 9) === 'CVS Tags:' || substr($str, 0, 17) === 'Branch point for:') {
$value = array();

foreach ($part->xpath('.//a/strong') as $a) {
$value []= (string)$a;
}

$key = $str[0] === 'B' ? ($str[6] === ':' ? 'branches' : 'branchpoints') : 'tags';
$entry[$key] = $value;
} elseif (substr($str, 0, 13) === 'Changes since') {
// "strong" element contains "X.Y: +1 -2 lines"
$value = (string)$part->strong;
$pos = strpos($value, ':');

// previous revision is before colon
$entry['previous'] = substr($value, 0, $pos);

// changes are behind colon
$entry['changes'] = substr($value, $pos + 2);
} elseif (substr($str, 0, 14) === 'Original Path:') {
$entry['original'] = (string)$part->a->em;
} elseif (substr($str, 0, 12) === 'File length:') {
$entry['size'] = (int)substr($str, 13);
} elseif (isset($part->strong->em) && (string)$part->strong->em === 'FILE REMOVED') {
$entry['deleted'] = true;
}
}

// previous is either set via "changes since" or link to "diff to" previous
if (isset($entry['diff']['previous'])) {
$entry['previous'] = $entry['diff']['previous'];
}

if ($off) {
$entry['vendor'] = true;
}

$entries []= $entry;
}

return $entries;
}

private function first(array $a)
{
return $a[0];
}

private function linkParameters($href)
{
$args = array();
Expand Down
9 changes: 9 additions & 0 deletions tests/ClientTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ public function testFetchDirectoryRevisionAttic()
$this->expectPromiseReject($promise);
}

public function testFetchLogRevision()
{
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/README.md?view=log&pathrev=1.0'))->will($this->returnValue($this->createPromiseRejected()));

$promise = $this->client->fetchLog('/README.md', '1.0');

$this->expectPromiseReject($promise);
}

public function testFetchPatch()
{
$this->browser->expects($this->once())->method('get')->with($this->equalTo('http://viewvc.example.org/README.md?view=patch&r1=1.0&r2=1.1'))->will($this->returnValue($this->createPromiseRejected()));
Expand Down
65 changes: 65 additions & 0 deletions tests/Io/ParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,71 @@ public function testDirectoryListing()
$this->assertEquals(array('images/', 'stylesheets/', 'index.xml', 'velocity.properties'), $files);
}

public function testLogSubversion()
{
$xml = $this->loadXml('is-a-file.html');

$log = $this->parser->parseLogEntries($xml);

$this->assertCount(3, $log);

// second entry has previous
$entry = $log[1];
$this->assertEquals('168695', $entry['revision']);
$this->assertEquals(new DateTime('Tue Jun 22 02:15:08 1999 UTC'), $entry['date']);
$this->assertEquals('(unknown author)', $entry['author']);
$this->assertEquals('168694', $entry['previous']);
$this->assertEquals('jakarta/ecs/branches/ecs/src/java/org/apache/ecs/AlignType.java', $entry['original']);
$this->assertEquals(4131, $entry['size']);

// last entry has no previous
$entry = $log[2];
$this->assertEquals('168694', $entry['revision']);
$this->assertEquals(new DateTime('Tue Jun 22 02:15:08 1999 UTC'), $entry['date']);
$this->assertEquals('jonbolt', $entry['author']);
$this->assertFalse(isset($entry['previous']));
}

public function testLogCvsBranches()
{
$xml = $this->loadXml('log-file-cvs-branches.html');

$log = $this->parser->parseLogEntries($xml);

$this->assertCount(4, $log);

// second entry is on vendor branch
$entry = $log[1];
$this->assertEquals('1.1.1.2', $entry['revision']);
$this->assertEquals(new DateTime('Tue Nov 5 05:48:37 2002 UTC'), $entry['date']);
$this->assertEquals('abcosico', $entry['author']);
$this->assertEquals('1.1.1.1', $entry['previous']);
$this->assertEquals(array('ICIS', 'IRRI', 'MAIN', 'avendor', 'bbu'), $entry['branches']);
$this->assertEquals(array('arelease', 'v1'), $entry['tags']);
$this->assertTrue(isset($entry['vendor']));

// last entry is branchpoint for all branches and is not on vendor branch
$entry = $log[3];
$this->assertEquals('1.1', $entry['revision']);
$this->assertEquals(array('ICIS', 'IRRI', 'MAIN', 'avendor', 'bbu'), $entry['branchpoints']);
$this->assertFalse(isset($entry['vendor']));
}

public function testLogCvsDeleted()
{
$xml = $this->loadXml('log-file-cvs-deleted.html');

$log = $this->parser->parseLogEntries($xml);

$this->assertCount(6, $log);

// second entry is a delete
$entry = $log[1];
$this->assertEquals('1.5', $entry['revision']);
$this->assertEquals('1.4', $entry['previous']);
$this->assertTrue($entry['deleted']);
}

private function loadXml($file)
{
return $this->loader->loadXmlFile(__DIR__ . '/../fixtures/' . $file);
Expand Down
Loading