diff --git a/src/Client.php b/src/Client.php
index f24a645..78f7678 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -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) {
diff --git a/src/Io/Parser.php b/src/Io/Parser.php
index ea12c8a..b49fb48 100644
--- a/src/Io/Parser.php
+++ b/src/Io/Parser.php
@@ -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(' ', substr($div->asXML(), 5, -6));
+ unset($parts[0], $parts[1]);
+
+ foreach ($parts as $part) {
+ $part = new SimpleXMLElement('
' . $part . '');
+ $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();
diff --git a/tests/ClientTest.php b/tests/ClientTest.php
index 8d9cefe..4684f13 100644
--- a/tests/ClientTest.php
+++ b/tests/ClientTest.php
@@ -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()));
diff --git a/tests/Io/ParserTest.php b/tests/Io/ParserTest.php
index b533190..88598e2 100644
--- a/tests/Io/ParserTest.php
+++ b/tests/Io/ParserTest.php
@@ -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);
diff --git a/tests/fixtures/log-file-cvs-branches.html b/tests/fixtures/log-file-cvs-branches.html
new file mode 100644
index 0000000..668af0e
--- /dev/null
+++ b/tests/fixtures/log-file-cvs-branches.html
@@ -0,0 +1,594 @@
+
+
+
+
+
+
+
+
+[ViewVC] Log of: flash/GMS.swf
+
+
+
+
+
+
+
+
+
+
+
+
+Default branch:
+ICIS ,
+IRRI ,
+avendor ,
+bbu
+
+
+
+
+
+
+Links to HEAD:
+
+(view )
+
+
+(annotate )
+
+
+
+
+
+
+Sticky Tag:
+
+
+
+
+
+
+Sort logs by:
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Revision
1.1.1.3 -
+
+
+(
view )
+
+
+
+
+(
annotate )
+
+
+
+-
[select for diffs]
+
+
+
+
+
(vendor branch)
+
+
+
+
Mon Jan 6 03:32:58 2003 UTC
+(12 years, 3 months ago)
+by
abcosico
+
+
+
Branch:
+
+
ICIS ,
+
+
IRRI ,
+
+
MAIN ,
+
+
avendor ,
+
+
bbu
+
+
+
+
CVS Tags:
+
+
HEAD ,
+
+
HEAD_NEW ,
+
+
dec-2002
+
+
+
+
+
+
+
Changes since
1.1.1.2: +270 -241 lines
+
+
+
+
+
+
+
+
+
Diff to
previous 1.1.1.2
+
+
+
+
+
+
+
+
+
+
+
no message
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Revision
1.1.1.2 -
+
+
+(
view )
+
+
+
+
+(
annotate )
+
+
+
+-
[select for diffs]
+
+
+
+
+
(vendor branch)
+
+
+
+
Tue Nov 5 05:48:37 2002 UTC
+(12 years, 5 months ago)
+by
abcosico
+
+
+
Branch:
+
+
ICIS ,
+
+
IRRI ,
+
+
MAIN ,
+
+
avendor ,
+
+
bbu
+
+
+
+
CVS Tags:
+
+
arelease ,
+
+
v1
+
+
+
+
+
+
+
Changes since
1.1.1.1: +241 -267 lines
+
+
+
+
+
+
+
+
+
Diff to
previous 1.1.1.1
+
+
+
+
+
+
+
+
+
+
+
no message
+
+
+
+
+
+
+
+
+
+
+
+
+Revision
1.1.1.1 -
+
+
+(
view )
+
+
+
+
+(
annotate )
+
+
+
+-
[select for diffs]
+
+
+
+
+
(vendor branch)
+
+
+
+
Wed Oct 30 07:15:23 2002 UTC
+(12 years, 5 months ago)
+by
abcosico
+
+
+
Branch:
+
+
ICIS ,
+
+
IRRI ,
+
+
MAIN ,
+
+
avendor ,
+
+
bbu
+
+
+
+
+
+
+
+
Changes since
1.1: +0 -0 lines
+
+
+
+
+
+
+
+
+
Diff to
previous 1.1
+
+
+
+
+
+
+
+
+
+
+
no message
+
+
+
+
+
+
+
+
+
+
+
+
+
+Revision
1.1 -
+
+
+(
view )
+
+
+
+
+(
annotate )
+
+
+
+-
[select for diffs]
+
+
+
+
+
+
+
Wed Oct 30 07:15:23 2002 UTC
+(12 years, 5 months ago)
+by
abcosico
+
+
+
+
+
Branch point for:
+
+
ICIS ,
+
+
IRRI ,
+
+
MAIN ,
+
+
avendor ,
+
+
bbu
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Initial revision
+
+
+
+
+
+
+
+
+
+
This form allows you to request diffs between any two
+revisions of this file.
+For each of the two "sides" of the diff,
+
+select a symbolic revision name using the selection box, or choose
+'Use Text Field' and enter a numeric revision.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/fixtures/log-file-cvs-branches.txt b/tests/fixtures/log-file-cvs-branches.txt
new file mode 100644
index 0000000..fbefe1f
--- /dev/null
+++ b/tests/fixtures/log-file-cvs-branches.txt
@@ -0,0 +1,5 @@
+Description:
+Another log file view, includes some tags, branches and vendor branches
+
+Obtained from:
+http://www.bioinformatics.org/cgi-bin/viewvc.cgi/flash/GMS.swf?view=log
\ No newline at end of file
diff --git a/tests/fixtures/log-file-cvs-deleted.html b/tests/fixtures/log-file-cvs-deleted.html
new file mode 100644
index 0000000..58f3df2
--- /dev/null
+++ b/tests/fixtures/log-file-cvs-deleted.html
@@ -0,0 +1,566 @@
+
+
+
+
+
+
+
+
+[gentoo-projects] Log of /www-redesign/www/index.html
+
+
+
+
+
+
+
+
+Log of /www-redesign/www/index.html
+
+
+
+ Parent Directory
+
+| Revision Log
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+Revision
1.6 -
+
+
+(
view )
+
+
+(
download )
+(
as text )
+(
annotate )
+
+
+
+-
[select for diffs]
+
+
+
+
+
+
+
Fri Jul 22 18:49:10 2005 UTC
+(9 years, 8 months ago)
+by
curtis119
+
+
+
Branch:
+
+
MAIN
+
+
+
+
CVS Tags:
+
+
HEAD
+
+
+
+
+
+
+
Changes since
1.5: +0 -0 lines
+
+
+
+
+
+
+
+
+
Diff to
previous 1.5
+
+
+
+
+
+
+
+
+
+
+
adding
+
+
+
+
+
+
+
+
+
+Revision
1.5
+
+
+
+
+
Thu Jul 21 01:01:03 2005 UTC
+(9 years, 8 months ago)
+by
curtis119
+
+
+
Branch:
+
+
MAIN
+
+
+
+
+
+
+
+
Changes since
1.4: +0 -0 lines
+
+
+
+
+
+
+
FILE REMOVED
+
+
removing
+
+
+
+
+
+
+
+
+
+
+
+
+Revision
1.4 -
+
+
+(
view )
+
+
+(
download )
+(
as text )
+(
annotate )
+
+
+
+-
[select for diffs]
+
+
+
+
+
+
+
Wed Jan 19 21:03:04 2005 UTC
+(10 years, 2 months ago)
+by
swift
+
+
+
Branch:
+
+
MAIN
+
+
+
+
+
+
+
+
Changes since
1.3: +80 -287 lines
+
+
+
+
+
+
+
+
+
Diff to
previous 1.3
+
+
+
+
+
+
+
+
+
+
+
Aarons back ;)
+
+
+
+
+
+
+
+
+
+
+
+
+Revision
1.3 -
+
+
+(
view )
+
+
+(
download )
+(
as text )
+(
annotate )
+
+
+
+-
[select for diffs]
+
+
+
+
+
+
+
Tue Jan 11 11:00:16 2005 UTC
+(10 years, 2 months ago)
+by
swift
+
+
+
Branch:
+
+
MAIN
+
+
+
+
+
+
+
+
Changes since
1.2: +94 -17 lines
+
+
+
+
+
+
+
+
+
Diff to
previous 1.2
+
+
+
+
+
+
+
+
+
+
+
Adding in Michael his stuff
+
+
+
+
+
+
+
+
+
+
+
+
+Revision
1.2 -
+
+
+(
view )
+
+
+(
download )
+(
as text )
+(
annotate )
+
+
+
+-
[select for diffs]
+
+
+
+
+
+
+
Sat Jan 1 20:27:06 2005 UTC
+(10 years, 3 months ago)
+by
swift
+
+
+
Branch:
+
+
MAIN
+
+
+
+
+
+
+
+
Changes since
1.1: +149 -41 lines
+
+
+
+
+
+
+
+
+
Diff to
previous 1.1
+
+
+
+
+
+
+
+
+
+
+
Some internal coding indentation; doesnt seem to affect firefox, lynx or links
+
+
+
+
+
+
+
+
+
+
+
+
+Revision
1.1 -
+
+
+(
view )
+
+
+(
download )
+(
as text )
+(
annotate )
+
+
+
+-
[select for diffs]
+
+
+
+
+
+
+
Sat Jan 1 19:58:53 2005 UTC
+(10 years, 3 months ago)
+by
swift
+
+
+
Branch:
+
+
MAIN
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Adding index page
+
+
+
+
+
+
+
+
+
+This form allows you to request diffs between any two revisions of this file.
+For each of the two "sides" of the diff,
+
+select a symbolic revision name using the selection box, or choose
+'Use Text Field' and enter a numeric revision.
+
+
+
+
+
+
+
+
+
+
+Sticky Tag:
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/fixtures/log-file-cvs-deleted.txt b/tests/fixtures/log-file-cvs-deleted.txt
new file mode 100644
index 0000000..0ce4561
--- /dev/null
+++ b/tests/fixtures/log-file-cvs-deleted.txt
@@ -0,0 +1,5 @@
+Description:
+Another CVS log that includes a deleted file
+
+Source:
+https://sources.gentoo.org/cgi-bin/viewvc.cgi/gentoo-projects/www-redesign/www/index.html?hideattic=1&view=log
\ No newline at end of file