Index: includes/api/ApiBase.php
===================================================================
--- includes/api/ApiBase.php	(revision 45687)
+++ includes/api/ApiBase.php	(working copy)
@@ -703,6 +703,7 @@
 		'missingparam' => array('code' => 'no$1', 'info' => "The \$1 parameter must be set"),
 		'invalidtitle' => array('code' => 'invalidtitle', 'info' => "Bad title ``\$1''"),
 		'nosuchpageid' => array('code' => 'nosuchpageid', 'info' => "There is no page with ID \$1"),
+		'nosuchrevid' => array('code' => 'nosuchrevid', 'info' => "There is no revision with ID \$1"),
 		'invaliduser' => array('code' => 'invaliduser', 'info' => "Invalid username ``\$1''"),
 		'invalidexpiry' => array('code' => 'invalidexpiry', 'info' => "Invalid expiry time ``\$1''"),
 		'pastexpiry' => array('code' => 'pastexpiry', 'info' => "Expiry time ``\$1'' is in the past"),
Index: includes/api/ApiQueryRevisions.php
===================================================================
--- includes/api/ApiQueryRevisions.php	(revision 45687)
+++ includes/api/ApiQueryRevisions.php	(working copy)
@@ -83,7 +83,9 @@
 		$enumRevMode = (!is_null($params['user']) || !is_null($params['excludeuser']) ||
 				!is_null($params['limit']) || !is_null($params['startid']) ||
 				!is_null($params['endid']) || $params['dir'] === 'newer' ||
-				!is_null($params['start']) || !is_null($params['end']));
+				!is_null($params['start']) || !is_null($params['end']) ||
+				$params['difftoprev'] || $params['difftotop']);
+		$this->enumRevMode = $enumRevMode;
 
 
 		$pageSet = $this->getPageSet();
@@ -98,11 +100,11 @@
 			$this->dieUsage('The revids= parameter may not be used with the list options (limit, startid, endid, dirNewer, start, end).', 'revids');
 
 		if ($pageCount > 1 && $enumRevMode)
-			$this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start and end parameters may only be used on a single page.', 'multpages');
+			$this->dieUsage('titles, pageids or a generator was used to supply multiple pages, but the limit, startid, endid, dirNewer, user, excludeuser, start, end, difftoprev and difftotop parameters may only be used on a single page.', 'multpages');
 
 		$this->addTables('revision');
-		$this->addFields( Revision::selectFields() );
-		$this->addTables( 'page' );
+		$this->addFields(Revision::selectFields());
+		$this->addTables('page');
 		$this->addWhere('page_id = rev_page');
 
 		$prop = array_flip($params['prop']);
@@ -116,12 +118,37 @@
 		$this->fld_size = isset ($prop['size']);
 		$this->fld_user = isset ($prop['user']);
 		$this->token = $params['token'];
+		
+		$this->diffto = $params['diffto'];
+		$this->difftoprev = $params['difftoprev'];
+		$this->difftotop = $params['difftotop'];
+		$this->diffprop = array_flip($params['diffprop']);
+		$this->needDiff = !is_null($this->diffto) || $this->difftoprev || $this->difftotop;
+		if($this->needDiff)
+			$this->formatter = new DiffFormatter;
+		if($this->difftoprev)
+			// Cache revision texts
+			$this->difftoprevCache = array();
+		if($this->difftotop)
+			$this->difftotopCached = null;
+		
+		if(!is_null($this->diffto)) {
+			global $wgContLang;
+			$diffToRev = Revision::newFromID($this->diffto);
+			if(!$diffToRev instanceof Revision)
+				$this->dieUsageMsg(array('nosuchrevid', $this->diffto));
+			$this->diffToText = $diffToRev->revText();
+			if($this->diffToText == '' && !$diffToRev->userCan(self::DELETED_TEXT))
+				// Deleted revision; fake non-existence
+				$this->dieUsageMsg(array('nosuchrevid', $this->diffto));
+			$this->diffToText = explode("\n", $wgContLang->segmentForDiff($this->diffToText));
+		}
 
