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 + + + + + + +
+
+ +ViewVC Help +
+ +
+ + + +View File | + +Revision Log + +| Show Annotations + + + + + + +| Root Listing + +
+
+root/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. + +

+
+ + + + + + + + + +
  + +Diffs between + + + + +and + + + + +
  +Type of Diff should be a + + +
+
+
+ + +
+ + + + 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 + + + + + + +
+ + + +
/[gentoo-projects]/www-redesign/www/index.html +
+ +
+
Gentoo
+

Log of /www-redesign/www/index.html

+ +

+ +Parent Directory Parent Directory + +| Revision Log Revision Log + + + + +

+ +
+ + + + + + + + + + + +
Links to HEAD: +(view) +(download) +(as text) +(annotate) +
+ + + + + + + + +
+
+ + + + + +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. + +

+
+ + + + + + + + + +
  + +Diffs between + + + + +and + + + + +
  +Type of Diff should be a + + +
+
+ + + +
+
+
+ + +Sort log by: + + +
+
+ +
+ +Sticky Tag: +
+
+ + + + + + +
+
+ + + + +
+ + + + + + + + + +
 ViewVC Help
Powered by ViewVC 1.1.20 
+ + + + 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