-		if ( !is_null($this->token) || $pageCount > 0) {
+		if (!is_null($this->token) || $pageCount > 0) {
 			$this->addFields( Revision::selectPageFields() );
 		}
 
-		if (isset ($prop['content'])) {
+		if (isset ($prop['content']) || $this->needDiff) {
 
 			// For each page we will request, the user must have read rights for that page
 			foreach ($pageSet->getGoodTitles() as $title) {
@@ -136,7 +163,7 @@
 			$this->addFields('old_id');
 			$this->addFields( Revision::selectTextFields() );
 
-			$this->fld_content = true;
+			$this->fld_content = isset($prop['content']);
 
 			$this->expandTemplates = $params['expandtemplates'];
 			$this->generateXML = $params['generatexml'];
@@ -146,8 +173,8 @@
 				$this->section = false;
 		}
 
-		$userMax = ( $this->fld_content ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
-		$botMax  = ( $this->fld_content ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
+		$userMax = ( $this->fld_content || $this->needDiff ? ApiBase::LIMIT_SML1 : ApiBase::LIMIT_BIG1 );
+		$botMax  = ( $this->fld_content || $this->needDiff ? ApiBase::LIMIT_SML2 : ApiBase::LIMIT_BIG2 );
 		$limit = $params['limit'];
 		if( $limit == 'max' ) {
 			$limit = $this->getMain()->canApiHighLimits() ? $botMax : $userMax;
@@ -233,8 +260,8 @@
 		$res = $this->select(__METHOD__);
 
 		$db = $this->getDB();
+		global $wgContLang;
 		while ($row = $db->fetchObject($res)) {
-
 			if (++ $count > $limit) {
 				// We've reached the one extra which shows that there are additional pages to be had. Stop here...
 				if (!$enumRevMode)
@@ -242,33 +269,60 @@
 				$this->setContinueEnumParameter('startid', intval($row->rev_id));
 				break;
 			}
+		
+			$revision = new Revision( $row );
+			if ($this->difftoprev)
+				// Cache this revision's text
+				$this->difftoprevCache[$revision->getID()] = explode("\n", $wgContLang->segmentForDiff($revision->revText()));
 
-			$revision = new Revision( $row );
 			$this->getResult()->addValue(
 				array (
 					'query',
 					'pages',
 					$revision->getPage(),
 					'revisions'),
-				null,
+				$revision->getID(),
 				$this->extractRowInfo( $revision ));
 		}
 		$db->freeResult($res);
-
-		// Ensure that all revisions are shown as '<rev>' elements
+		
 		$result = $this->getResult();
-		if ($result->getIsRawMode()) {
-			$data =& $result->getData();
-			foreach ($data['query']['pages'] as & $page) {
-				if (is_array($page) && array_key_exists('revisions', $page)) {
-					$result->setIndexedTagName($page['revisions'], 'rev');
+		$data =& $result->getData();
+		$diffArr = array();
+		if($this->difftoprev)
+		{
+			ksort($this->difftoprevCache, SORT_NUMERIC);
+			$prevID = null;
+			foreach($this->difftoprevCache as $revid => $newText)
+			{
+				if(is_null($prevID))
+				{
+					// The first one doesn't have a previous
+					// one so it doesn't get a diff
+					$prevID = $revid;
+					continue;
 				}
+				$diff = new Diff($this->difftoprevCache[$prevID], $this->difftoprevCache[$revid]);
+				$diffArr[$revid] = $this->diffResult($diff, $prevID, $revid);
+				$prevID = $revid;
 			}
 		}
+
+		// Ensure that all revisions are shown as '<rev>' elements
+		// and add difftoprevs if needed
+		foreach ($data['query']['pages'] as & $page) {
+			if (is_array($page) && array_key_exists('revisions', $page)) {
+				$result->setIndexedTagName($page['revisions'], 'rev');
+				if($this->difftoprev)
+					foreach($page['revisions'] as $revid => &$rev)
+						if(isset($diffArr[$revid]))
+							$rev['difftoprev'] = $diffArr[$revid];
+			}
+		}
 	}
 
 	private function extractRowInfo( $revision ) {
-
+		global $wgContLang;
 		$vals = array ();
 
 		if ($this->fld_ids) {
@@ -299,7 +353,7 @@
 				$vals['comment'] = $comment;
 		}
 
-		if(!is_null($this->token) || ($this->fld_content && $this->expandTemplates))
+		if(!is_null($this->token) || ($this->fld_content && $this->expandTemplates) || $this->needDiff)
 			$title = $revision->getTitle();
 
 		if(!is_null($this->token))
@@ -314,10 +368,43 @@
 					$vals[$t . 'token'] = $val;
 			}
 		}
+		
+		if($this->needDiff)
+			$thisText = explode("\n", $wgContLang->segmentForDiff($revision->revText()));
+		
+		if(!is_null($this->diffto))
+		{
+			$diff = new Diff($thisText, $this->diffToText);
+			$vals['diffto'] = $this->diffResult($diff, $revision->getID(), $this->diffto); 
+		}
+		
+		if($this->difftotop)
+		{
+			if(!is_null($this->difftotopCached))
+			{
+				$topText = $this->difftotopCached;
+				$topID = $this->difftotopID;
+			}
+			else
+			{
+				if($revision->getID() == $title->getLatestRevID())
+					// This is the top revision
+					$topRev = $revision;
+				else
+					$topRev = Revision::newFromTitle($title);
+				$topText = explode("\n", $wgContLang->segmentForDiff($topRev->revText()));
+				$topID = $topRev->getID();
+				// Cache the top text and ID for later use
+				$this->difftotopCached = $topText;
+				$this->difftotopID = $topID;
+			}
+			$diff = new Diff($thisText, $topText);
+			$vals['difftotop'] = $this->diffResult($diff, $revision->getID(), $topID);
+		}
 
 		if ($this->fld_content) {
 			global $wgParser;
-			$text = $revision->getText();
+			$text = $revision->revText();
 			# Expand templates after getting section content because
 			# template-added sections don't count and Parser::preprocess()
 			# will have less input
@@ -344,6 +431,28 @@
 		}
 		return $vals;
 	}
+	
+	private function diffResult($diff, $fromID, $toID)
+	{
+		global $wgContLang;
+		$r = array();
+		if(isset($this->diffprop['revids']))
+		{
+			$r['from'] = $fromID;
+			$r['to'] = $toID;
+		}
+		if(isset($this->diffprop['diff']) ||
+				isset($this->diffprop['size']) ||
+				isset($this->diffprop['lines']))
+			$diff = $wgContLang->unsegmentForDiff($this->formatter->format($diff));
+		if(isset($this->diffprop['diff']))
+			ApiResult::setContent($r, $diff);
+		if(isset($this->diffprop['size']))
+			$r['size'] = strlen($diff);
+		if(isset($this->diffprop['lines']))
+			$r['lines'] = substr_count($diff, "\n");
+		return $r;
+	}
 
 	public function getAllowedParams() {
 		return array (
@@ -398,6 +507,21 @@
 				ApiBase :: PARAM_TYPE => array_keys($this->getTokenFunctions()),
 				ApiBase :: PARAM_ISMULTI => true
 			),
+			'diffto' => array(
+				ApiBase :: PARAM_TYPE => 'integer',
+			),
+			'difftoprev' => false,
+			'difftotop' => false,
+			'diffprop' => array(
+				ApiBase :: PARAM_ISMULTI => true,
+				ApiBase :: PARAM_DFLT => 'diff|lines|size|revids',
+				ApiBase :: PARAM_TYPE => array(
+					'diff',
+					'lines',
+					'size',
+					'revids',
+				)
+			),
 		);
 	}
 
@@ -416,6 +540,13 @@
 			'generatexml' => 'generate XML parse tree for revision content',
 			'section' => 'only retrieve the content of this section',
 			'token' => 'Which tokens to obtain for each revision',
+			'diffto' => 'Revision ID to diff each revision to',
+			'difftoprev' => array(
+				'Diff each revision to the previous (rvdir=older) or next (rvdir=newer) revision IN THE LIST (enum)',
+				'NOTE: This is not necessarily the previous/next revision when rvuser/rvexcludeuser is set',
+			),	
+			'difftotop' => 'Diff each revision to the page\'s current revision (enum)',
+			'diffprop' => 'Diff properties to return',
 		);
 	}
 
Index: RELEASE-NOTES
===================================================================
--- RELEASE-NOTES	(revision 45687)
+++ RELEASE-NOTES	(working copy)
@@ -46,6 +46,7 @@
   incorrectly
 * (bug 16858) Revamped list=deletedrevs to make listing deleted contributions
   and listing all deleted pages possible
+* (bug 13209) Added diff functionality to prop=revisions
 
 === Languages updated in 1.15 ===
 
