r54192 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r54191‎ | r54192 | r54193 >
Date:06:59, 2 August 2009
Author:brion
Status:ok
Tags:
Comment:
Merge trunk updates to CodeReview through r54191 -- includes test case integration, subdirectory splits, and change logging view
Modified paths:
  • /branches/wmf-deployment/extensions/CodeReview (modified) (history)
  • /branches/wmf-deployment/extensions/CodeReview/ApiCodeComments.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/ApiCodeDiff.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/ApiCodeUpdate.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeAuthorListView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeComment.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeCommentsListView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodePropChange.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeReleaseNotes.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRepoListView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRepository.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeReview.alias.php (modified) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeReview.i18n.php (modified) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeReview.php (modified) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRevision.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRevisionAuthorLink.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRevisionAuthorView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRevisionCommitter.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRevisionListView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRevisionStatusView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRevisionTagView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeRevisionView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeStatusListView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/CodeTagListView.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/DiffHighlighter.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/SpecialCode.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/SpecialRepoAdmin.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/Subversion.php (deleted) (history)
  • /branches/wmf-deployment/extensions/CodeReview/api (added) (history)
  • /branches/wmf-deployment/extensions/CodeReview/archives/code_relations_index.sql (modified) (history)
  • /branches/wmf-deployment/extensions/CodeReview/archives/codereview-code_tests.sql (added) (history)
  • /branches/wmf-deployment/extensions/CodeReview/backend (added) (history)
  • /branches/wmf-deployment/extensions/CodeReview/codereview.css (modified) (history)
  • /branches/wmf-deployment/extensions/CodeReview/codereview.sql (modified) (history)
  • /branches/wmf-deployment/extensions/CodeReview/ui (added) (history)

Diff [purge]

Index: branches/wmf-deployment/extensions/CodeReview/CodeReleaseNotes.php
@@ -1,168 +0,0 @@
2 -<?php
3 -
4 -class CodeReleaseNotes extends CodeView {
5 - function __construct( $repoName ) {
6 - global $wgRequest, $wgWikiSVN, $IP;
7 - parent::__construct( $repoName );
8 - $this->mRepo = CodeRepository::newFromName( $repoName );
9 - $this->mPath = htmlspecialchars( trim( $wgRequest->getVal( 'path' ) ) );
10 - if ( strlen( $this->mPath ) && $this->mPath[0] !== '/' ) {
11 - $this->mPath = "/{$this->mPath}"; // make sure this is a valid path
12 - }
13 - $this->mPath = preg_replace( '/\/$/', '', $this->mPath ); // kill last slash
14 - $this->mStartRev = $wgRequest->getIntOrNull( 'startrev' );
15 - $this->mEndRev = $wgRequest->getIntOrNull( 'endrev' );
16 - # Default start rev to last live one if possible
17 - if ( !$this->mStartRev && $this->mRepo && $this->mRepo->getName() == $wgWikiSVN ) {
18 - $this->mStartRev = SpecialVersion::getSvnRevision( $IP ) + 1;
19 - }
20 - }
21 -
22 - function execute() {
23 - if ( !$this->mRepo ) {
24 - $view = new CodeRepoListView();
25 - $view->execute();
26 - return;
27 - }
28 - $this->showForm();
29 - # Sanity/performance check...
30 - $lastRev = $this->mRepo->getLastStoredRev();
31 - if ( $this->mStartRev < ( $lastRev - 3000 ) )
32 - $this->mStartRev = NULL;
33 - # Show notes if we have at least a starting revision
34 - if ( $this->mStartRev ) {
35 - $this->showReleaseNotes();
36 - }
37 - }
38 -
39 - protected function showForm() {
40 - global $wgOut, $wgScript, $wgUser;
41 - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/releasenotes' );
42 - $wgOut->addHTML(
43 - Xml::openElement( 'form', array( 'action' => $wgScript, 'method' => 'get' ) ) .
44 - "<fieldset><legend>" . wfMsgHtml( 'code-release-legend' ) . "</legend>" .
45 - Xml::hidden( 'title', $special->getPrefixedDBKey() ) . '<b>' .
46 - Xml::inputlabel( wfMsg( "code-release-startrev" ), 'startrev', 'startrev', 10, $this->mStartRev ) .
47 - '</b>&nbsp;' .
48 - Xml::inputlabel( wfMsg( "code-release-endrev" ), 'endrev', 'endrev', 10, $this->mEndRev ) .
49 - '&nbsp;' .
50 - Xml::inputlabel( wfMsg( "code-pathsearch-path" ), 'path', 'path', 45, $this->mPath ) .
51 - '&nbsp;' .
52 - Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
53 - "</fieldset>" . Xml::closeElement( 'form' )
54 - );
55 - }
56 -
57 - protected function showReleaseNotes() {
58 - global $wgOut;
59 - $linker = new CodeCommentLinkerHtml( $this->mRepo );
60 - $dbr = wfGetDB( DB_SLAVE );
61 - if ( $this->mEndRev ) {
62 - $where = 'cr_id BETWEEN ' . intval( $this->mStartRev ) . ' AND ' . intval( $this->mEndRev );
63 - } else {
64 - $where = 'cr_id >= ' . intval( $this->mStartRev );
65 - }
66 - if ( $this->mPath ) {
67 - $where .= ' AND (cr_path LIKE ' . $dbr->addQuotes( $dbr->escapeLike( "{$this->mPath}/" ) . '%' );
68 - $where .= ' OR cr_path = ' . $dbr->addQuotes( $this->mPath ) . ')';
69 - }
70 - # Select commits within this range...
71 - $res = $dbr->select( array( 'code_rev', 'code_tags' ),
72 - array( 'cr_message', 'cr_author', 'cr_id', 'ct_tag AS rnotes' ),
73 - array(
74 - 'cr_repo_id' => $this->mRepo->getId(), // this repo
75 - "cr_status NOT IN('reverted','deferred','fixme')", // not reverted/deferred/fixme
76 - "cr_message != ''",
77 - $where // in range
78 - ),
79 - __METHOD__,
80 - array( 'ORDER BY' => 'cr_id DESC' ),
81 - array( 'code_tags' => array( 'LEFT JOIN', # Tagged for release notes?
82 - 'ct_repo_id = cr_repo_id AND ct_rev_id = cr_id AND ct_tag = "release-notes"' )
83 - )
84 - );
85 - $wgOut->addHTML( '<ul>' );
86 - # Output any relevant seeming commits...
87 - while ( $row = $dbr->fetchObject( $res ) ) {
88 - $summary = htmlspecialchars( $row->cr_message );
89 - # Add this commit summary if needed.
90 - if ( $row->rnotes || $this->isRelevant( $summary ) ) {
91 - # Keep it short if possible...
92 - $summary = $this->shortenSummary( $summary );
93 - # Anything left? (this can happen with some heuristics)
94 - if ( $summary ) {
95 - $summary = str_replace( "\n", "<br/>", $summary ); // Newlines -> <br/>
96 - $wgOut->addHTML( "<li>" );
97 - $wgOut->addHTML(
98 - $linker->link( $summary ) . " <i>(" . htmlspecialchars( $row->cr_author ) .
99 - ', ' . $linker->link( "r{$row->cr_id}" ) . ")</i>"
100 - );
101 - $wgOut->addHTML( "</li>\n" );
102 - }
103 - }
104 - }
105 - $wgOut->addHTML( '</ul>' );
106 - }
107 -
108 - private function shortenSummary( $summary, $first = true ) {
109 - # Astericks often used for point-by-point bullets
110 - if ( preg_match( '/(^|\n) ?\*/', $summary ) ) {
111 - $blurbs = explode( '*', $summary );
112 - # Double newlines separate importance generally
113 - } else if ( strpos( $summary, "\n\n" ) !== false ) {
114 - $blurbs = explode( "\n\n", $summary );
115 - } else {
116 - return trim( $summary );
117 - }
118 - $blurbs = array_map( 'trim', $blurbs ); # Clean up items
119 - $blurbs = array_filter( $blurbs ); # Filter out any garbage
120 - # Doesn't start with '*' and has some length?
121 - # If so, then assume that the top bit is important.
122 - if ( count( $blurbs ) ) {
123 - $header = strpos( ltrim( $summary ), '*' ) !== 0 && str_word_count( $blurbs[0] ) >= 5;
124 - } else {
125 - $header = false;
126 - }
127 - # Keep it short if possible...
128 - if ( count( $blurbs ) > 1 ) {
129 - $summary = array();
130 - foreach ( $blurbs as $blurb ) {
131 - # Always show the first bit
132 - if ( $header && $first && count( $summary ) == 0 ) {
133 - $summary[] = $this->shortenSummary( $blurb, true );
134 - # Is this bit important? Does it mention a revision?
135 - } else if ( $this->isRelevant( $blurb ) || preg_match( '/\br(\d+)\b/', $blurb ) ) {
136 - $bit = $this->shortenSummary( $blurb, false );
137 - if ( $bit ) $summary[] = $bit;
138 - }
139 - }
140 - $summary = implode( "\n", $summary );
141 - } else {
142 - $summary = implode( "\n", $blurbs );
143 - }
144 - return $summary;
145 - }
146 -
147 - // Quick relevance tests (these *should* be over-inclusive a little if anything)
148 - private function isRelevant( $summary, $whole = true ) {
149 - # Fixed a bug? Mentioned a config var?
150 - if ( preg_match( '/\b(bug #?(\d+)|\$[we]g[0-9a-z]{3,50})\b/i', $summary ) )
151 - return true;
152 - # Sanity check: summary cannot be *too* short to be useful
153 - $words = str_word_count( $summary );
154 - if ( mb_strlen( $summary ) < 40 || $words <= 5 )
155 - return false;
156 - # All caps words (like "BREAKING CHANGE"/magic words)?
157 - if ( preg_match( '/\b[A-Z]{6,30}\b/', $summary ) )
158 - return true;
159 - # Random keywords
160 - if ( preg_match( '/\b(wiki|HTML\d|CSS\d|UTF-?8|(Apache|PHP|CGI|Java|Perl|Python|\w+SQL) ?\d?\.?\d?)\b/i', $summary ) )
161 - return true;
162 - # Are we looking at the whole summary or an aspect of it?
163 - if ( $whole ) {
164 - return preg_match( '/(^|\n) ?\*/', $summary ); # List of items?
165 - } else {
166 - return true;
167 - }
168 - }
169 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionView.php
@@ -1,570 +0,0 @@
2 -<?php
3 -
4 -// Special:Code/MediaWiki/40696
5 -class CodeRevisionView extends CodeView {
6 -
7 - function __construct( $repoName, $rev, $replyTarget = null ) {
8 - global $wgRequest;
9 - parent::__construct();
10 - $this->mRepo = CodeRepository::newFromName( $repoName );
11 - $this->mRev = $this->mRepo ? $this->mRepo->getRevision( intval( $rev ) ) : null;
12 - $this->mPreviewText = false;
13 - # URL params...
14 - $this->mAddTags = $wgRequest->getText( 'wpTag' );
15 - $this->mRemoveTags = $wgRequest->getText( 'wpRemoveTag' );
16 - $this->mStatus = $wgRequest->getText( 'wpStatus' );
17 - $this->jumpToNext = $wgRequest->getCheck( 'wpSaveAndNext' );
18 - $this->mReplyTarget = $replyTarget ?
19 - (int)$replyTarget : $wgRequest->getIntOrNull( 'wpParent' );
20 - $this->text = $wgRequest->getText( "wpReply{$this->mReplyTarget}" );
21 - $this->mSkipCache = ( $wgRequest->getVal( 'action' ) == 'purge' );
22 - # Make tag arrays
23 - $this->mAddTags = $this->splitTags( $this->mAddTags );
24 - $this->mRemoveTags = $this->splitTags( $this->mRemoveTags );
25 - }
26 -
27 - function execute() {
28 - global $wgOut, $wgUser, $wgLang;
29 - if ( !$this->mRepo ) {
30 - $view = new CodeRepoListView();
31 - $view->execute();
32 - return;
33 - }
34 - if ( !$this->mRev ) {
35 - $view = new CodeRevisionListView( $this->mRepo->getName() );
36 - $view->execute();
37 - return;
38 - }
39 - $this->mStatus = $this->mStatus ? $this->mStatus : $this->mRev->getStatus();
40 -
41 - $redirectOnPost = $this->checkPostings();
42 - if ( $redirectOnPost ) {
43 - $wgOut->redirect( $redirectOnPost );
44 - return;
45 - }
46 -
47 - $wgOut->setPageTitle( wfMsgHtml('code-rev-title',$this->mRev->getId()) );
48 -
49 - $repoLink = $this->mSkin->link( SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ),
50 - htmlspecialchars( $this->mRepo->getName() ) );
51 - $revText = $this->navigationLinks();
52 - $paths = '';
53 - $modifiedPaths = $this->mRev->getModifiedPaths();
54 - foreach ( $modifiedPaths as $row ) {
55 - $paths .= $this->formatPathLine( $row->cp_path, $row->cp_action );
56 - }
57 - if ( $paths ) {
58 - $paths = "<div class='mw-codereview-paths'><ul>\n$paths</ul></div>\n";
59 - }
60 - $comments = $this->formatComments();
61 - $commentsLink = "";
62 - if ( $comments ) {
63 - $commentsLink = " (<a href=\"#code-comments\">" . wfMsgHtml( 'code-comments' ) . "</a>)\n";
64 - }
65 - $fields = array(
66 - 'code-rev-repo' => $repoLink,
67 - 'code-rev-rev' => $revText,
68 - 'code-rev-date' => $wgLang->timeanddate( $this->mRev->getTimestamp(), true ),
69 - 'code-rev-author' => $this->authorLink( $this->mRev->getAuthor() ),
70 - 'code-rev-status' => $this->statusForm() . $commentsLink,
71 - 'code-rev-tags' => $this->tagForm(),
72 - 'code-rev-message' => $this->formatMessage( $this->mRev->getMessage() ),
73 - 'code-rev-paths' => $paths,
74 - );
75 - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mRev->getId() );
76 -
77 - $html = Xml::openElement( 'form', array( 'action' => $special->getLocalUrl(), 'method' => 'post' ) );
78 -
79 - if ( $wgUser->isAllowed( 'codereview-post-comment' ) ) {
80 - $html .= $this->addActionButtons();
81 - }
82 -
83 - $html .= $this->formatMetaData( $fields );
84 - # Output diff
85 - if ( $this->mRev->isDiffable() ) {
86 - $diffHtml = $this->formatDiff();
87 - $html .=
88 - "<h2>" . wfMsgHtml( 'code-rev-diff' ) .
89 - ' <small>[' . $this->mSkin->makeLinkObj( $special,
90 - wfMsg( 'code-rev-purge-link' ), 'action=purge' ) . ']</small></h2>' .
91 - "<div class='mw-codereview-diff' id='mw-codereview-diff'>" . $diffHtml . "</div>\n";
92 - $html .= $this->formatImgDiff();
93 - }
94 - # Show code relations
95 - $relations = $this->formatReferences();
96 - if ( $relations ) {
97 - $html .= "<h2 id='code-references'>" . wfMsgHtml( 'code-references' ) .
98 - "</h2>\n" . $relations;
99 - }
100 - # Add revision comments
101 - if ( $comments ) {
102 - $html .= "<h2 id='code-comments'>" . wfMsgHtml( 'code-comments' ) .
103 - "</h2>\n" . $comments;
104 - }
105 -
106 - if ( $this->mReplyTarget ) {
107 - global $wgJsMimeType;
108 - $id = intval( $this->mReplyTarget );
109 - $html .= "<script type=\"$wgJsMimeType\">addOnloadHook(function(){" .
110 - "document.getElementById('wpReplyTo$id').focus();" .
111 - "});</script>\n";
112 - }
113 -
114 - if ( $wgUser->isAllowed( 'codereview-post-comment' ) ) {
115 - $html .= $this->addActionButtons();
116 - }
117 -
118 - $changes = $this->formatPropChanges();
119 - if ( $changes ) {
120 - $html .= "<h2 id='code-changes'>" . wfMsgHtml( 'code-prop-changes' ) . "</h2>\n" . $changes;
121 - }
122 - $html .= xml::closeElement( 'form' );
123 -
124 - $wgOut->addHTML( $html );
125 - }
126 -
127 - protected function navigationLinks() {
128 - global $wgLang;
129 -
130 - $rev = $this->mRev->getId();
131 - $prev = $this->mRev->getPrevious();
132 - $next = $this->mRev->getNext();
133 - $repo = $this->mRepo->getName();
134 -
135 - $links = array();
136 -
137 - if ( $prev ) {
138 - $prevTarget = SpecialPage::getTitleFor( 'Code', "$repo/$prev" );
139 - $links[] = '&lt;&nbsp;' . $this->mSkin->link( $prevTarget, "r$prev" );
140 - }
141 -
142 - $revText = "<b>r$rev</b>";
143 - $viewvc = $this->mRepo->getViewVcBase();
144 - if ( $viewvc ) {
145 - $url = htmlspecialchars( "$viewvc/?view=rev&revision=$rev" );
146 - $viewvcTxt = wfMsgHtml( 'code-rev-rev-viewvc' );
147 - $revText .= " (<a href=\"$url\" title=\"revision $rev\">$viewvcTxt</a>)";
148 - }
149 - $links[] = $revText;
150 -
151 - if ( $next ) {
152 - $nextTarget = SpecialPage::getTitleFor( 'Code', "$repo/$next" );
153 - $links[] = $this->mSkin->link( $nextTarget, "r$next" ) . '&nbsp;&gt;';
154 - }
155 -
156 - return $wgLang->pipeList( $links );
157 - }
158 -
159 - protected function checkPostings() {
160 - global $wgRequest, $wgUser;
161 - if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
162 - // Look for a posting...
163 - $text = $wgRequest->getText( "wpReply{$this->mReplyTarget}" );
164 - $parent = $wgRequest->getIntOrNull( 'wpParent' );
165 - $review = $wgRequest->getInt( 'wpReview' );
166 - $isPreview = $wgRequest->getCheck( 'wpPreview' );
167 - if ( $isPreview ) {
168 - // Save the text for reference on later comment display...
169 - $this->mPreviewText = $text;
170 - }
171 - }
172 - return false;
173 - }
174 -
175 - protected function formatPathLine( $path, $action ) {
176 - // Uses messages 'code-rev-modified-a', 'code-rev-modified-r', 'code-rev-modified-d', 'code-rev-modified-m'
177 - $desc = wfMsgHtml( 'code-rev-modified-' . strtolower( $action ) );
178 - // Find any ' (from x)' from rename comment in the path.
179 - preg_match( '/ \([^\)]+\)$/', $path, $matches );
180 - $from = isset( $matches[0] ) ? $matches[0] : '';
181 - // Remove ' (from x)' from rename comment in the path.
182 - $path = preg_replace( '/ \([^\)]+\)$/', '', $path );
183 - $viewvc = $this->mRepo->getViewVcBase();
184 - $diff = '';
185 - if ( $viewvc ) {
186 - $rev = $this->mRev->getId();
187 - $prev = $rev - 1;
188 - $safePath = wfUrlEncode( $path );
189 - if ( $action !== 'D' ) {
190 - $link = $this->mSkin->makeExternalLink(
191 - "$viewvc$safePath?view=markup&pathrev=$rev",
192 - $path . $from );
193 - } else {
194 - $link = $safePath;
195 - }
196 - if ( $action !== 'A' && $action !== 'D' ) {
197 - $diff = ' (' .
198 - $this->mSkin->makeExternalLink(
199 - "$viewvc$safePath?&pathrev=$rev&r1=$prev&r2=$rev",
200 - wfMsg( 'code-rev-diff-link' ) ) .
201 - ')';
202 - }
203 - } else {
204 - $link = $safePath;
205 - }
206 - return "<li>$link ($desc)$diff</li>\n";
207 - }
208 -
209 - protected function tagForm() {
210 - global $wgUser;
211 - $tags = $this->mRev->getTags();
212 - $list = '';
213 - if ( count( $tags ) ) {
214 - $list = implode( ", ",
215 - array_map(
216 - array( $this, 'formatTag' ),
217 - $tags )
218 - ) . '&nbsp;';
219 - }
220 - if ( $wgUser->isAllowed( 'codereview-add-tag' ) ) {
221 - $list .= $this->addTagForm( $this->mAddTags, $this->mRemoveTags );
222 - }
223 - return $list;
224 - }
225 -
226 - protected function splitTags( $input ) {
227 - if ( !$this->mRev ) return array();
228 - $tags = array_map( 'trim', explode( ",", $input ) );
229 - foreach ( $tags as $key => $tag ) {
230 - $normal = $this->mRev->normalizeTag( $tag );
231 - if ( $normal === false ) {
232 - return null;
233 - }
234 - $tags[$key] = $normal;
235 - }
236 - return $tags;
237 - }
238 -
239 - static function listTags( $tags ) {
240 - if ( empty( $tags ) )
241 - return "";
242 - return implode( ",", $tags );
243 - }
244 -
245 - protected function statusForm() {
246 - global $wgUser;
247 - if ( $wgUser->isAllowed( 'codereview-set-status' ) ) {
248 - $repo = $this->mRepo->getName();
249 - $rev = $this->mRev->getId();
250 - return Xml::openElement( 'select', array( 'name' => 'wpStatus' ) ) .
251 - self::buildStatusList( $this->mRev->getStatus(), $this ) .
252 - xml::closeElement( 'select' );
253 - } else {
254 - return htmlspecialchars( $this->statusDesc( $this->mRev->getStatus() ) );
255 - }
256 - }
257 -
258 - static function buildStatusList( $status, $view ) {
259 - $states = CodeRevision::getPossibleStates();
260 - $out = '';
261 - foreach ( $states as $state ) {
262 - $out .= Xml::option( $view->statusDesc( $state ), $state,
263 - $status === $state );
264 - }
265 - return $out;
266 - }
267 -
268 - /** Parameters are the tags to be added/removed sent with the request */
269 - static function addTagForm( $addTags, $removeTags ) {
270 - global $wgUser;
271 - return '<div><table><tr><td>' .
272 - Xml::inputLabel( wfMsg( 'code-rev-tag-add' ), 'wpTag', 'wpTag', 20,
273 - self::listTags( $addTags ) ) . '</td><td>&nbsp;</td><td>' .
274 - Xml::inputLabel( wfMsg( 'code-rev-tag-remove' ), 'wpRemoveTag', 'wpRemoveTag', 20,
275 - self::listTags( $removeTags ) ) . '</td></tr></table></div>';
276 - }
277 -
278 - protected function formatTag( $tag ) {
279 - global $wgUser;
280 - $repo = $this->mRepo->getName();
281 - $special = SpecialPage::getTitleFor( 'Code', "$repo/tag/$tag" );
282 - return $this->mSkin->link( $special, htmlspecialchars( $tag ) );
283 - }
284 -
285 - protected function formatDiff() {
286 - global $wgEnableAPI;
287 -
288 - // Asynchronous diff loads will require the API
289 - // And JS in the client, but tough shit eh? ;)
290 - $deferDiffs = $wgEnableAPI;
291 -
292 - if ( $this->mSkipCache ) {
293 - // We're purging the cache on purpose, probably
294 - // because the cached data was corrupt.
295 - $cache = 'skipcache';
296 - } elseif ( $deferDiffs ) {
297 - // If data is already cached, we'll take it now;
298 - // otherwise defer the load to an AJAX request.
299 - // This lets the page be manipulable even if the
300 - // SVN connection is slow or uncooperative.
301 - $cache = 'cached';
302 - } else {
303 - $cache = '';
304 - }
305 - $diff = $this->mRepo->getDiff( $this->mRev->getId(), $cache );
306 - if ( !$diff && $deferDiffs ) {
307 - // We'll try loading it by AJAX...
308 - return $this->stubDiffLoader();
309 - } else {
310 - $hilite = new CodeDiffHighlighter();
311 - return $hilite->render( $diff );
312 - }
313 - }
314 -
315 - protected function formatImgDiff() {
316 - global $wgCodeReviewImgRegex;
317 - // Get image diffs
318 - $imgDiffs = $html = '';
319 - $modifiedPaths = $this->mRev->getModifiedPaths();
320 - foreach ( $modifiedPaths as $row ) {
321 - // Typical image file?
322 - if( preg_match($wgCodeReviewImgRegex,$row->cp_path) ) {
323 - $imgDiffs .= 'Index: '.htmlspecialchars( $row->cp_path )."\n";
324 - $imgDiffs .= '<table border="1px" style="background:white;"><tr>';
325 - if( $row->cp_action !== 'A' ) { // old
326 - // What was done to it?
327 - $action = $row->cp_action == 'D' ? 'code-rev-modified-d' : 'code-rev-modified-r';
328 - // Link to old image
329 - $imgDiffs .= $this->formatImgCell( $row->cp_path, $this->mRev->getPrevious(), $action );
330 - }
331 - if( $row->cp_action !== 'D' ) { // new
332 - // What was done to it?
333 - $action = $row->cp_action == 'A' ? 'code-rev-modified-a' : 'code-rev-modified-m';
334 - // Link to new image
335 - $imgDiffs .= $this->formatImgCell( $row->cp_path, $this->mRev->getId(), $action );
336 - }
337 - $imgDiffs .= "</tr></table>\n";
338 - }
339 - }
340 - if( $imgDiffs ) {
341 - $html = '<h2>'.wfMsgHtml('code-rev-imagediff').'</h2>';
342 - $html .= "<div class='mw-codereview-imgdiff'>$imgDiffs</div>\n";
343 - }
344 - return $html;
345 - }
346 -
347 - protected function formatImgCell( $path, $rev, $message ) {
348 - $viewvc = $this->mRepo->getViewVcBase();
349 - $safePath = wfUrlEncode( $path );
350 - $url = "{$viewvc}{$safePath}?&pathrev=$rev&revision=$rev";
351 -
352 - $alt = wfMsg( $message );
353 -
354 - return Xml::tags( 'td',
355 - array(),
356 - Xml::tags( 'a',
357 - array( 'href' => $url ),
358 - Xml::element( 'img',
359 - array(
360 - 'src' => $url,
361 - 'alt' => $alt,
362 - 'title' => $alt,
363 - 'border' => '0' ) ) ) );
364 - }
365 -
366 - protected function stubDiffLoader() {
367 - global $wgOut, $wgScriptPath, $wgCodeReviewStyleVersion;
368 - $encRepo = Xml::encodeJsVar( $this->mRepo->getName() );
369 - $encRev = Xml::encodeJsVar( $this->mRev->getId() );
370 - $wgOut->addScriptFile( "$wgScriptPath/extensions/CodeReview/codereview.js?$wgCodeReviewStyleVersion" );
371 - $wgOut->addInlineScript(
372 - "addOnloadHook(
373 - function() {
374 - CodeReview.loadDiff($encRepo,$encRev);
375 - }
376 - );" );
377 - return wfMsg( 'code-load-diff' );
378 - }
379 -
380 - protected function formatComments() {
381 - $comments = implode( "\n",
382 - array_map( array( $this, 'formatCommentInline' ), $this->mRev->getComments() )
383 - );
384 - if ( !$this->mReplyTarget ) {
385 - $comments .= $this->postCommentForm();
386 - }
387 - if ( !$comments ) {
388 - return false;
389 - }
390 - return "<div class='mw-codereview-comments'>$comments</div>";
391 - }
392 -
393 - protected function formatPropChanges() {
394 - $changes = implode( "\n",
395 - array_map( array( $this, 'formatChangeInline' ), $this->mRev->getPropChanges() )
396 - );
397 - if ( !$changes ) {
398 - return false;
399 - }
400 - return "<ul class='mw-codereview-changes'>$changes</ul>";
401 - }
402 -
403 - protected function formatReferences() {
404 - $refs = implode( "\n",
405 - array_map( array( $this, 'formatReferenceInline' ), $this->mRev->getReferences() )
406 - );
407 - if ( !$refs ) {
408 - return false;
409 - }
410 - $header = '<th>'.wfMsg( 'code-field-id' ).'</th>';
411 - $header .= '<th>'.wfMsg( 'code-field-message' ) .'</th>';
412 - $header .= '<th>'.wfMsg( 'code-field-author' ).'</th>';
413 - $header .= '<th>'.wfMsg( 'code-field-timestamp' ).'</th>';
414 - return "<table border='1' class='TablePager'><tr>{$header}</tr>{$refs}</table>";
415 - }
416 -
417 - protected function formatCommentInline( $comment ) {
418 - if ( $comment->id === $this->mReplyTarget ) {
419 - return $this->formatComment( $comment,
420 - $this->postCommentForm( $comment->id ) );
421 - } else {
422 - return $this->formatComment( $comment );
423 - }
424 - }
425 -
426 - protected function formatChangeInline( $change ) {
427 - global $wgLang;
428 - $revId = $change->rev->getId();
429 - $line = $wgLang->timeanddate( $change->timestamp, true );
430 - $line .= '&nbsp;' . $this->mSkin->userLink( $change->user, $change->userText );
431 - $line .= $this->mSkin->userToolLinks( $change->user, $change->userText );
432 - // Uses messages 'code-change-status', 'code-change-tags'
433 - $line .= '&nbsp;' . wfMsgExt( "code-change-{$change->attrib}", 'parseinline', $revId );
434 - $line .= " <i>[";
435 - if ( $change->removed ) {
436 - $line .= '<b>' . wfMsg( 'code-change-removed' ) . '</b> ';
437 - $line .= htmlspecialchars( $change->removed );
438 - $line .= $change->added ? "&nbsp;" : "";
439 - }
440 - if ( $change->added ) {
441 - $line .= '<b>' . wfMsg( 'code-change-added' ) . '</b> ';
442 - $line .= htmlspecialchars( $change->added );
443 - }
444 - $line .= "]</i>";
445 - return "<li>$line</li>";
446 - }
447 -
448 - protected function formatReferenceInline( $row ) {
449 - global $wgLang;
450 - $rev = intval( $row->cr_id );
451 - $repo = $this->mRepo->getName();
452 - // Borrow the code revision list css
453 - $css = 'mw-codereview-status-' . htmlspecialchars( $row->cr_status );
454 - $date = $wgLang->timeanddate( $row->cr_timestamp, true );
455 - $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" );
456 - $revLink = $this->mSkin->link( $title, "r$rev" );
457 - $summary = $this->messageFragment( $row->cr_message );
458 - $author = $this->authorLink( $row->cr_author );
459 - return "<tr class='$css'><td>$revLink</td><td>$summary</td><td>$author</td><td>$date</td></tr>";
460 - }
461 -
462 - protected function commentLink( $commentId ) {
463 - $repo = $this->mRepo->getName();
464 - $rev = $this->mRev->getId();
465 - $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" );
466 - $title->setFragment( "#c{$commentId}" );
467 - return $title;
468 - }
469 -
470 - protected function revLink() {
471 - $repo = $this->mRepo->getName();
472 - $rev = $this->mRev->getId();
473 - $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" );
474 - return $title;
475 - }
476 -
477 - protected function previewComment( $text, $review = 0 ) {
478 - $comment = $this->mRev->previewComment( $text, $review );
479 - return $this->formatComment( $comment );
480 - }
481 -
482 - protected function formatComment( $comment, $replyForm = '' ) {
483 - global $wgOut, $wgLang;
484 - $linker = new CodeCommentLinkerWiki( $this->mRepo );
485 -
486 - if ( $comment->id === null ) {
487 - $linkId = 'cpreview';
488 - $permaLink = "<b>Preview:</b> ";
489 - } else {
490 - $linkId = 'c' . intval( $comment->id );
491 - $permaLink = $this->mSkin->link( $this->commentLink( $comment->id ), "#" );
492 - }
493 -
494 - return Xml::openElement( 'div',
495 - array(
496 - 'class' => 'mw-codereview-comment',
497 - 'id' => $linkId,
498 - 'style' => $this->commentStyle( $comment ) ) ) .
499 - '<div class="mw-codereview-comment-meta">' .
500 - $permaLink .
501 - wfMsgHtml( 'code-rev-comment-by',
502 - $this->mSkin->userLink( $comment->user, $comment->userText ) .
503 - $this->mSkin->userToolLinks( $comment->user, $comment->userText ) ) .
504 - ' &nbsp; ' .
505 - $wgLang->timeanddate( $comment->timestamp, true ) .
506 - ' ' .
507 - $this->commentReplyLink( $comment->id ) .
508 - '</div>' .
509 - '<div class="mw-codereview-comment-text">' .
510 - $wgOut->parse( $linker->link( $comment->text ) ) .
511 - '</div>' .
512 - $replyForm .
513 - '</div>';
514 - }
515 -
516 - protected function commentStyle( $comment ) {
517 - $depth = $comment->threadDepth();
518 - $margin = ( $depth - 1 ) * 48;
519 - return "margin-left: ${margin}px";
520 - }
521 -
522 - protected function commentReplyLink( $id ) {
523 - global $wgUser;
524 - if ( !$wgUser->isAllowed( 'codereview-post-comment' ) ) return '';
525 - $repo = $this->mRepo->getName();
526 - $rev = $this->mRev->getId();
527 - $self = SpecialPage::getTitleFor( 'Code', "$repo/$rev/reply/$id" );
528 - $self->setFragment( "#c$id" );
529 - return '[' . $this->mSkin->link( $self, wfMsg( 'codereview-reply-link' ) ) . ']';
530 - }
531 -
532 - protected function postCommentForm( $parent = null ) {
533 - global $wgUser;
534 - if ( $this->mPreviewText !== false && $parent === $this->mReplyTarget ) {
535 - $preview = $this->previewComment( $this->mPreviewText );
536 - $text = htmlspecialchars( $this->mPreviewText );
537 - } else {
538 - $preview = '';
539 - $text = $this->text;
540 - }
541 - $repo = $this->mRepo->getName();
542 - $rev = $this->mRev->getId();
543 - if ( !$wgUser->isAllowed( 'codereview-post-comment' ) ) {
544 - return '';
545 - }
546 - return '<div class="mw-codereview-post-comment">' .
547 - $preview .
548 - Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
549 - ( $parent ? Xml::hidden( 'wpParent', $parent ) : '' ) .
550 - '<div>' .
551 - Xml::openElement( 'textarea', array(
552 - 'name' => "wpReply{$parent}",
553 - 'id' => "wpReplyTo{$parent}",
554 - 'cols' => 40,
555 - 'rows' => 5 ) ) .
556 - $text .
557 - '</textarea>' .
558 - '</div>' .
559 - '</div>';
560 - }
561 -
562 - protected function addActionButtons() {
563 - return '<div>' .
564 - Xml::submitButton( wfMsg( 'code-rev-submit' ), array( 'name' => 'wpSave' ) ) .
565 - ' ' .
566 - Xml::submitButton( wfMsg( 'code-rev-submit-next' ), array( 'name' => 'wpSaveAndNext' ) ) .
567 - ' ' .
568 - Xml::submitButton( wfMsg( 'code-rev-comment-preview' ), array( 'name' => 'wpPreview' ) ) .
569 - '</div>';
570 - }
571 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRepoListView.php
@@ -1,33 +0,0 @@
2 -<?php
3 -
4 -// Special:Code
5 -class CodeRepoListView {
6 -
7 - function execute() {
8 - global $wgOut;
9 - $repos = CodeRepository::getRepoList();
10 - if ( !count( $repos ) ) {
11 - $wgOut->addWikiMsg( 'code-no-repo' );
12 - return;
13 - }
14 - $text = '';
15 - foreach ( $repos as $repo ) {
16 - $name = $repo->getName();
17 - $text .= "* " . self::getNavItem( $name ) . "\n";
18 - }
19 - $wgOut->addWikiText( $text );
20 - }
21 -
22 - public static function getNavItem( $name ) {
23 - global $wgLang;
24 - $text = "'''[[Special:Code/$name|$name]]''' (";
25 - $links[] = "[[Special:Code/$name/comments|" . wfMsgHtml( 'code-notes' ) . "]]";
26 - $links[] = "[[Special:Code/$name/status|" . wfMsgHtml( 'code-status' ) . "]]";
27 - $links[] = "[[Special:Code/$name/tag|" . wfMsgHtml( 'code-tags' ) . "]]";
28 - $links[] = "[[Special:Code/$name/author|" . wfMsgHtml( 'code-authors' ) . "]]";
29 - $links[] = "[[Special:Code/$name/releasenotes|" . wfMsgHtml( 'code-releasenotes' ) . "]]";
30 - $text .= $wgLang->pipeList( $links );
31 - $text .= ")";
32 - return $text;
33 - }
34 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevision.php
@@ -1,618 +0,0 @@
2 -<?php
3 -if ( !defined( 'MEDIAWIKI' ) ) die();
4 -
5 -class CodeRevision {
6 - public static function newFromSvn( CodeRepository $repo, $data ) {
7 - $rev = new CodeRevision();
8 - $rev->mRepoId = $repo->getId();
9 - $rev->mRepo = $repo;
10 - $rev->mId = intval( $data['rev'] );
11 - $rev->mAuthor = $data['author'];
12 - $rev->mTimestamp = wfTimestamp( TS_MW, strtotime( $data['date'] ) );
13 - $rev->mMessage = rtrim( $data['msg'] );
14 - $rev->mPaths = $data['paths'];
15 - $rev->mStatus = 'new';
16 -
17 - $common = null;
18 - if ( $rev->mPaths ) {
19 - if ( count( $rev->mPaths ) == 1 )
20 - $common = $rev->mPaths[0]['path'];
21 - else {
22 - $first = array_shift( $rev->mPaths );
23 -
24 - $common = explode( '/', $first['path'] );
25 -
26 - foreach ( $rev->mPaths as $path ) {
27 - $compare = explode( '/', $path['path'] );
28 -
29 - // make sure $common is the shortest path
30 - if ( count( $compare ) < count( $common ) )
31 - list( $compare, $common ) = array( $common, $compare );
32 -
33 - $tmp = array();
34 - foreach ( $common as $k => $v )
35 - if ( $v == $compare[$k] ) $tmp[] = $v;
36 - else break;
37 - $common = $tmp;
38 - }
39 - $common = implode( '/', $common );
40 -
41 - array_unshift( $rev->mPaths, $first );
42 - }
43 - }
44 - $rev->mCommonPath = $common;
45 - return $rev;
46 - }
47 -
48 - public static function newFromRow( CodeRepository $repo, $row ) {
49 - $rev = new CodeRevision();
50 - $rev->mRepoId = intval( $row->cr_repo_id );
51 - if ( $rev->mRepoId != $repo->getId() ) {
52 - throw new MWException( "Invalid repo ID in " . __METHOD__ );
53 - }
54 - $rev->mRepo = $repo;
55 - $rev->mId = intval( $row->cr_id );
56 - $rev->mAuthor = $row->cr_author;
57 - $rev->mTimestamp = wfTimestamp( TS_MW, $row->cr_timestamp );
58 - $rev->mMessage = $row->cr_message;
59 - $rev->mStatus = $row->cr_status;
60 - $rev->mCommonPath = $row->cr_path;
61 - return $rev;
62 - }
63 -
64 - public function getId() {
65 - return intval( $this->mId );
66 - }
67 -
68 - public function getRepoId() {
69 - return intval( $this->mRepoId );
70 - }
71 -
72 - public function getAuthor() {
73 - return $this->mAuthor;
74 - }
75 -
76 - public function getWikiUser() {
77 - return $this->mRepo->authorWikiUser( $this->getAuthor() );
78 - }
79 -
80 - public function getTimestamp() {
81 - return $this->mTimestamp;
82 - }
83 -
84 - public function getMessage() {
85 - return $this->mMessage;
86 - }
87 -
88 - public function getStatus() {
89 - return $this->mStatus;
90 - }
91 -
92 - public function getCommonPath() {
93 - return $this->mCommonPath;
94 - }
95 -
96 - public static function getPossibleStates() {
97 - return array( 'new', 'fixme', 'reverted', 'resolved', 'ok', 'verified', 'deferred' );
98 - }
99 -
100 - public function isValidStatus( $status ) {
101 - return in_array( $status, self::getPossibleStates(), true );
102 - }
103 -
104 - public function setStatus( $status, $user ) {
105 - if ( !$this->isValidStatus( $status ) ) {
106 - throw new MWException( "Tried to save invalid code revision status" );
107 - }
108 - // Get the old status from the master
109 - $dbw = wfGetDB( DB_MASTER );
110 - $oldStatus = $dbw->selectField( 'code_rev',
111 - 'cr_status',
112 - array( 'cr_repo_id' => $this->mRepoId, 'cr_id' => $this->mId ),
113 - __METHOD__
114 - );
115 - if ( $oldStatus === $status ) {
116 - return false; // nothing to do here
117 - }
118 - // Update status
119 - $this->mStatus = $status;
120 - $dbw->update( 'code_rev',
121 - array( 'cr_status' => $status ),
122 - array(
123 - 'cr_repo_id' => $this->mRepoId,
124 - 'cr_id' => $this->mId ),
125 - __METHOD__
126 - );
127 - // Log this change
128 - if ( $user && $user->getId() ) {
129 - $dbw->insert( 'code_prop_changes',
130 - array(
131 - 'cpc_repo_id' => $this->getRepoId(),
132 - 'cpc_rev_id' => $this->getId(),
133 - 'cpc_attrib' => 'status',
134 - 'cpc_removed' => $oldStatus,
135 - 'cpc_added' => $status,
136 - 'cpc_timestamp' => $dbw->timestamp(),
137 - 'cpc_user' => $user->getId(),
138 - 'cpc_user_text' => $user->getName()
139 - ),
140 - __METHOD__
141 - );
142 - }
143 - return true;
144 - }
145 -
146 - public function save() {
147 - $dbw = wfGetDB( DB_MASTER );
148 - $dbw->begin();
149 -
150 - $dbw->insert( 'code_rev',
151 - array(
152 - 'cr_repo_id' => $this->mRepoId,
153 - 'cr_id' => $this->mId,
154 - 'cr_author' => $this->mAuthor,
155 - 'cr_timestamp' => $dbw->timestamp( $this->mTimestamp ),
156 - 'cr_message' => $this->mMessage,
157 - 'cr_status' => $this->mStatus,
158 - 'cr_path' => $this->mCommonPath ),
159 - __METHOD__,
160 - array( 'IGNORE' )
161 - );
162 - // Already exists? Update the row!
163 - $newRevision = $dbw->affectedRows() > 0;
164 - if ( !$newRevision ) {
165 - $dbw->update( 'code_rev',
166 - array(
167 - 'cr_author' => $this->mAuthor,
168 - 'cr_timestamp' => $dbw->timestamp( $this->mTimestamp ),
169 - 'cr_message' => $this->mMessage,
170 - 'cr_path' => $this->mCommonPath ),
171 - array(
172 - 'cr_repo_id' => $this->mRepoId,
173 - 'cr_id' => $this->mId ),
174 - __METHOD__
175 - );
176 - }
177 - // Update path tracking used for output and searching
178 - if ( $this->mPaths ) {
179 - $data = array();
180 - foreach ( $this->mPaths as $path ) {
181 - $data[] = array(
182 - 'cp_repo_id' => $this->mRepoId,
183 - 'cp_rev_id' => $this->mId,
184 - 'cp_path' => $path['path'],
185 - 'cp_action' => $path['action'] );
186 - }
187 - $dbw->insert( 'code_paths', $data, __METHOD__, array( 'IGNORE' ) );
188 - }
189 - // Update bug references table...
190 - $affectedBugs = array();
191 - if ( preg_match_all( '/\bbug (\d+)\b/', $this->mMessage, $m ) ) {
192 - $data = array();
193 - foreach( $m[1] as $bug ) {
194 - $data[] = array(
195 - 'cb_repo_id' => $this->mRepoId,
196 - 'cb_from' => $this->mId,
197 - 'cb_bug' => $bug
198 - );
199 - $affectedBugs[] = intval($bug);
200 - }
201 - $dbw->insert( 'code_bugs', $data, __METHOD__, array( 'IGNORE' ) );
202 - }
203 - // Get the revisions this commit references...
204 - $affectedRevs = array();
205 - if ( preg_match_all( '/\br(\d{2,})\b/', $this->mMessage, $m ) ) {
206 - foreach( $m[1] as $rev ) {
207 - $affectedRevs[] = intval($rev);
208 - }
209 - }
210 - // Also, get previous revisions that have bugs in common...
211 - if( count($affectedBugs) ) {
212 - $res = $dbw->select( 'code_bugs',
213 - array( 'cb_from' ),
214 - array(
215 - 'cb_repo_id' => $this->mRepoId,
216 - 'cb_bug' => $affectedBugs,
217 - 'cb_from < '.intval($this->mId), # just in case
218 - ),
219 - __METHOD__,
220 - array( 'USE INDEX' => 'cb_repo_id' )
221 - );
222 - foreach( $res as $row ) {
223 - $affectedRevs[] = intval($row->cb_from);
224 - }
225 - }
226 - // Filter any duplicate revisions
227 - if( count($affectedRevs) ) {
228 - $data = array();
229 - $affectedRevs = array_unique($affectedRevs);
230 - foreach( $affectedRevs as $rev ) {
231 - $data[] = array(
232 - 'cf_repo_id' => $this->mRepoId,
233 - 'cf_from' => $this->mId,
234 - 'cf_to' => $rev
235 - );
236 - $affectedRevs[] = intval($rev);
237 - }
238 - $dbw->insert( 'code_relations', $data, __METHOD__, array( 'IGNORE' ) );
239 - }
240 - // Email the authors of revisions that this follows up on
241 - if( $newRevision && count($affectedRevs) > 0 ) {
242 - // Get committer wiki user name, or repo name at least
243 - $user = $this->mRepo->authorWikiUser( $this->mAuthor );
244 - $committer = $user ? $user->getName() : htmlspecialchars($this->mAuthor);
245 - // Get the authors of these revisions
246 - $res = $dbw->select( 'code_rev',
247 - array( 'cr_author', 'cr_id' ),
248 - array(
249 - 'cr_repo_id' => $this->mRepoId,
250 - 'cr_id' => $affectedRevs,
251 - 'cr_id < '.intval($this->mId), # just in case
252 - // No sense in notifying if it's the same person
253 - 'cr_author != '.$dbw->addQuotes($this->mAuthor)
254 - ),
255 - __METHOD__,
256 - array( 'USE INDEX' => 'PRIMARY' )
257 - );
258 - // Get repo and build comment title (for url)
259 - $title = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mId );
260 - $url = $title->getFullUrl();
261 - wfLoadExtensionMessages( 'CodeReview' );
262 - foreach( $res as $row ) {
263 - $user = $this->mRepo->authorWikiUser( $row->cr_author );
264 - // User must exist on wiki and have a valid email addy
265 - if( !$user || !$user->canReceiveEmail() ) continue;
266 - // Send message in receiver's language
267 - $lang = array( 'language' => $user->getOption( 'language' ) );
268 - $user->sendMail(
269 - wfMsgExt( 'codereview-email-subj2', $lang, $this->mRepo->getName(), $row->cr_id ),
270 - wfMsgExt( 'codereview-email-body2', $lang, $committer, $row->cr_id, $url, $this->mMessage )
271 - );
272 - }
273 - }
274 - $dbw->commit();
275 - }
276 -
277 - public function getModifiedPaths() {
278 - $dbr = wfGetDB( DB_SLAVE );
279 - return $dbr->select(
280 - 'code_paths',
281 - array( 'cp_path', 'cp_action' ),
282 - array( 'cp_repo_id' => $this->mRepoId, 'cp_rev_id' => $this->mId ),
283 - __METHOD__
284 - );
285 - }
286 -
287 - public function isDiffable() {
288 - $paths = $this->getModifiedPaths();
289 - if ( !$paths->numRows() || $paths->numRows() > 20 ) {
290 - return false; // things need to get done this year
291 - }
292 - return true;
293 - }
294 -
295 - public function previewComment( $text, $review, $parent = null ) {
296 - $data = $this->commentData( $text, $review, $parent );
297 - $data['cc_id'] = null;
298 - return CodeComment::newFromData( $this, $data );
299 - }
300 -
301 - public function saveComment( $text, $review, $parent = null ) {
302 - global $wgUser;
303 - if ( !strlen( $text ) ) {
304 - return 0;
305 - }
306 - $dbw = wfGetDB( DB_MASTER );
307 - $data = $this->commentData( $text, $review, $parent );
308 -
309 - $dbw->begin();
310 - $data['cc_id'] = $dbw->nextSequenceValue( 'code_comment_cc_id' );
311 - $dbw->insert( 'code_comment', $data, __METHOD__ );
312 - $commentId = $dbw->insertId();
313 - $dbw->commit();
314 -
315 - // Give email notices to committer and commenters
316 - global $wgCodeReviewENotif, $wgEnableEmail;
317 - if ( $wgCodeReviewENotif && $wgEnableEmail ) {
318 - // Make list of users to send emails to
319 - $users = $this->getCommentingUsers();
320 - if ( $user = $this->getWikiUser() ) {
321 - $users[$user->getId()] = $user;
322 - }
323 - // Get repo and build comment title (for url)
324 - $title = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mId );
325 - $title->setFragment( "#c{$commentId}" );
326 - $url = $title->getFullUrl();
327 - foreach ( $users as $userId => $user ) {
328 - // No sense in notifying this commenter
329 - if ( $wgUser->getId() == $user->getId() ) {
330 - continue;
331 - }
332 - // Send message in receiver's language
333 - $lang = array( 'language' => $user->getOption( 'language' ) );
334 - if ( $user->canReceiveEmail() ) {
335 - $user->sendMail(
336 - wfMsgExt( 'codereview-email-subj', $lang, $this->mRepo->getName(), $this->mId ),
337 - wfMsgExt( 'codereview-email-body', $lang, $wgUser->getName(), $url, $this->mId, $text )
338 - );
339 - }
340 - }
341 - }
342 -
343 - return $commentId;
344 - }
345 -
346 - protected function commentData( $text, $review, $parent = null ) {
347 - global $wgUser;
348 - $dbw = wfGetDB( DB_MASTER );
349 - $ts = wfTimestamp( TS_MW );
350 - $sortkey = $this->threadedSortkey( $parent, $ts );
351 - return array(
352 - 'cc_repo_id' => $this->mRepoId,
353 - 'cc_rev_id' => $this->mId,
354 - 'cc_text' => $text,
355 - 'cc_parent' => $parent,
356 - 'cc_user' => $wgUser->getId(),
357 - 'cc_user_text' => $wgUser->getName(),
358 - 'cc_timestamp' => $dbw->timestamp( $ts ),
359 - 'cc_review' => $review,
360 - 'cc_sortkey' => $sortkey );
361 - }
362 -
363 - protected function threadedSortKey( $parent, $ts ) {
364 - if ( $parent ) {
365 - // We construct a threaded sort key by concatenating the timestamps
366 - // of all our parent comments
367 - $dbw = wfGetDB( DB_MASTER );
368 - $parentKey = $dbw->selectField( 'code_comment',
369 - 'cc_sortkey',
370 - array( 'cc_id' => $parent ),
371 - __METHOD__ );
372 - if ( $parentKey ) {
373 - return $parentKey . ',' . $ts;
374 - } else {
375 - // hmmmm
376 - throw new MWException( 'Invalid parent submission' );
377 - }
378 - } else {
379 - return $ts;
380 - }
381 - }
382 -
383 - public function getComments() {
384 - $dbr = wfGetDB( DB_SLAVE );
385 - $result = $dbr->select( 'code_comment',
386 - array(
387 - 'cc_id',
388 - 'cc_text',
389 - 'cc_parent',
390 - 'cc_user',
391 - 'cc_user_text',
392 - 'cc_timestamp',
393 - 'cc_review',
394 - 'cc_sortkey' ),
395 - array(
396 - 'cc_repo_id' => $this->mRepoId,
397 - 'cc_rev_id' => $this->mId ),
398 - __METHOD__,
399 - array(
400 - 'ORDER BY' => 'cc_sortkey' )
401 - );
402 - $comments = array();
403 - foreach ( $result as $row ) {
404 - $comments[] = CodeComment::newFromRow( $this, $row );
405 - }
406 - $result->free();
407 - return $comments;
408 - }
409 -
410 - public function getPropChanges() {
411 - $dbr = wfGetDB( DB_SLAVE );
412 - $result = $dbr->select( array( 'code_prop_changes', 'user' ),
413 - array(
414 - 'cpc_attrib',
415 - 'cpc_removed',
416 - 'cpc_added',
417 - 'cpc_timestamp',
418 - 'cpc_user',
419 - 'cpc_user_text',
420 - 'user_name'
421 - ), array(
422 - 'cpc_repo_id' => $this->mRepoId,
423 - 'cpc_rev_id' => $this->mId,
424 - ),
425 - __METHOD__,
426 - array( 'ORDER BY' => 'cpc_timestamp DESC' ),
427 - array( 'user' => array( 'LEFT JOIN', 'cpc_user = user_id' ) )
428 - );
429 - $changes = array();
430 - foreach ( $result as $row ) {
431 - $changes[] = CodePropChange::newFromRow( $this, $row );
432 - }
433 - $result->free();
434 - return $changes;
435 - }
436 -
437 - protected function getCommentingUsers() {
438 - $dbr = wfGetDB( DB_SLAVE );
439 - $res = $dbr->select( 'code_comment',
440 - 'DISTINCT(cc_user)',
441 - array(
442 - 'cc_repo_id' => $this->mRepoId,
443 - 'cc_rev_id' => $this->mId,
444 - 'cc_user != 0' // users only
445 - ),
446 - __METHOD__
447 - );
448 - $users = array();
449 - while ( $row = $res->fetchObject() ) {
450 - $users[$row->cc_user] = User::newFromId( $row->cc_user );
451 - }
452 - return $users;
453 - }
454 -
455 - public function getReferences() {
456 - $refs = array();
457 - $dbr = wfGetDB( DB_SLAVE );
458 - $res = $dbr->select(
459 - array( 'code_relations', 'code_rev' ),
460 - array( 'cr_id', 'cr_status', 'cr_timestamp', 'cr_author', 'cr_message' ),
461 - array(
462 - 'cf_repo_id' => $this->mRepoId,
463 - 'cf_to' => $this->mId,
464 - 'cr_repo_id = cf_repo_id',
465 - 'cr_id = cf_from'
466 - ),
467 - __METHOD__
468 - );
469 - while ( $row = $res->fetchObject() ) {
470 - $refs[] = $row;
471 - }
472 - return $refs;
473 - }
474 -
475 - public function getTags( $from = DB_SLAVE ) {
476 - $db = wfGetDB( $from );
477 - $result = $db->select( 'code_tags',
478 - array( 'ct_tag' ),
479 - array(
480 - 'ct_repo_id' => $this->mRepoId,
481 - 'ct_rev_id' => $this->mId ),
482 - __METHOD__ );
483 -
484 - $tags = array();
485 - foreach ( $result as $row ) {
486 - $tags[] = $row->ct_tag;
487 - }
488 - return $tags;
489 - }
490 -
491 - public function changeTags( $addTags, $removeTags, $user = NULL ) {
492 - // Get the current tags and see what changes
493 - $tagsNow = $this->getTags( DB_MASTER );
494 - // Normalize our input tags
495 - $addTags = $this->normalizeTags( $addTags );
496 - $removeTags = $this->normalizeTags( $removeTags );
497 - $addTags = array_diff( $addTags, $tagsNow );
498 - $removeTags = array_intersect( $removeTags, $tagsNow );
499 - // Do the queries
500 - $dbw = wfGetDB( DB_MASTER );
501 - if ( $addTags ) {
502 - $dbw->insert( 'code_tags',
503 - $this->tagData( $addTags ),
504 - __METHOD__,
505 - array( 'IGNORE' )
506 - );
507 - }
508 - if ( $removeTags ) {
509 - $dbw->delete( 'code_tags',
510 - array(
511 - 'ct_repo_id' => $this->mRepoId,
512 - 'ct_rev_id' => $this->mId,
513 - 'ct_tag' => $removeTags ),
514 - __METHOD__
515 - );
516 - }
517 - // Log this change
518 - if ( ( $removeTags || $addTags ) && $user && $user->getId() ) {
519 - $dbw->insert( 'code_prop_changes',
520 - array(
521 - 'cpc_repo_id' => $this->getRepoId(),
522 - 'cpc_rev_id' => $this->getId(),
523 - 'cpc_attrib' => 'tags',
524 - 'cpc_removed' => implode( ',', $removeTags ),
525 - 'cpc_added' => implode( ',', $addTags ),
526 - 'cpc_timestamp' => $dbw->timestamp(),
527 - 'cpc_user' => $user->getId(),
528 - 'cpc_user_text' => $user->getName()
529 - ),
530 - __METHOD__
531 - );
532 - }
533 - }
534 -
535 - protected function normalizeTags( $tags ) {
536 - $out = array();
537 - foreach ( $tags as $tag ) {
538 - $out[] = $this->normalizeTag( $tag );
539 - }
540 - return $out;
541 - }
542 -
543 - protected function tagData( $tags ) {
544 - $data = array();
545 - foreach ( $tags as $tag ) {
546 - $data[] = array(
547 - 'ct_repo_id' => $this->mRepoId,
548 - 'ct_rev_id' => $this->mId,
549 - 'ct_tag' => $this->normalizeTag( $tag ) );
550 - }
551 - return $data;
552 - }
553 -
554 - public function normalizeTag( $tag ) {
555 - global $wgContLang;
556 - $lower = $wgContLang->lc( $tag );
557 -
558 - $title = Title::newFromText( $tag );
559 - if ( $title && $lower === $wgContLang->lc( $title->getPrefixedText() ) ) {
560 - return $lower;
561 - } else {
562 - return false;
563 - }
564 - }
565 -
566 - public function isValidTag( $tag ) {
567 - return ( $this->normalizeTag( $tag ) !== false );
568 - }
569 -
570 - public function getPrevious() {
571 - // hack!
572 - if ( $this->mId > 1 ) {
573 - return $this->mId - 1;
574 - } else {
575 - return false;
576 - }
577 - }
578 -
579 - public function getNext() {
580 - $dbr = wfGetDB( DB_SLAVE );
581 - $encId = $dbr->addQuotes( $this->mId );
582 - $row = $dbr->selectRow( 'code_rev',
583 - 'cr_id',
584 - array(
585 - 'cr_repo_id' => $this->mRepoId,
586 - "cr_id > $encId" ),
587 - __METHOD__,
588 - array(
589 - 'ORDER BY' => 'cr_repo_id, cr_id',
590 - 'LIMIT' => 1 ) );
591 -
592 - if ( $row ) {
593 - return intval( $row->cr_id );
594 - } else {
595 - return false;
596 - }
597 - }
598 -
599 - public function getNextUnresolved() {
600 - $dbr = wfGetDB( DB_SLAVE );
601 - $encId = $dbr->addQuotes( $this->mId );
602 - $row = $dbr->selectRow( 'code_rev',
603 - 'cr_id',
604 - array(
605 - 'cr_repo_id' => $this->mRepoId,
606 - "cr_id > $encId",
607 - 'cr_status' => array( 'new', 'fixme' ) ),
608 - __METHOD__,
609 - array(
610 - 'ORDER BY' => 'cr_repo_id, cr_id',
611 - 'LIMIT' => 1 )
612 - );
613 - if ( $row ) {
614 - return intval( $row->cr_id );
615 - } else {
616 - return false;
617 - }
618 - }
619 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionAuthorLink.php
@@ -1,89 +0,0 @@
2 -<?php
3 -
4 -// Special:Code/MediaWiki/author/johndoe/link
5 -
6 -class CodeRevisionAuthorLink extends CodeRevisionAuthorView {
7 - function __construct( $repoName, $author ) {
8 - global $wgRequest;
9 - parent::__construct( $repoName, $author );
10 - $this->mTarget = $wgRequest->getVal( 'linktouser' );
11 - }
12 -
13 - function getTitle() {
14 - $repo = $this->mRepo->getName();
15 - $auth = $this->mAuthor;
16 - return SpecialPage::getTitleFor( 'Code', "$repo/author/$auth/link" );
17 - }
18 -
19 - function execute() {
20 - global $wgOut, $wgRequest, $wgUser;
21 - if ( !$wgUser->isAllowed( 'codereview-link-user' ) ) {
22 - $wgOut->permissionRequired( 'codereview-link-user' );
23 - return;
24 - }
25 - if ( $wgRequest->wasPosted() ) {
26 - $this->doSubmit();
27 - } else {
28 - $this->doForm();
29 - }
30 - }
31 -
32 - function doForm() {
33 - global $wgOut;
34 - $form = Xml::openElement( 'form', array( 'method' => 'post',
35 - 'action' => $this->getTitle()->getLocalUrl(),
36 - 'name' => 'uluser', 'id' => 'mw-codeauthor-form1' ) );
37 - $form .= Xml::openElement( 'fieldset' );
38 -
39 - $additional = '';
40 - // Is there already a user linked to this author?
41 - if ( $this->mUser ) {
42 - $form .= Xml::element( 'legend', array(), wfMsg( 'code-author-alterlink' ) );
43 - $additional = Xml::openElement( 'fieldset' ) .
44 - Xml::element( 'legend', array(), wfMsg( 'code-author-orunlink' ) ) .
45 - Xml::submitButton( wfMsg( 'code-author-unlink' ), array( 'name' => 'unlink' ) ) .
46 - Xml::closeElement( 'fieldset' );
47 - } else {
48 - $form .= Xml::element( 'legend', array(), wfMsg( 'code-author-dolink' ) );
49 - }
50 -
51 - $form .= Xml::inputLabel( wfMsg( 'code-author-name' ), 'linktouser', 'username', 30, '' ) . ' ' .
52 - Xml::submitButton( wfMsg( 'ok' ), array( 'name' => 'newname' ) ) .
53 - Xml::closeElement( 'fieldset' ) .
54 - $additional .
55 - Xml::closeElement( 'form' ) . "\n";
56 -
57 - $wgOut->addHTML( $this->linkStatus() . $form );
58 - }
59 -
60 - function doSubmit() {
61 - global $wgOut, $wgRequest;
62 - // Link an author to a wiki user
63 - if ( strlen( $this->mTarget ) && $wgRequest->getCheck( 'newname' ) ) {
64 - $user = User::newFromName( $this->mTarget, false );
65 - if ( !$user || !$user->getId() ) {
66 - $wgOut->addWikiMsg( 'nosuchusershort', $this->mTarget );
67 - return;
68 - }
69 - $this->mRepo->linkUser( $this->mAuthor, $user );
70 - $userlink = $this->mSkin->userLink( $user->getId(), $user->getName() );
71 - $wgOut->addHTML(
72 - '<div class="successbox">' .
73 - wfMsgHtml( 'code-author-success', $this->authorLink( $this->mAuthor ), $userlink ) .
74 - '</div>'
75 - );
76 - // Unlink an author to a wiki users
77 - } else if ( $wgRequest->getVal( 'unlink' ) ) {
78 - if ( !$this->mUser ) {
79 - $wgOut->addHTML( wfMsg( 'code-author-orphan' ) );
80 - return;
81 - }
82 - $this->mRepo->unlinkUser( $this->mAuthor );
83 - $wgOut->addHTML(
84 - '<div class="successbox">' .
85 - wfMsgHtml( 'code-author-unlinksuccess', $this->authorLink( $this->mAuthor ) ) .
86 - '</div>'
87 - );
88 - }
89 - }
90 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeComment.php
@@ -1,29 +0,0 @@
2 -<?php
3 -if ( !defined( 'MEDIAWIKI' ) ) die();
4 -
5 -class CodeComment {
6 - function __construct( $rev ) {
7 - $this->rev = $rev;
8 - }
9 -
10 - static function newFromRow( $rev, $row ) {
11 - return self::newFromData( $rev, get_object_vars( $row ) );
12 - }
13 -
14 - static function newFromData( $rev, $data ) {
15 - $comment = new CodeComment( $rev );
16 - $comment->id = intval( $data['cc_id'] );
17 - $comment->text = $data['cc_text']; // fixme
18 - $comment->user = $data['cc_user'];
19 - $comment->userText = $data['cc_user_text'];
20 - $comment->timestamp = wfTimestamp( TS_MW, $data['cc_timestamp'] );
21 - $comment->review = $data['cc_review'];
22 - $comment->sortkey = $data['cc_sortkey'];
23 - return $comment;
24 - }
25 -
26 - function threadDepth() {
27 - $timestamps = explode( ",", $this->sortkey );
28 - return count( $timestamps );
29 - }
30 -}
Index: branches/wmf-deployment/extensions/CodeReview/Subversion.php
@@ -1,326 +0,0 @@
2 -<?php
3 -if ( !defined( 'MEDIAWIKI' ) ) die();
4 -
5 -abstract class SubversionAdaptor {
6 - protected $mRepo;
7 -
8 - public static function newFromRepo( $repo ) {
9 - global $wgSubversionProxy, $wgSubversionProxyTimeout;
10 - if ( $wgSubversionProxy ) {
11 - return new SubversionProxy( $repo, $wgSubversionProxy, $wgSubversionProxyTimeout );
12 - } elseif ( function_exists( 'svn_log' ) ) {
13 - return new SubversionPecl( $repo );
14 - } else {
15 - return new SubversionShell( $repo );
16 - }
17 - }
18 -
19 - function __construct( $repo ) {
20 - $this->mRepo = $repo;
21 - }
22 -
23 - abstract function getFile( $path, $rev = null );
24 -
25 - abstract function getDiff( $path, $rev1, $rev2 );
26 -
27 - abstract function getDirList( $path, $rev = null );
28 -
29 - /*
30 - array of array(
31 - 'rev' => 123,
32 - 'author' => 'myname',
33 - 'msg' => 'log message'
34 - 'date' => '8601 date',
35 - 'paths' => array(
36 - array(
37 - 'action' => one of M, A, D, R
38 - 'path' => repo URL of file,
39 - ),
40 - ...
41 - )
42 - */
43 - abstract function getLog( $path, $startRev = null, $endRev = null );
44 -
45 - protected function _rev( $rev, $default ) {
46 - if ( $rev === null ) {
47 - return $default;
48 - } else {
49 - return intval( $rev );
50 - }
51 - }
52 -}
53 -
54 -/**
55 - * Using the SVN PECL extension...
56 - */
57 -class SubversionPecl extends SubversionAdaptor {
58 - function getFile( $path, $rev = null ) {
59 - return svn_cat( $this->mRepo . $path, $rev );
60 - }
61 -
62 - function getDiff( $path, $rev1, $rev2 ) {
63 - list( $fout, $ferr ) = svn_diff(
64 - $this->mRepo . $path, $rev1,
65 - $this->mRepo . $path, $rev2 );
66 -
67 - if ( $fout ) {
68 - // We have to read out the file descriptors. :P
69 - $out = '';
70 - while ( !feof( $fout ) ) {
71 - $out .= fgets( $fout );
72 - }
73 - fclose( $fout );
74 - fclose( $ferr );
75 -
76 - return $out;
77 - } else {
78 - return new MWException( "Diffing error" );
79 - }
80 - }
81 -
82 - function getDirList( $path, $rev = null ) {
83 - return svn_ls( $this->mRepo . $path,
84 - $this->_rev( $rev, SVN_REVISION_HEAD ) );
85 - }
86 -
87 - function getLog( $path, $startRev = null, $endRev = null ) {
88 - return svn_log( $this->mRepo . $path,
89 - $this->_rev( $startRev, SVN_REVISION_INITIAL ),
90 - $this->_rev( $endRev, SVN_REVISION_HEAD ) );
91 - }
92 -}
93 -
94 -/**
95 - * Using the thingy-bobber
96 - */
97 -class SubversionShell extends SubversionAdaptor {
98 - function getFile( $path, $rev = null ) {
99 - if ( $rev )
100 - $path .= "@$rev";
101 - $command = sprintf(
102 - "svn cat --non-interactive %s %s",
103 - $this->getExtraArgs(),
104 - wfEscapeShellArg( $this->mRepo . $path ) );
105 -
106 - return wfShellExec( $command );
107 - }
108 -
109 - function getDiff( $path, $rev1, $rev2 ) {
110 - $command = sprintf(
111 - "svn diff -r%d:%d --non-interactive %s %s",
112 - intval( $rev1 ),
113 - intval( $rev2 ),
114 - $this->getExtraArgs(),
115 - wfEscapeShellArg( $this->mRepo . $path ) );
116 -
117 - return wfShellExec( $command );
118 - }
119 -
120 - function getLog( $path, $startRev = null, $endRev = null ) {
121 - $lang = wfIsWindows() ? "" : "LC_ALL=en_US.utf-8 ";
122 - $command = sprintf(
123 - "{$lang}svn log -v -r%s:%s --non-interactive %s %s",
124 - wfEscapeShellArg( $this->_rev( $startRev, 'BASE' ) ),
125 - wfEscapeShellArg( $this->_rev( $endRev, 'HEAD' ) ),
126 - $this->getExtraArgs(),
127 - wfEscapeShellArg( $this->mRepo . $path ) );
128 -
129 - $lines = explode( "\n", wfShellExec( $command ) );
130 - $out = array();
131 -
132 - $divider = str_repeat( '-', 72 );
133 - $formats = array(
134 - 'rev' => '/^r(\d+)$/',
135 - 'author' => '/^(.*)$/',
136 - 'date' => '/^(.*?) \(.*\)$/',
137 - 'lines' => '/^(\d+) lines?$/',
138 - );
139 - $state = "start";
140 - foreach ( $lines as $line ) {
141 - $line = rtrim( $line );
142 -
143 - switch( $state ) {
144 - case "start":
145 - if ( $line == $divider ) {
146 - $state = "revdata";
147 - break;
148 - } else {
149 - return $out;
150 - # throw new MWException( "Unexpected start line: $line" );
151 - }
152 - case "revdata":
153 - if ( $line == "" ) {
154 - $state = "done";
155 - break;
156 - }
157 - $data = array();
158 - $bits = explode( " | ", $line );
159 - $i = 0;
160 - foreach ( $formats as $key => $regex ) {
161 - $text = $bits[$i++];
162 - if ( preg_match( $regex, $text, $matches ) ) {
163 - $data[$key] = $matches[1];
164 - } else {
165 - throw new MWException(
166 - "Unexpected format for $key in '$text'" );
167 - }
168 - }
169 - $data['msg'] = '';
170 - $data['paths'] = array();
171 - $state = 'changedpaths';
172 - break;
173 - case "changedpaths":
174 - if ( $line == "Changed paths:" ) { // broken when svn messages are not in English
175 - $state = "path";
176 - } elseif ( $line == "" ) {
177 - // No changed paths?
178 - $state = "msg";
179 - } else {
180 - throw new MWException(
181 - "Expected 'Changed paths:' or '', got '$line'" );
182 - }
183 - break;
184 - case "path":
185 - if ( $line == "" ) {
186 - // Out of paths. Move on to the message...
187 - $state = 'msg';
188 - } else {
189 - if ( preg_match( '/^ (.) (.*)$/', $line, $matches ) ) {
190 - $data['paths'][] = array(
191 - 'action' => $matches[1],
192 - 'path' => $matches[2] );
193 - }
194 - }
195 - break;
196 - case "msg":
197 - $data['msg'] .= $line;
198 - if ( --$data['lines'] ) {
199 - $data['msg'] .= "\n";
200 - } else {
201 - unset( $data['lines'] );
202 - $out[] = $data;
203 - $state = "start";
204 - }
205 - break;
206 - case "done":
207 - throw new MWException( "Unexpected input after end: $line" );
208 - default:
209 - throw new MWException( "Invalid state '$state'" );
210 - }
211 - }
212 -
213 - return $out;
214 - }
215 -
216 - function getDirList( $path, $rev = null ) {
217 - $command = sprintf(
218 - "svn list --xml -r%s --non-interactive %s %s",
219 - wfEscapeShellArg( $this->_rev( $rev, 'HEAD' ) ),
220 - $this->getExtraArgs(),
221 - wfEscapeShellArg( $this->mRepo . $path ) );
222 - $document = new DOMDocument();
223 -
224 - if ( !@$document->loadXML( wfShellExec( $command ) ) )
225 - // svn list --xml returns invalid XML if the file does not exist
226 - // FIXME: report bug upstream
227 - return false;
228 -
229 - $entries = $document->getElementsByTagName( 'entry' );
230 - $result = array();
231 - foreach ( $entries as $entry ) {
232 - $item = array();
233 - $item['type'] = $entry->getAttribute( 'kind' );
234 - foreach ( $entry->childNodes as $child ) {
235 - switch ( $child->nodeName ) {
236 - case 'name':
237 - $item['name'] = $child->textContent;
238 - break;
239 - case 'size':
240 - $item['size'] = intval( $child->textContent );
241 - break;
242 - case 'commit':
243 - $item['created_rev'] = intval( $child->getAttribute( 'revision' ) );
244 - foreach ( $child->childNodes as $commitEntry ) {
245 - switch ( $commitEntry->nodeName ) {
246 - case 'author':
247 - $item['last_author'] = $commitEntry->textContent;
248 - break;
249 - case 'date':
250 - $item['time_t'] = wfTimestamp( TS_UNIX, $commitEntry->textContent );
251 - break;
252 - }
253 - }
254 - break;
255 - }
256 - }
257 - $result[] = $item;
258 - }
259 - return $result;
260 - }
261 -
262 - /**
263 - * Returns a string of extra arguments to be passed into the shell commands
264 - */
265 - private function getExtraArgs() {
266 - global $wgSubversionUser, $wgSubversionPassword;
267 - if ( $wgSubversionUser ) {
268 - return '--username ' . wfEscapeShellArg( $wgSubversionUser )
269 - . ' --password ' . wfEscapeShellArg( $wgSubversionPassword );
270 - }
271 - return '';
272 - }
273 -}
274 -
275 -/**
276 - * Using a remote JSON proxy
277 - */
278 -class SubversionProxy extends SubversionAdaptor {
279 - function __construct( $repo, $proxy, $timeout = 30 ) {
280 - parent::__construct( $repo );
281 - $this->mProxy = $proxy;
282 - $this->mTimeout = $timeout;
283 - }
284 -
285 - function getFile( $path, $rev = null ) {
286 - throw new MWException( "NYI" );
287 - }
288 -
289 - function getDiff( $path, $rev1, $rev2 ) {
290 - return $this->_proxy( array(
291 - 'action' => 'diff',
292 - 'path' => $path,
293 - 'rev1' => $rev1,
294 - 'rev2' => $rev2 ) );
295 - }
296 -
297 - function getLog( $path, $startRev = null, $endRev = null ) {
298 - return $this->_proxy( array(
299 - 'action' => 'log',
300 - 'path' => $path,
301 - 'start' => $startRev,
302 - 'end' => $endRev ) );
303 - }
304 -
305 - function getDirList( $path, $rev = null ) {
306 - return $this->_proxy( array(
307 - 'action' => 'list',
308 - 'path' => $path,
309 - 'rev' => $rev ) );
310 - }
311 -
312 - protected function _proxy( $params ) {
313 - foreach ( $params as $key => $val ) {
314 - if ( is_null( $val ) ) {
315 - // Don't pass nulls to remote
316 - unset( $params[$key] );
317 - }
318 - }
319 - $target = $this->mProxy . '?' . wfArrayToCgi( $params );
320 - $blob = Http::get( $target, $this->mTimeout );
321 - if ( $blob === false ) {
322 - throw new MWException( "SVN proxy error" );
323 - }
324 - $data = unserialize( $blob );
325 - return $data;
326 - }
327 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeStatusListView.php
@@ -1,20 +0,0 @@
2 -<?php
3 -
4 -// Special:Code/MediaWiki/status
5 -class CodeStatusListView extends CodeView {
6 - function __construct( $repoName ) {
7 - parent::__construct();
8 - $this->mRepo = CodeRepository::newFromName( $repoName );
9 - }
10 -
11 - function execute() {
12 - global $wgOut;
13 - $name = $this->mRepo->getName();
14 - $states = CodeRevision::getPossibleStates();
15 - $text = "== " . wfMsg ( "code-field-status" ) . " ==\n";
16 - foreach ( $states as $state ) {
17 - $text .= "* [[Special:Code/$name/status/$state|" . wfMsg ( "code-status-" . $state ) . "]]\n";
18 - }
19 - $wgOut->addWikiText( $text );
20 - }
21 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeCommentsListView.php
@@ -1,113 +0,0 @@
2 -<?php
3 -
4 -// Special:Code/MediaWiki
5 -class CodeCommentsListView extends CodeView {
6 - public $mRepo;
7 -
8 - function __construct( $repoName ) {
9 - parent::__construct();
10 - $this->mRepo = CodeRepository::newFromName( $repoName );
11 - }
12 -
13 - function execute() {
14 - global $wgOut;
15 - $pager = $this->getPager();
16 - $wgOut->addHTML(
17 - $pager->getNavigationBar() .
18 - $pager->getLimitForm() .
19 - $pager->getBody() .
20 - $pager->getNavigationBar()
21 - );
22 - }
23 -
24 - function getPager() {
25 - return new CodeCommentsTablePager( $this );
26 - }
27 - function getRepo() {
28 - return $this->mRepo;
29 - }
30 -}
31 -
32 -// Pager for CodeRevisionListView
33 -class CodeCommentsTablePager extends TablePager {
34 -
35 - function __construct( CodeCommentsListView $view ) {
36 - global $IP;
37 - $this->mView = $view;
38 - $this->mRepo = $view->mRepo;
39 - $this->mDefaultDirection = true;
40 - $this->mCurSVN = SpecialVersion::getSvnRevision( $IP );
41 - parent::__construct();
42 - }
43 -
44 - function isFieldSortable( $field ) {
45 - return $field == 'cr_timestamp';
46 - }
47 -
48 - function getDefaultSort() { return 'cc_timestamp'; }
49 -
50 - function getQueryInfo() {
51 - return array(
52 - 'tables' => array( 'code_comment', 'code_rev' ),
53 - 'fields' => array_keys( $this->getFieldNames() ),
54 - 'conds' => array( 'cc_repo_id' => $this->mRepo->getId() ),
55 - 'join_conds' => array(
56 - 'code_rev' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' )
57 - )
58 - );
59 - }
60 -
61 - function getFieldNames() {
62 - return array(
63 - 'cc_timestamp' => wfMsg( 'code-field-timestamp' ),
64 - 'cc_user_text' => wfMsg( 'code-field-user' ),
65 - 'cc_rev_id' => wfMsg( 'code-field-id' ),
66 - 'cr_status' => wfMsg( 'code-field-status' ),
67 - 'cr_message' => wfMsg( 'code-field-message' ),
68 - 'cc_text' => wfMsg( 'code-field-text' ),
69 - );
70 - }
71 -
72 - function formatValue( $name, $value ) {
73 - global $wgUser, $wgLang;
74 - switch( $name ) {
75 - case 'cc_rev_id':
76 - return $this->mView->mSkin->link(
77 - SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value . '#code-comments' ),
78 - htmlspecialchars( $value ) );
79 - case 'cr_status':
80 - return $this->mView->mSkin->link(
81 - SpecialPage::getTitleFor( 'Code',
82 - $this->mRepo->getName() . '/status/' . $value ),
83 - htmlspecialchars( $this->mView->statusDesc( $value ) ) );
84 - case 'cc_user_text':
85 - return $this->mView->mSkin->userLink( -1, $value );
86 - case 'cr_message':
87 - return $this->mView->messageFragment( $value );
88 - case 'cc_text':
89 - # Truncate this, blah blah...
90 - return htmlspecialchars( $wgLang->truncate( $value, 300 ) );
91 - case 'cc_timestamp':
92 - global $wgLang;
93 - return $wgLang->timeanddate( $value, true );
94 - }
95 - }
96 -
97 - // Note: this function is poorly factored in the parent class
98 - function formatRow( $row ) {
99 - global $wgWikiSVN;
100 - $class = "mw-codereview-status-{$row->cr_status}";
101 - if ( $this->mRepo->mName == $wgWikiSVN ) {
102 - $class .= " mw-codereview-" . ( $row->cc_rev_id <= $this->mCurSVN ? 'live' : 'notlive' );
103 - }
104 - return str_replace(
105 - '<tr>',
106 - Xml::openElement( 'tr',
107 - array( 'class' => $class ) ),
108 - parent::formatRow( $row ) );
109 - }
110 -
111 - function getTitle() {
112 - return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/comments' );
113 - }
114 -}
Index: branches/wmf-deployment/extensions/CodeReview/ApiCodeComments.php
@@ -1,139 +0,0 @@
2 -<?php
3 -
4 -/*
5 - * Created on Oct 29, 2008
6 - *
7 - * API for MediaWiki 1.8+
8 - *
9 - * Copyright (C) 2008 Bryan Tong Minh <Bryan.TongMinh@Gmail.com>
10 - *
11 - * This program is free software; you can redistribute it and/or modify
12 - * it under the terms of the GNU General Public License as published by
13 - * the Free Software Foundation; either version 2 of the License, or
14 - * (at your option) any later version.
15 - *
16 - * This program is distributed in the hope that it will be useful,
17 - * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 - * GNU General Public License for more details.
20 - *
21 - * You should have received a copy of the GNU General Public License along
22 - * with this program; if not, write to the Free Software Foundation, Inc.,
23 - * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 - * http://www.gnu.org/copyleft/gpl.html
25 - */
26 -
27 -class ApiCodeComments extends ApiQueryBase {
28 - public function __construct( $query, $moduleName ) {
29 - parent::__construct( $query, $moduleName, 'cc' );
30 - }
31 -
32 - public function execute() {
33 - global $wgUser;
34 - // Before doing anything at all, let's check permissions
35 - if( !$wgUser->isAllowed('codereview-use') ) {
36 - $this->dieUsage('You don\'t have permission to view code comments','permissiondenied');
37 - }
38 - $params = $this->extractRequestParams();
39 - if ( is_null( $params['repo'] ) )
40 - $this->dieUsageMsg( array( 'missingparam', 'repo' ) );
41 - $this->props = array_flip( $params['prop'] );
42 -
43 - $listview = new CodeCommentsListView( $params['repo'] );
44 - if ( is_null( $listview->getRepo() ) )
45 - $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
46 - $pager = $listview->getPager();
47 -
48 - if ( !is_null( $params['start'] ) )
49 - $pager->setOffset( $this->getDB()->timestamp( $params['start'] ) );
50 - $limit = $params['limit'];
51 - $pager->setLimit( $limit );
52 -
53 - $pager->doQuery();
54 -
55 - $comments = $pager->getResult();
56 - $data = array();
57 -
58 - $count = 0;
59 - $lastTimestamp = 0;
60 - while ( $row = $comments->fetchObject() ) {
61 - if ( $count == $limit ) {
62 - $this->setContinueEnumParameter( 'start',
63 - wfTimestamp( TS_ISO_8601, $lastTimestamp ) );
64 - break;
65 - }
66 -
67 - $data[] = $this->formatRow( $row );
68 - $lastTimestamp = $row->cc_timestamp;
69 - $count++;
70 - }
71 - $comments->free();
72 -
73 - $result = $this->getResult();
74 - $result->setIndexedTagName( $data, 'comment' );
75 - $result->addValue( 'query', $this->getModuleName(), $data );
76 - }
77 -
78 - private function formatRow( $row ) {
79 - $item = array();
80 - if ( isset( $this->props['timestamp'] ) )
81 - $item['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cc_timestamp );
82 - if ( isset( $this->props['user'] ) )
83 - $item['user'] = $row->cc_user_text;
84 - if ( isset( $this->props['revision'] ) )
85 - $item['status'] = $row->cr_status;
86 - if ( isset( $this->props['text'] ) )
87 - ApiResult::setContent( $item, $row->cc_text );
88 - return $item;
89 - }
90 -
91 - public function getAllowedParams() {
92 - return array (
93 - 'repo' => null,
94 - 'limit' => array (
95 - ApiBase :: PARAM_DFLT => 10,
96 - ApiBase :: PARAM_TYPE => 'limit',
97 - ApiBase :: PARAM_MIN => 1,
98 - ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
99 - ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
100 - ),
101 - 'start' => array(
102 - ApiBase :: PARAM_TYPE => 'timestamp'
103 - ),
104 - 'prop' => array (
105 - ApiBase :: PARAM_ISMULTI => true,
106 - ApiBase :: PARAM_DFLT => 'timestamp|user|revision',
107 - ApiBase :: PARAM_TYPE => array (
108 - 'timestamp',
109 - 'user',
110 - 'revision',
111 - 'text',
112 - ),
113 - ),
114 - );
115 - }
116 -
117 - public function getParamDescription() {
118 - return array(
119 - 'repo' => 'Name of the repository',
120 - 'limit' => 'How many comments to return',
121 - 'start' => 'Timestamp to start listing at',
122 - 'prop' => 'Which properties to return',
123 - );
124 - }
125 -
126 - public function getDescription() {
127 - return 'List comments on revisions in CodeReview';
128 - }
129 -
130 - public function getExamples() {
131 - return array(
132 - 'api.php?action=query&list=codecomments&ccrepo=MediaWiki',
133 - 'api.php?action=query&list=codecomments&ccrepo=MediaWiki&ccprop=timestamp|user|revision|text',
134 - );
135 - }
136 -
137 - public function getVersion() {
138 - return __CLASS__ . ': $Id$';
139 - }
140 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRepository.php
@@ -1,339 +0,0 @@
2 -<?php
3 -if ( !defined( 'MEDIAWIKI' ) ) die();
4 -
5 -class CodeRepository {
6 - static $userLinks = array();
7 -
8 - public static function newFromName( $name ) {
9 - $dbw = wfGetDB( DB_MASTER );
10 - $row = $dbw->selectRow(
11 - 'code_repo',
12 - array(
13 - 'repo_id',
14 - 'repo_name',
15 - 'repo_path',
16 - 'repo_viewvc',
17 - 'repo_bugzilla' ),
18 - array( 'repo_name' => $name ),
19 - __METHOD__ );
20 -
21 - if ( $row ) {
22 - return self::newFromRow( $row );
23 - } else {
24 - return null;
25 - }
26 - }
27 -
28 - public static function newFromId( $id ) {
29 - $dbw = wfGetDB( DB_MASTER );
30 - $row = $dbw->selectRow(
31 - 'code_repo',
32 - array(
33 - 'repo_id',
34 - 'repo_name',
35 - 'repo_path',
36 - 'repo_viewvc',
37 - 'repo_bugzilla' ),
38 - array( 'repo_id' => intval( $id ) ),
39 - __METHOD__ );
40 -
41 - if ( $row ) {
42 - return self::newFromRow( $row );
43 - } else {
44 - return null;
45 - }
46 - }
47 -
48 - static function newFromRow( $row ) {
49 - $repo = new CodeRepository();
50 - $repo->mId = intval( $row->repo_id );
51 - $repo->mName = $row->repo_name;
52 - $repo->mPath = $row->repo_path;
53 - $repo->mViewVc = $row->repo_viewvc;
54 - $repo->mBugzilla = $row->repo_bugzilla;
55 - return $repo;
56 - }
57 -
58 - static function getRepoList() {
59 - $dbr = wfGetDB( DB_SLAVE );
60 - $res = $dbr->select( 'code_repo', '*', array(), __METHOD__ );
61 - $repos = array();
62 - foreach ( $res as $row ) {
63 - $repos[] = self::newFromRow( $row );
64 - }
65 - return $repos;
66 - }
67 -
68 - public function getId() {
69 - return intval( $this->mId );
70 - }
71 -
72 - public function getName() {
73 - return $this->mName;
74 - }
75 -
76 - public function getPath() {
77 - return $this->mPath;
78 - }
79 -
80 - public function getViewVcBase() {
81 - return $this->mViewVc;
82 - }
83 -
84 - /**
85 - * Return a bug URL or false.
86 - */
87 - public function getBugPath( $bugId ) {
88 - if ( $this->mBugzilla ) {
89 - return str_replace( '$1',
90 - urlencode( $bugId ), $this->mBugzilla );
91 - }
92 - return false;
93 - }
94 -
95 - public function getLastStoredRev() {
96 - $dbr = wfGetDB( DB_SLAVE );
97 - $row = $dbr->selectField(
98 - 'code_rev',
99 - 'MAX(cr_id)',
100 - array( 'cr_repo_id' => $this->getId() ),
101 - __METHOD__
102 - );
103 - return intval( $row );
104 - }
105 -
106 - public function getAuthorList() {
107 - global $wgMemc;
108 - $key = wfMemcKey( 'codereview', 'authors', $this->getId() );
109 - $authors = $wgMemc->get( $key );
110 - if ( is_array( $authors ) ) {
111 - return $authors;
112 - }
113 - $dbr = wfGetDB( DB_SLAVE );
114 - $res = $dbr->select(
115 - 'code_rev',
116 - array( 'cr_author', 'MAX(cr_timestamp) AS time' ),
117 - array( 'cr_repo_id' => $this->getId() ),
118 - __METHOD__,
119 - array( 'GROUP BY' => 'cr_author',
120 - 'ORDER BY' => 'time DESC', 'LIMIT' => 500 )
121 - );
122 - $authors = array();
123 - while ( $row = $dbr->fetchObject( $res ) ) {
124 - $authors[] = $row->cr_author;
125 - }
126 - $wgMemc->set( $key, $authors, 3600 * 24 * 3 );
127 - return $authors;
128 - }
129 -
130 - public function getTagList() {
131 - global $wgMemc;
132 - $key = wfMemcKey( 'codereview', 'tags', $this->getId() );
133 - $tags = $wgMemc->get( $key );
134 - if ( is_array( $tags ) ) {
135 - return $tags;
136 - }
137 - $dbr = wfGetDB( DB_SLAVE );
138 - $res = $dbr->select(
139 - 'code_tags',
140 - array( 'ct_tag', 'COUNT(*) AS revs' ),
141 - array( 'ct_repo_id' => $this->getId() ),
142 - __METHOD__,
143 - array( 'GROUP BY' => 'ct_tag',
144 - 'ORDER BY' => 'revs DESC', 'LIMIT' => 500 )
145 - );
146 - $tags = array();
147 - while ( $row = $dbr->fetchObject( $res ) ) {
148 - $tags[] = $row->ct_tag;
149 - }
150 - $wgMemc->set( $key, $tags, 3600 * 24 * 3 );
151 - return $tags;
152 - }
153 -
154 - /**
155 - * Load a particular revision out of the DB
156 - */
157 - public function getRevision( $id ) {
158 - if ( !$this->isValidRev( $id ) ) {
159 - return null;
160 - }
161 - $dbr = wfGetDB( DB_SLAVE );
162 - $row = $dbr->selectRow( 'code_rev',
163 - '*',
164 - array(
165 - 'cr_id' => $id,
166 - 'cr_repo_id' => $this->getId(),
167 - ),
168 - __METHOD__
169 - );
170 - if ( !$row )
171 - throw new MWException( 'Failed to load expected revision data' );
172 - return CodeRevision::newFromRow( $this, $row );
173 - }
174 -
175 - /**
176 - * @param int $rev Revision ID
177 - * @param $useCache 'skipcache' to avoid caching
178 - * 'cached' to *only* fetch if cached
179 - */
180 - public function getDiff( $rev, $useCache = '' ) {
181 - global $wgMemc;
182 - wfProfileIn( __METHOD__ );
183 -
184 - $rev1 = $rev - 1;
185 - $rev2 = $rev;
186 -
187 - $revision = $this->getRevision( $rev );
188 - if ( $revision == null || !$revision->isDiffable() ) {
189 - wfProfileOut( __METHOD__ );
190 - return false;
191 - }
192 -
193 - # Try memcached...
194 - $key = wfMemcKey( 'svn', md5( $this->mPath ), 'diff', $rev1, $rev2 );
195 - if ( $useCache === 'skipcache' ) {
196 - $data = NULL;
197 - } else {
198 - $data = $wgMemc->get( $key );
199 - }
200 -
201 - # Try the database...
202 - if ( !$data && $useCache != 'skipcache' ) {
203 - $dbr = wfGetDB( DB_SLAVE );
204 - $row = $dbr->selectRow( 'code_rev',
205 - array( 'cr_diff', 'cr_flags' ),
206 - array( 'cr_repo_id' => $this->mId, 'cr_id' => $rev, 'cr_diff IS NOT NULL' ),
207 - __METHOD__
208 - );
209 - if ( $row ) {
210 - $flags = explode( ',', $row->cr_flags );
211 - $data = $row->cr_diff;
212 - // If the text was fetched without an error, convert it
213 - if ( $data !== false && in_array( 'gzip', $flags ) ) {
214 - # Deal with optional compression of archived pages.
215 - # This can be done periodically via maintenance/compressOld.php, and
216 - # as pages are saved if $wgCompressRevisions is set.
217 - $data = gzinflate( $data );
218 - }
219 - }
220 - }
221 -
222 - # Generate the diff as needed...
223 - if ( !$data && $useCache !== 'cached' ) {
224 - $svn = SubversionAdaptor::newFromRepo( $this->mPath );
225 - $data = $svn->getDiff( '', $rev1, $rev2 );
226 - // Store to cache
227 - $wgMemc->set( $key, $data, 3600 * 24 * 3 );
228 - // Permanent DB storage
229 - $storedData = $data;
230 - $flags = Revision::compressRevisionText( $storedData );
231 - $dbw = wfGetDB( DB_MASTER );
232 - $dbw->update( 'code_rev',
233 - array( 'cr_diff' => $storedData, 'cr_flags' => $flags ),
234 - array( 'cr_repo_id' => $this->mId, 'cr_id' => $rev ),
235 - __METHOD__
236 - );
237 - }
238 - wfProfileOut( __METHOD__ );
239 - return $data;
240 - }
241 -
242 - /**
243 - * Is the requested revid a valid revision to show?
244 - * @return bool
245 - * @param $rev int Rev id to check
246 - */
247 - public function isValidRev( $rev ) {
248 - $rev = intval( $rev );
249 - if ( $rev > 0 && $rev <= $this->getLastStoredRev() ) {
250 - return true;
251 - }
252 - return false;
253 - }
254 -
255 - /*
256 - * Link the $author to the wikiuser $user
257 - * @param string $author
258 - * @param User $user
259 - * @return bool success
260 - */
261 - public function linkUser( $author, User $user ) {
262 - // We must link to an existing user
263 - if ( !$user->getId() ) {
264 - return false;
265 - }
266 - $dbw = wfGetDB( DB_MASTER );
267 - // Insert in the auther -> user link row.
268 - // Skip existing rows.
269 - $dbw->insert( 'code_authors',
270 - array(
271 - 'ca_repo_id' => $this->getId(),
272 - 'ca_author' => $author,
273 - 'ca_user_text' => $user->getName()
274 - ),
275 - __METHOD__,
276 - array( 'IGNORE' )
277 - );
278 - // If the last query already found a row, then update it.
279 - if ( !$dbw->affectedRows() ) {
280 - $dbw->update(
281 - 'code_authors',
282 - array( 'ca_user_text' => $user->getName() ),
283 - array(
284 - 'ca_repo_id' => $this->getId(),
285 - 'ca_author' => $author,
286 - ),
287 - __METHOD__
288 - );
289 - }
290 - self::$userLinks[$author] = $user;
291 - return ( $dbw->affectedRows() > 0 );
292 - }
293 -
294 - /*
295 - * Remove local user links for $author
296 - * @param string $author
297 - * @return bool success
298 - */
299 - public function unlinkUser( $author ) {
300 - $dbw = wfGetDB( DB_MASTER );
301 - $dbw->delete(
302 - 'code_authors',
303 - array(
304 - 'ca_repo_id' => $this->getId(),
305 - 'ca_author' => $author,
306 - ),
307 - __METHOD__
308 - );
309 - self::$userLinks[$author] = false;
310 - return ( $dbw->affectedRows() > 0 );
311 - }
312 -
313 - /*
314 - * returns a User object if $author has a wikiuser associated,
315 - * or false
316 - */
317 - public function authorWikiUser( $author ) {
318 - if ( isset( self::$userLinks[$author] ) )
319 - return self::$userLinks[$author];
320 -
321 - $dbr = wfGetDB( DB_SLAVE );
322 - $wikiUser = $dbr->selectField(
323 - 'code_authors',
324 - 'ca_user_text',
325 - array(
326 - 'ca_repo_id' => $this->getId(),
327 - 'ca_author' => $author,
328 - ),
329 - __METHOD__
330 - );
331 - $user = null;
332 - if ( $wikiUser )
333 - $user = User::newFromName( $wikiUser );
334 - if ( $user instanceof User )
335 - $res = $user;
336 - else
337 - $res = false;
338 - return self::$userLinks[$author] = $res;
339 - }
340 -}
Index: branches/wmf-deployment/extensions/CodeReview/SpecialCode.php
@@ -1,237 +0,0 @@
2 -<?php
3 -if ( !defined( 'MEDIAWIKI' ) ) die();
4 -
5 -class SpecialCode extends SpecialPage {
6 - function __construct() {
7 - parent::__construct( 'Code' , 'codereview-use' );
8 - }
9 -
10 - function execute( $subpage ) {
11 - global $wgOut, $wgRequest, $wgUser, $wgScriptPath, $wgCodeReviewStyleVersion;
12 -
13 - wfLoadExtensionMessages( 'CodeReview' );
14 -
15 - if( !$this->userCanExecute( $wgUser ) ) {
16 - $this->displayRestrictionError();
17 - return;
18 - }
19 -
20 - $this->setHeaders();
21 - $wgOut->addStyle( "$wgScriptPath/extensions/CodeReview/codereview.css?$wgCodeReviewStyleVersion" );
22 - # Remove stray slashes
23 - $subpage = preg_replace( '/\/$/', '', $subpage );
24 -
25 - if ( $subpage == '' ) {
26 - $view = new CodeRepoListView();
27 - } else {
28 - $params = explode( '/', $subpage );
29 - switch( count( $params ) ) {
30 - case 1:
31 - $view = new CodeRevisionListView( $params[0] );
32 - break;
33 - case 2:
34 - if ( $params[1] === 'tag' ) {
35 - $view = new CodeTagListView( $params[0] );
36 - break;
37 - } elseif ( $params[1] === 'author' ) {
38 - $view = new CodeAuthorListView( $params[0] );
39 - break;
40 - } elseif ( $params[1] === 'status' ) {
41 - $view = new CodeStatusListView( $params[0] );
42 - break;
43 - } elseif ( $params[1] === 'comments' ) {
44 - $view = new CodeCommentsListView( $params[0] );
45 - break;
46 - } elseif ( $params[1] === 'releasenotes' ) {
47 - $view = new CodeReleaseNotes( $params[0] );
48 - break;
49 - } else if ( $wgRequest->wasPosted() && !$wgRequest->getCheck( 'wpPreview' ) ) {
50 - # Add any tags, Set status, Adds comments
51 - $submit = new CodeRevisionCommitter( $params[0], $params[1] );
52 - $submit->execute();
53 - return;
54 - } else { // revision details
55 - $view = new CodeRevisionView( $params[0], $params[1] );
56 - break;
57 - }
58 - case 3:
59 - if ( $params[1] === 'tag' ) {
60 - $view = new CodeRevisionTagView( $params[0], $params[2] );
61 - break;
62 - } elseif ( $params[1] === 'author' ) {
63 - $view = new CodeRevisionAuthorView( $params[0], $params[2] );
64 - break;
65 - } elseif ( $params[1] === 'status' ) {
66 - $view = new CodeRevisionStatusView( $params[0], $params[2] );
67 - break;
68 - } elseif ( $params[1] === 'comments' ) {
69 - $view = new CodeCommentsListView( $params[0] );
70 - break;
71 - } else {
72 - # Nonsense parameters, back out
73 - if ( empty( $params[1] ) )
74 - $view = new CodeRevisionListView( $params[0] );
75 - else
76 - $view = new CodeRevisionView( $params[0], $params[1] );
77 - break;
78 - }
79 - case 4:
80 - if ( $params[1] == 'author' && $params[3] == 'link' ) {
81 - $view = new CodeRevisionAuthorLink( $params[0], $params[2] );
82 - break;
83 - }
84 - default:
85 - if ( $params[2] == 'reply' ) {
86 - $view = new CodeRevisionView( $params[0], $params[1], $params[3] );
87 - break;
88 - }
89 - $wgOut->addWikiText( wfMsg( 'nosuchactiontext' ) );
90 - $wgOut->returnToMain( null, SpecialPage::getTitleFor( 'Code' ) );
91 - return;
92 - }
93 - }
94 - $view->execute();
95 -
96 - // Add subtitle for easy navigation
97 - global $wgOut;
98 - if ( $view instanceof CodeView && ( $repo = $view->getRepo() ) ) {
99 - $wgOut->setSubtitle( wfMsgExt( 'codereview-subtitle', 'parse', CodeRepoListView::getNavItem( $repo->getName() ) ) );
100 - }
101 - }
102 -}
103 -
104 -/**
105 - * Extended by CodeRevisionListView and CodeRevisionView
106 - */
107 -abstract class CodeView {
108 - var $mRepo;
109 -
110 - function __construct() {
111 - global $wgUser;
112 - $this->mSkin = $wgUser->getSkin();
113 - }
114 -
115 - function validPost( $permission ) {
116 - global $wgRequest, $wgUser;
117 - return $wgRequest->wasPosted()
118 - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) )
119 - && $wgUser->isAllowed( $permission );
120 - }
121 -
122 - abstract function execute();
123 -
124 - /*
125 - * returns a User object if $author has a wikiuser associated,
126 - * of false
127 - */
128 - function authorWikiUser( $author ) {
129 - return $this->mRepo->authorWikiUser( $author );
130 - }
131 -
132 - function authorLink( $author ) {
133 - $repo = $this->mRepo->getName();
134 - $special = SpecialPage::getTitleFor( 'Code', "$repo/author/$author" );
135 - return $this->mSkin->link( $special, htmlspecialchars( $author ) );
136 - }
137 -
138 - function statusDesc( $status ) {
139 - return wfMsg( "code-status-$status" );
140 - }
141 -
142 - function formatMessage( $text ) {
143 - $text = nl2br( htmlspecialchars( $text ) );
144 - $linker = new CodeCommentLinkerHtml( $this->mRepo );
145 - return $linker->link( $text );
146 - }
147 -
148 - function messageFragment( $value ) {
149 - global $wgLang;
150 - $message = trim( $value );
151 - $lines = explode( "\n", $message, 2 );
152 - $first = $lines[0];
153 - $trimmed = $wgLang->truncate( $first, 80 );
154 - return $this->formatMessage( $trimmed );
155 - }
156 - /*
157 - * Formatted HTML array for properties display
158 - * @param array fields : 'propname' => HTML data
159 - */
160 - function formatMetaData( $fields ) {
161 - $html = '<table class="mw-codereview-meta">';
162 - foreach ( $fields as $label => $data ) {
163 - $html .= "<tr><td>" . wfMsgHtml( $label ) . "</td><td>$data</td></tr>\n";
164 - }
165 - return $html . "</table>\n";
166 - }
167 -
168 - function getRepo() {
169 - if ( $this->mRepo )
170 - return $this->mRepo;
171 - return false;
172 - }
173 -}
174 -
175 -class CodeCommentLinker {
176 - function __construct( $repo ) {
177 - global $wgUser;
178 - $this->mSkin = $wgUser->getSkin();
179 - $this->mRepo = $repo;
180 - }
181 -
182 - function link( $text ) {
183 - # Catch links like http://www.mediawiki.org/wiki/Special:Code/MediaWiki/44245#c829
184 - # Ended by space or brackets (like those pesky <br/> tags)
185 - $text = preg_replace_callback( '/(\b)(' . wfUrlProtocols() . ')([^ <>]+)(\b)/', array( $this, 'generalLink' ), $text );
186 - $text = preg_replace_callback( '/\br(\d+)\b/', array( $this, 'messageRevLink' ), $text );
187 - $text = preg_replace_callback( '/\bbug #?(\d+)\b/i', array( $this, 'messageBugLink' ), $text );
188 - return $text;
189 - }
190 -
191 - function generalLink( $arr ) {
192 - $url = $arr[2] . $arr[3];
193 - // Re-add the surrounding space/punctuation
194 - return $arr[1] . $this->makeExternalLink( $url, $url ) . $arr[4];
195 - }
196 -
197 - function messageBugLink( $arr ) {
198 - $text = $arr[0];
199 - $bugNo = intval( $arr[1] );
200 - $url = $this->mRepo->getBugPath( $bugNo );
201 - if ( $url ) {
202 - return $this->makeExternalLink( $url, $text );
203 - } else {
204 - return $text;
205 - }
206 - }
207 -
208 - function messageRevLink( $matches ) {
209 - $text = $matches[0];
210 - $rev = intval( $matches[1] );
211 -
212 - $repo = $this->mRepo->getName();
213 - $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" );
214 -
215 - return $this->makeInternalLink( $title, $text );
216 - }
217 -
218 -}
219 -
220 -class CodeCommentLinkerHtml extends CodeCommentLinker {
221 - function makeExternalLink( $url, $text ) {
222 - return $this->mSkin->makeExternalLink( $url, $text );
223 - }
224 -
225 - function makeInternalLink( $title, $text ) {
226 - return $this->mSkin->link( $title, $text );
227 - }
228 -}
229 -
230 -class CodeCommentLinkerWiki extends CodeCommentLinker {
231 - function makeExternalLink( $url, $text ) {
232 - return "[$url $text]";
233 - }
234 -
235 - function makeInternalLink( $title, $text ) {
236 - return "[[" . $title->getPrefixedText() . "|$text]]";
237 - }
238 -}
Index: branches/wmf-deployment/extensions/CodeReview/SpecialRepoAdmin.php
@@ -1,123 +0,0 @@
2 -<?php
3 -if ( !defined( 'MEDIAWIKI' ) ) die();
4 -
5 -class SpecialRepoAdmin extends SpecialPage {
6 - function __construct() {
7 - parent::__construct( 'RepoAdmin', 'repoadmin' );
8 - }
9 -
10 - function execute( $subpage ) {
11 - global $wgRequest, $wgUser;
12 -
13 - wfLoadExtensionMessages( 'CodeReview' );
14 - $this->setHeaders();
15 -
16 - if ( !$this->userCanExecute( $wgUser ) ) {
17 - $this->displayRestrictionError();
18 - return;
19 - }
20 -
21 - $repo = $wgRequest->getVal( 'repo', $subpage );
22 - if ( $repo == '' ) {
23 - $view = new RepoAdminListView( $this );
24 - } else {
25 - $view = new RepoAdminRepoView( $this, $repo );
26 - }
27 - $view->execute();
28 - }
29 -}
30 -
31 -class RepoAdminListView {
32 - var $mPage;
33 -
34 - function __construct( $page ) {
35 - $this->mPage = $page;
36 - }
37 -
38 - function getForm() {
39 - global $wgScript;
40 - return Xml::fieldset( wfMsg( 'repoadmin-new-legend' ) ) .
41 - Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
42 - Xml::hidden( 'title', $this->mPage->getTitle()->getPrefixedDBKey() ) .
43 - Xml::inputLabel( wfMsg( 'repoadmin-new-label' ), 'repo', 'repo' ) .
44 - Xml::submitButton( wfMsg( 'repoadmin-new-button' ) ) .
45 - '</form></fieldset>';
46 - }
47 -
48 - function execute() {
49 - global $wgOut;
50 - $wgOut->addHTML( $this->getForm() );
51 - $repos = CodeRepository::getRepoList();
52 - if ( !count( $repos ) ) {
53 - return;
54 - }
55 - $text = '';
56 - foreach ( $repos as $repo ) {
57 - $name = $repo->getName();
58 - $text .= "* [[Special:RepoAdmin/$name|$name]]\n";
59 - }
60 - $wgOut->addWikiText( $text );
61 - }
62 -}
63 -
64 -class RepoAdminRepoView {
65 - var $mPage;
66 -
67 - function __construct( $page, $repo ) {
68 - $this->mPage = $page;
69 - $this->mRepoName = $repo;
70 - $this->mRepo = CodeRepository::newFromName( $repo );
71 - }
72 -
73 - function execute() {
74 - global $wgOut, $wgRequest, $wgUser;
75 - $repoExists = (bool)$this->mRepo;
76 - $repoPath = $wgRequest->getVal( 'wpRepoPath', $repoExists ? $this->mRepo->mPath : '' );
77 - $bugPath = $wgRequest->getVal( 'wpBugPath', $repoExists ? $this->mRepo->mBugzilla : '' );
78 - $viewPath = $wgRequest->getVal( 'wpViewPath', $repoExists ? $this->mRepo->mViewVc : '' );
79 - if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $this->mRepoName ) ) {
80 - // @todo log
81 - $dbw = wfGetDB( DB_MASTER );
82 - if ( $repoExists ) {
83 - $dbw->update(
84 - 'code_repo',
85 - array(
86 - 'repo_path' => $repoPath,
87 - 'repo_viewvc' => $viewPath,
88 - 'repo_bugzilla' => $bugPath
89 - ),
90 - array( 'repo_id' => $this->mRepo->getId() ),
91 - __METHOD__
92 - );
93 - } else {
94 - $dbw->insert(
95 - 'code_repo',
96 - array(
97 - 'repo_name' => $this->mRepoName,
98 - 'repo_path' => $repoPath,
99 - 'repo_viewvc' => $viewPath,
100 - 'repo_bugzilla' => $bugPath
101 - ),
102 - __METHOD__
103 - );
104 - }
105 - $wgOut->wrapWikiMsg( '<div class="successbox">$1</div>', array( 'repoadmin-edit-sucess', $this->mRepoName ) );
106 - return;
107 - }
108 - $wgOut->addHTML(
109 - Xml::fieldset( wfMsg( 'repoadmin-edit-legend', $this->mRepoName ) ) .
110 - Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mPage->getTitle( $this->mRepoName )->getLocalURL() ) ) .
111 - Xml::buildForm(
112 - array(
113 - 'repoadmin-edit-path' =>
114 - Xml::input( 'wpRepoPath', 60, $repoPath ),
115 - 'repoadmin-edit-bug' =>
116 - Xml::input( 'wpBugPath', 60, $bugPath ),
117 - 'repoadmin-edit-view' =>
118 - Xml::input( 'wpViewPath', 60, $viewPath ) ) ) .
119 - Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->mRepoName ) ) .
120 - Xml::submitButton( wfMsg( 'repoadmin-edit-button' ) ) .
121 - '</form></fieldset>'
122 - );
123 - }
124 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionTagView.php
@@ -1,33 +0,0 @@
2 -<?php
3 -
4 -class CodeRevisionTagView extends CodeRevisionListView {
5 - function __construct( $repoName, $tag ) {
6 - parent::__construct( $repoName );
7 - $this->mTag = $tag;
8 - }
9 -
10 - function getPager() {
11 - return new SvnRevTagTablePager( $this, $this->mTag );
12 - }
13 -}
14 -
15 -class SvnRevTagTablePager extends SvnRevTablePager {
16 - function __construct( $view, $tag ) {
17 - parent::__construct( $view );
18 - $this->mTag = $tag;
19 - }
20 -
21 - function getQueryInfo() {
22 - $info = parent::getQueryInfo();
23 - $info['tables'][] = 'code_tags';
24 - $info['conds'][] = 'cr_repo_id=ct_repo_id';
25 - $info['conds'][] = 'cr_id=ct_rev_id';
26 - $info['conds']['ct_tag'] = $this->mTag; // fixme: normalize input?
27 - return $info;
28 - }
29 -
30 - function getTitle() {
31 - $repo = $this->mRepo->getName();
32 - return SpecialPage::getTitleFor( 'Code', "$repo/tag/$this->mTag" );
33 - }
34 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodePropChange.php
@@ -1,24 +0,0 @@
2 -<?php
3 -if ( !defined( 'MEDIAWIKI' ) ) die();
4 -
5 -class CodePropChange {
6 - function __construct( $rev ) {
7 - $this->rev = $rev;
8 - }
9 -
10 - static function newFromRow( $rev, $row ) {
11 - return self::newFromData( $rev, get_object_vars( $row ) );
12 - }
13 -
14 - static function newFromData( $rev, $data ) {
15 - $change = new CodeComment( $rev );
16 - $change->attrib = $data['cpc_attrib'];
17 - $change->removed = $data['cpc_removed'];
18 - $change->added = $data['cpc_added'];
19 - $change->user = $data['cpc_user'];
20 - // We'd prefer the up to date user table name
21 - $change->userText = isset( $data['user_name'] ) ? $data['user_name'] : $data['cpc_user_text'];
22 - $change->timestamp = wfTimestamp( TS_MW, $data['cpc_timestamp'] );
23 - return $change;
24 - }
25 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeAuthorListView.php
@@ -1,21 +0,0 @@
2 -<?php
3 -
4 -// Special:Code/MediaWiki/author
5 -class CodeAuthorListView extends CodeView {
6 - function __construct( $repoName ) {
7 - parent::__construct();
8 - $this->mRepo = CodeRepository::newFromName( $repoName );
9 - }
10 -
11 - function execute() {
12 - global $wgOut;
13 - $authors = $this->mRepo->getAuthorList();
14 - $name = $this->mRepo->getName();
15 - $text = wfMsg( 'code-authors-text' ) . "\n";
16 - foreach ( $authors as $user ) {
17 - if ( $user )
18 - $text .= "* [[Special:Code/$name/author/$user|$user]]\n";
19 - }
20 - $wgOut->addWikiText( $text );
21 - }
22 -}
Index: branches/wmf-deployment/extensions/CodeReview/DiffHighlighter.php
@@ -1,64 +0,0 @@
2 -<?php
3 -
4 -/**
5 - * Highlight a SVN diff for easier readibility
6 - */
7 -class CodeDiffHighlighter {
8 -
9 - /**
10 - * Main entry point. Given a diff text, highlight it
11 - * and wrap it in a div
12 - * @param $text string Text to highlight
13 - * @return string
14 - */
15 - function render( $text ) {
16 - return '<pre class="mw-codereview-diff">' .
17 - $this->splitLines( $text ) .
18 - "</pre>\n";
19 - }
20 -
21 - /**
22 - * Given a bunch of text, split it into individual
23 - * lines, color them, then put it back into one big
24 - * string
25 - * @param $text string Text to split and highlight
26 - * @return string
27 - */
28 - function splitLines( $text ) {
29 - return implode( "\n",
30 - array_map( array( $this, 'colorLine' ),
31 - explode( "\n", $text ) ) );
32 - }
33 -
34 - /**
35 - * Turn a diff line into a properly formatted string suitable
36 - * for output
37 - * @param $line string Line from a diff
38 - * @return string
39 - */
40 - function colorLine( $line ) {
41 - list( $element, $attribs ) = $this->tagForLine( $line );
42 - return Xml::element( $element, $attribs, $line );
43 - }
44 -
45 - /**
46 - * Take a line of a diff and apply the appropriate stylings
47 - * @param $line string Line to check
48 - * @return array
49 - */
50 - function tagForLine( $line ) {
51 - static $default = array( 'span', array() );
52 - static $tags = array(
53 - '-' => array( 'del', array() ),
54 - '+' => array( 'ins', array() ),
55 - '@' => array( 'span', array( 'class' => 'meta' ) ),
56 - ' ' => array( 'span', array() ),
57 - );
58 - $first = substr( $line, 0, 1 );
59 - if ( isset( $tags[$first] ) )
60 - return $tags[$first];
61 - else
62 - return $default;
63 - }
64 -
65 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionStatusView.php
@@ -1,30 +0,0 @@
2 -<?php
3 -
4 -class CodeRevisionStatusView extends CodeRevisionListView {
5 - function __construct( $repoName, $status ) {
6 - parent::__construct( $repoName );
7 - $this->mStatus = $status;
8 - }
9 -
10 - function getPager() {
11 - return new SvnRevStatusTablePager( $this, $this->mStatus );
12 - }
13 -}
14 -
15 -class SvnRevStatusTablePager extends SvnRevTablePager {
16 - function __construct( $view, $status ) {
17 - parent::__construct( $view );
18 - $this->mStatus = $status;
19 - }
20 -
21 - function getQueryInfo() {
22 - $info = parent::getQueryInfo();
23 - $info['conds']['cr_status'] = $this->mStatus; // FIXME: normalize input?
24 - return $info;
25 - }
26 -
27 - function getTitle() {
28 - $repo = $this->mRepo->getName();
29 - return SpecialPage::getTitleFor( 'Code', "$repo/status/$this->mStatus" );
30 - }
31 -}
Index: branches/wmf-deployment/extensions/CodeReview/ApiCodeDiff.php
@@ -1,80 +0,0 @@
2 -<?php
3 -
4 -class ApiCodeDiff extends ApiBase {
5 -
6 - public function execute() {
7 - global $wgUser;
8 - // Before doing anything at all, let's check permissions
9 - if( !$wgUser->isAllowed('codereview-use') ) {
10 - $this->dieUsage('You don\'t have permission to view code diffs','permissiondenied');
11 - }
12 - $params = $this->extractRequestParams();
13 -
14 - if ( !isset( $params['repo'] ) ) {
15 - $this->dieUsageMsg( array( 'missingparam', 'repo' ) );
16 - }
17 - if ( !isset( $params['rev'] ) ) {
18 - $this->dieUsageMsg( array( 'missingparam', 'rev' ) );
19 - }
20 -
21 - $repo = CodeRepository::newFromName( $params['repo'] );
22 - if ( !$repo ) {
23 - $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
24 - }
25 -
26 - $svn = SubversionAdaptor::newFromRepo( $repo->getPath() );
27 - $lastStoredRev = $repo->getLastStoredRev();
28 -
29 - if ( $params['rev'] > $lastStoredRev ) {
30 - $this->dieUsage( "There is no revision with ID {$params['rev']}", 'nosuchrev' );
31 - }
32 -
33 - $diff = $repo->getDiff( $params['rev'] );
34 -
35 - if ( $diff ) {
36 - $hilite = new CodeDiffHighlighter();
37 - $html = $hilite->render( $diff );
38 - } else {
39 - // FIXME: Are we sure we don't want to throw an error here?
40 - $html = 'Failed to load diff.';
41 - }
42 -
43 - $data = array(
44 - 'repo' => $params['repo'],
45 - 'id' => $params['rev'],
46 - 'diff' => $html
47 - );
48 - $this->getResult()->addValue( 'code', 'rev', $data );
49 - }
50 -
51 - public function getAllowedParams() {
52 - return array(
53 - 'repo' => null,
54 - 'rev' => array(
55 - ApiBase::PARAM_TYPE => 'integer',
56 - ApiBase::PARAM_MIN => 1
57 - )
58 - );
59 - }
60 -
61 - public function getParamDescription() {
62 - return array(
63 - 'repo' => 'Name of repository to look at',
64 - 'rev' => 'Revision ID to fetch diff of' );
65 - }
66 -
67 - public function getDescription() {
68 - return array(
69 - 'Fetch formatted diff from CodeReview\'s backing revision control system.' );
70 - }
71 -
72 - public function getExamples() {
73 - return array(
74 - 'api.php?action=codediff&repo=MediaWiki&rev=42080',
75 - );
76 - }
77 -
78 - public function getVersion() {
79 - return __CLASS__ . ': $Id$';
80 - }
81 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeTagListView.php
@@ -1,20 +0,0 @@
2 -<?php
3 -
4 -// Special:Code/MediaWiki/tag
5 -class CodeTagListView extends CodeView {
6 - function __construct( $repoName ) {
7 - parent::__construct();
8 - $this->mRepo = CodeRepository::newFromName( $repoName );
9 - }
10 -
11 - function execute() {
12 - global $wgOut;
13 - $tags = $this->mRepo->getTagList();
14 - $name = $this->mRepo->getName();
15 - $text = '';
16 - foreach ( $tags as $tag ) {
17 - $text .= "* [[Special:Code/$name/tag/$tag|$tag]]\n";
18 - }
19 - $wgOut->addWikiText( $text );
20 - }
21 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionAuthorView.php
@@ -1,65 +0,0 @@
2 -<?php
3 -
4 -class CodeRevisionAuthorView extends CodeRevisionListView {
5 - function __construct( $repoName, $author ) {
6 - parent::__construct( $repoName );
7 - $this->mAuthor = $author;
8 - $this->mUser = $this->authorWikiUser( $author );
9 - }
10 -
11 - function getPager() {
12 - return new SvnRevAuthorTablePager( $this, $this->mAuthor );
13 - }
14 -
15 - function linkStatus() {
16 - if ( !$this->mUser )
17 - return wfMsg( 'code-author-orphan' );
18 -
19 - return wfMsgHtml( 'code-author-haslink',
20 - $this->mSkin->userLink( $this->mUser->getId(), $this->mUser->getName() ) .
21 - $this->mSkin->userToolLinks( $this->mUser->getId(), $this->mUser->getName() ) );
22 - }
23 -
24 - function execute() {
25 - global $wgOut, $wgUser;
26 -
27 - $linkInfo = $this->linkStatus();
28 -
29 - if ( $wgUser->isAllowed( 'codereview-link-user' ) ) {
30 - $repo = $this->mRepo->getName();
31 - $page = SpecialPage::getTitleFor( 'Code', "$repo/author/$this->mAuthor/link" );
32 - $linkInfo .= ' (' . $this->mSkin->link( $page,
33 - wfMsg( 'code-author-' . ( $this->mUser ? 'un':'' ) . 'link' ) ) . ')' ;
34 - }
35 -
36 - $repoLink = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ),
37 - htmlspecialchars( $this->mRepo->getName() ) );
38 - $fields = array(
39 - 'code-rev-repo' => $repoLink,
40 - 'code-rev-author' => $this->authorLink( $this->mAuthor ),
41 - );
42 -
43 - $wgOut->addHTML( $this->formatMetaData( $fields ) . $linkInfo );
44 -
45 - parent::execute();
46 - }
47 -
48 -}
49 -
50 -class SvnRevAuthorTablePager extends SvnRevTablePager {
51 - function __construct( $view, $author ) {
52 - parent::__construct( $view );
53 - $this->mAuthor = $author;
54 - }
55 -
56 - function getQueryInfo() {
57 - $info = parent::getQueryInfo();
58 - $info['conds']['cr_author'] = $this->mAuthor; // fixme: normalize input?
59 - return $info;
60 - }
61 -
62 - function getTitle() {
63 - $repo = $this->mRepo->getName();
64 - return SpecialPage::getTitleFor( 'Code', "$repo/author/$this->mAuthor" );
65 - }
66 -}
Index: branches/wmf-deployment/extensions/CodeReview/ApiCodeUpdate.php
@@ -1,104 +0,0 @@
2 -<?php
3 -
4 -class ApiCodeUpdate extends ApiBase {
5 -
6 - public function execute() {
7 - global $wgUser;
8 - // Before doing anything at all, let's check permissions
9 - if( !$wgUser->isAllowed('codereview-use') ) {
10 - $this->dieUsage('You don\'t have permission update code','permissiondenied');
11 - }
12 - $params = $this->extractRequestParams();
13 -
14 - if ( !isset( $params['repo'] ) ) {
15 - $this->dieUsageMsg( array( 'missingparam', 'repo' ) );
16 - }
17 - if ( !isset( $params['rev'] ) ) {
18 - $this->dieUsageMsg( array( 'missingparam', 'rev' ) );
19 - }
20 -
21 - $repo = CodeRepository::newFromName( $params['repo'] );
22 - if ( !$repo ) {
23 - $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
24 - }
25 -
26 - $svn = SubversionAdaptor::newFromRepo( $repo->getPath() );
27 - $lastStoredRev = $repo->getLastStoredRev();
28 -
29 - if ( $lastStoredRev >= $params['rev'] ) {
30 - // Nothing to do, we're up to date.
31 - // Return an empty result
32 - $this->getResult()->addValue( null, $this->getModuleName(), array() );
33 - return;
34 - }
35 -
36 - // FIXME: this could be a lot?
37 - $log = $svn->getLog( '', $lastStoredRev + 1, $params['rev'] );
38 - if ( !$log ) {
39 - // FIXME: When and how often does this happen?
40 - // Should we use dieUsage() here instead?
41 - ApiBase::dieDebug( __METHOD__, "Something awry..." );
42 - }
43 -
44 - $result = array();
45 - foreach ( $log as $data ) {
46 - $codeRev = CodeRevision::newFromSvn( $repo, $data );
47 - $codeRev->save();
48 - $result[] = array(
49 - 'id' => $codeRev->getId(),
50 - 'author' => $codeRev->getAuthor(),
51 - 'timestamp' => wfTimestamp( TS_ISO_8601, $codeRev->getTimestamp() ),
52 - 'message' => $codeRev->getMessage()
53 - );
54 - }
55 - // Cache the diffs if there are a only a few.
56 - // Mainly for WMF post-commit ping hook...
57 - if ( count( $result ) <= 2 ) {
58 - foreach ( $result as $revData ) {
59 - $diff = $repo->getDiff( $revData['id'] ); // trigger caching
60 - }
61 - }
62 - $this->getResult()->setIndexedTagName( $result, 'rev' );
63 - $this->getResult()->addValue( null, $this->getModuleName(), $result );
64 - }
65 -
66 - public function mustBePosted() {
67 - // Discourage casual browsing :)
68 - return true;
69 - }
70 -
71 - public function isWriteMode() {
72 - return true;
73 - }
74 -
75 - public function getAllowedParams() {
76 - return array(
77 - 'repo' => null,
78 - 'rev' => array(
79 - ApiBase::PARAM_TYPE => 'integer',
80 - ApiBase::PARAM_MIN => 1
81 - )
82 - );
83 - }
84 -
85 - public function getParamDescription() {
86 - return array(
87 - 'repo' => 'Name of repository to update',
88 - 'rev' => 'Revision ID number to update to' );
89 - }
90 -
91 - public function getDescription() {
92 - return array(
93 - 'Update CodeReview repository data from master revision control system.' );
94 - }
95 -
96 - public function getExamples() {
97 - return array(
98 - 'api.php?action=codeupdate&repo=MediaWiki&rev=42080',
99 - );
100 - }
101 -
102 - public function getVersion() {
103 - return __CLASS__ . ': $Id$';
104 - }
105 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionCommitter.php
@@ -1,76 +0,0 @@
2 -<?php
3 -
4 -class CodeRevisionCommitter extends CodeRevisionView {
5 -
6 - function __construct( $repoName, $rev ) {
7 - // Parent should set $this->mRepo, $this->mRev, $this->mReplyTarget
8 - parent::__construct( $repoName, $rev );
9 - }
10 -
11 - function execute() {
12 - global $wgRequest, $wgOut, $wgUser;
13 -
14 - if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
15 - $wgOut->addHTML( '<strong>' . wfMsg( 'sessionfailure' ) . '</strong>' );
16 - parent::execute();
17 - return;
18 - }
19 - if ( !$this->mRev ) {
20 - parent::execute();
21 - return;
22 - }
23 -
24 - $redirTarget = null;
25 - $dbw = wfGetDB( DB_MASTER );
26 -
27 - $dbw->begin();
28 - // Change the status if allowed
29 - if ( $this->validPost( 'codereview-set-status' ) && $this->mRev->isValidStatus( $this->mStatus ) ) {
30 - $this->mRev->setStatus( $this->mStatus, $wgUser );
31 - }
32 - $addTags = $removeTags = array();
33 - if ( $this->validPost( 'codereview-add-tag' ) && count( $this->mAddTags ) ) {
34 - $addTags = $this->mAddTags;
35 - }
36 - if ( $this->validPost( 'codereview-remove-tag' ) && count( $this->mRemoveTags ) ) {
37 - $removeTags = $this->mRemoveTags;
38 - }
39 - // If allowed to change any tags, then do so
40 - if ( count( $addTags ) || count( $removeTags ) ) {
41 - $this->mRev->changeTags( $addTags, $removeTags, $wgUser );
42 - }
43 - // Add any comments
44 - if ( $this->validPost( 'codereview-post-comment' ) && strlen( $this->text ) ) {
45 - $parent = $wgRequest->getIntOrNull( 'wpParent' );
46 - $review = $wgRequest->getInt( 'wpReview' );
47 - $isPreview = $wgRequest->getCheck( 'wpPreview' );
48 - $id = $this->mRev->saveComment( $this->text, $review, $parent );
49 - // For comments, take us back to the rev page focused on the new comment
50 - if ( !$this->jumpToNext ) {
51 - $redirTarget = $this->commentLink( $id );
52 - }
53 - }
54 - $dbw->commit();
55 -
56 - // Return to rev page
57 - if ( !$redirTitle ) {
58 - if ( $this->jumpToNext ) {
59 - if ( $next = $this->mRev->getNextUnresolved() ) {
60 - $redirTitle = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $next );
61 - } else {
62 - $redirTitle = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() );
63 - }
64 - } else {
65 - # $redirTarget already set for comments
66 - $redirTitle = $redirTarget ? $redirTarget : $this->revLink();
67 - }
68 - }
69 - $wgOut->redirect( $redirTitle->getFullUrl() );
70 - }
71 -
72 - public function validPost( $permission ) {
73 - global $wgUser, $wgRequest;
74 - return parent::validPost( $permission ) && $wgRequest->wasPosted()
75 - && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
76 - }
77 -}
Index: branches/wmf-deployment/extensions/CodeReview/CodeRevisionListView.php
@@ -1,300 +0,0 @@
2 -<?php
3 -
4 -// Special:Code/MediaWiki
5 -class CodeRevisionListView extends CodeView {
6 - public $mRepo, $mPath;
7 - function __construct( $repoName ) {
8 - global $wgRequest;
9 - parent::__construct();
10 - $this->mRepo = CodeRepository::newFromName( $repoName );
11 - $this->mPath = htmlspecialchars( trim( $wgRequest->getVal( 'path' ) ) );
12 - if ( strlen( $this->mPath ) && $this->mPath[0] !== '/' ) {
13 - $this->mPath = "/{$this->mPath}"; // make sure this is a valid path
14 - }
15 - $this->mAuthor = null;
16 - }
17 -
18 - function execute() {
19 - global $wgOut, $wgUser, $wgRequest;
20 - if ( !$this->mRepo ) {
21 - $view = new CodeRepoListView();
22 - $view->execute();
23 - return;
24 - }
25 -
26 - // Check for batch change requests.
27 - $editToken = $wgRequest->getVal( 'wpBatchChangeEditToken' );
28 - if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $editToken ) ) {
29 - $this->doBatchChange();
30 - return;
31 - }
32 -
33 - $this->showForm();
34 - $pager = $this->getPager();
35 -
36 - // Build batch change interface as needed
37 - $this->batchForm = $wgUser->isAllowed( 'codereview-set-status' ) ||
38 - $wgUser->isAllowed( 'codereview-add-tag' );
39 -
40 - $wgOut->addHTML(
41 - $pager->getNavigationBar() .
42 - $pager->getLimitForm() .
43 - Xml::openElement( 'form',
44 - array( 'action' => $pager->getTitle()->getLocalURL(), 'method' => 'post' )
45 - ) .
46 - $pager->getBody() .
47 - $pager->getNavigationBar() .
48 - ( $this->batchForm ? $this->buildBatchInterface( $pager ) : "" ) .
49 - Xml::closeElement( 'form' )
50 - );
51 - }
52 -
53 - function doBatchChange() {
54 - global $wgRequest;
55 -
56 - $revisions = $wgRequest->getArray( 'wpRevisionSelected' );
57 - $removeTags = $wgRequest->getVal( 'wpRemoveTag' );
58 - $addTags = $wgRequest->getVal( 'wpTag' );
59 - $status = $wgRequest->getVal( 'wpStatus' );
60 -
61 - // Grab data from the DB
62 - $dbr = wfGetDB( DB_SLAVE );
63 - $revObjects = array();
64 - $res = $dbr->select( 'code_rev', '*', array( 'cr_id' => $revisions ), __METHOD__ );
65 - while ( $row = $dbr->fetchObject( $res ) ) {
66 - $revObjects[] = CodeRevision::newFromRow( $this->mRepo, $row );
67 - }
68 -
69 - global $wgUser;
70 - if ( $wgUser->isAllowed( 'codereview-add-tag' ) &&
71 - $addTags || $removeTags ) {
72 - $addTags = array_map( 'trim', explode( ",", $addTags ) );
73 - $removeTags = array_map( 'trim', explode( ",", $removeTags ) );
74 -
75 - foreach ( $revObjects as $id => $rev ) {
76 - $rev->changeTags( $addTags, $removeTags, $wgUser );
77 - }
78 - }
79 -
80 - if ( $wgUser->isAllowed( 'codereview-set-status' ) &&
81 - $revObjects[0]->isValidStatus( $status ) ) {
82 - foreach ( $revObjects as $id => $rev ) {
83 - $rev->setStatus( $status, $wgUser );
84 - }
85 - }
86 -
87 - // Automatically refresh
88 - // This way of getting GET parameters is horrible, but effective.
89 - $fields = array_merge( $_GET, $_POST );
90 - foreach ( array_keys( $fields ) as $key ) {
91 - if ( substr( $key, 0, 2 ) == 'wp' || $key == 'title' )
92 - unset( $fields[$key] );
93 - }
94 -
95 - global $wgOut;
96 - $wgOut->redirect( $this->getPager()->getTitle()->getFullURL( $fields ) );
97 - }
98 -
99 - protected function buildBatchInterface( $pager ) {
100 - global $wgUser;
101 -
102 - $changeInterface = '';
103 - $changeFields = array();
104 -
105 - if ( $wgUser->isAllowed( 'codereview-set-status' ) ) {
106 - $changeFields['code-batch-status'] =
107 - Xml::tags( 'select', array( 'name' => 'wpStatus' ),
108 - Xml::tags( 'option',
109 - array( 'value' => '', 'selected' => 'selected' ), ' '
110 - ) .
111 - CodeRevisionView::buildStatusList( null, $this )
112 - );
113 - }
114 -
115 - if ( $wgUser->isAllowed( 'codereview-add-tag' ) ) {
116 - $changeFields['code-batch-tags'] = CodeRevisionView::addTagForm( '', '' );
117 - }
118 -
119 - if ( !count( $changeFields ) ) return ''; // nothing to do here
120 -
121 - $changeInterface = Xml::fieldset( wfMsg( 'codereview-batch-title' ),
122 - Xml::buildForm( $changeFields, 'codereview-batch-submit' ) );
123 -
124 - $changeInterface .= $pager->getHiddenFields();
125 - $changeInterface .= Xml::hidden( 'wpBatchChangeEditToken', $wgUser->editToken() );
126 -
127 - return $changeInterface;
128 - }
129 -
130 - function showForm( $path = '' ) {
131 - global $wgOut, $wgScript;
132 - if ( $this->mAuthor ) {
133 - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/author/' . $this->mAuthor );
134 - } else {
135 - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/path' );
136 - }
137 - $wgOut->addHTML(
138 - Xml::openElement( 'form', array( 'action' => $wgScript, 'method' => 'get' ) ) .
139 - "<fieldset><legend>" . wfMsgHtml( 'code-pathsearch-legend' ) . "</legend>" .
140 - Xml::hidden( 'title', $special->getPrefixedDBKey() ) .
141 - Xml::inputlabel( wfMsg( "code-pathsearch-path" ), 'path', 'path', 55, $this->mPath ) .
142 - '&nbsp;' . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
143 - "</fieldset>" . Xml::closeElement( 'form' )
144 - );
145 - }
146 -
147 - function getPager() {
148 - return new SvnRevTablePager( $this );
149 - }
150 -}
151 -
152 -// Pager for CodeRevisionListView
153 -class SvnRevTablePager extends TablePager {
154 -
155 - function __construct( $view ) {
156 - global $IP;
157 - $this->mView = $view;
158 - $this->mRepo = $view->mRepo;
159 - $this->mDefaultDirection = true;
160 - $this->mCurSVN = SpecialVersion::getSvnRevision( $IP );
161 - parent::__construct();
162 - }
163 -
164 - function getSVNPath() {
165 - return $this->mView->mPath;
166 - }
167 -
168 - function isFieldSortable( $field ) {
169 - return $field == $this->getDefaultSort();
170 - }
171 -
172 - function getDefaultSort() {
173 - return strlen( $this->mView->mPath ) ? 'cp_rev_id' : 'cr_id';
174 - }
175 -
176 - function getQueryInfo() {
177 - // Path-based query...
178 - if ( $this->getDefaultSort() === 'cp_rev_id' ) {
179 - return array(
180 - 'tables' => array( 'code_paths', 'code_rev', 'code_comment' ),
181 - 'fields' => $this->getSelectFields(),
182 - 'conds' => array(
183 - 'cp_repo_id' => $this->mRepo->getId(),
184 - 'cp_path LIKE ' . $this->mDb->addQuotes( $this->mDb->escapeLike( $this->getSVNPath() ) . '%' ),
185 - // performance
186 - 'cp_rev_id > ' . $this->mRepo->getLastStoredRev() - 20000
187 - ),
188 - 'options' => array( 'GROUP BY' => 'cp_rev_id', 'USE INDEX' => array( 'code_path' => 'cp_repo_id' ) ),
189 - 'join_conds' => array(
190 - 'code_rev' => array( 'INNER JOIN', 'cr_repo_id = cp_repo_id AND cr_id = cp_rev_id' ),
191 - 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cp_repo_id AND cc_rev_id = cp_rev_id' )
192 - )
193 - );
194 - // No path; entire repo...
195 - } else {
196 - return array(
197 - 'tables' => array( 'code_rev', 'code_comment' ),
198 - 'fields' => $this->getSelectFields(),
199 - 'conds' => array( 'cr_repo_id' => $this->mRepo->getId() ),
200 - 'options' => array( 'GROUP BY' => 'cr_id' ),
201 - 'join_conds' => array(
202 - 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' )
203 - )
204 - );
205 - }
206 - return false;
207 - }
208 -
209 - function getSelectFields() {
210 - return array( $this->getDefaultSort(), 'cr_status', 'COUNT( DISTINCT cc_id ) AS comments',
211 - 'cr_path', 'cr_message', 'cr_author', 'cr_timestamp' );
212 - }
213 -
214 - function getFieldNames() {
215 - $fields = array(
216 - $this->getDefaultSort() => wfMsg( 'code-field-id' ),
217 - 'cr_status' => wfMsg( 'code-field-status' ),
218 - 'comments' => wfMsg( 'code-field-comments' ),
219 - 'cr_path' => wfMsg( 'code-field-path' ),
220 - 'cr_message' => wfMsg( 'code-field-message' ),
221 - 'cr_author' => wfMsg( 'code-field-author' ),
222 - 'cr_timestamp' => wfMsg( 'code-field-timestamp' ),
223 - );
224 - # Only show checkboxen as needed
225 - if ( !empty( $this->mView->batchForm ) ) {
226 - $fields = array( 'selectforchange' => wfMsg( 'code-field-select' ) ) + $fields;
227 - }
228 - return $fields;
229 - }
230 -
231 - function formatValue( $name, $value ) { } // unused
232 -
233 - function formatRevValue( $name, $value, $row ) {
234 - global $wgUser, $wgLang;
235 - switch( $name ) {
236 - case 'selectforchange':
237 - $sort = $this->getDefaultSort();
238 - return Xml::check( "wpRevisionSelected[]", false, array( 'value' => $row->$sort ) );
239 - case 'cp_rev_id':
240 - case 'cr_id':
241 - return $this->mView->mSkin->link(
242 - SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value ),
243 - htmlspecialchars( $value ) );
244 - case 'cr_status':
245 - return $this->mView->mSkin->link(
246 - SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/status/' . $value ),
247 - htmlspecialchars( $this->mView->statusDesc( $value ) )
248 - );
249 - case 'cr_author':
250 - return $this->mView->authorLink( $value );
251 - case 'cr_message':
252 - return $this->mView->messageFragment( $value );
253 - case 'cr_timestamp':
254 - global $wgLang;
255 - return $wgLang->timeanddate( $value, true );
256 - case 'comments':
257 - if ( $value ) {
258 - $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $row-> { $this->getDefaultSort() } );
259 - $special->setFragment( '#code-comments' );
260 - return $this->mView->mSkin->link( $special, htmlspecialchars( $value ) );
261 - } else {
262 - return intval( $value );
263 - }
264 - case 'cr_path':
265 - return Xml::openElement( 'div', array( 'title' => (string)$value ) ) .
266 - $this->mView->mSkin->link(
267 - SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/path' ),
268 - $wgLang->truncate( (string)$value, 30 ),
269 - array(),
270 - array( 'path' => (string)$value ) ) . "</div>";
271 - }
272 - }
273 -
274 - // Note: this function is poorly factored in the parent class
275 - function formatRow( $row ) {
276 - global $wgWikiSVN;
277 - $css = "mw-codereview-status-{$row->cr_status}";
278 - if ( $this->mRepo->mName == $wgWikiSVN ) {
279 - $css .= " mw-codereview-" . ( $row-> { $this->getDefaultSort() } <= $this->mCurSVN ? 'live' : 'notlive' );
280 - }
281 - $s = "<tr class=\"$css\">\n";
282 - // Some of this stolen from Pager.php...sigh
283 - $fieldNames = $this->getFieldNames();
284 - $this->mCurrentRow = $row; # In case formatValue needs to know
285 - foreach ( $fieldNames as $field => $name ) {
286 - $value = isset( $row->$field ) ? $row->$field : null;
287 - $formatted = strval( $this->formatRevValue( $field, $value, $row ) );
288 - if ( $formatted == '' ) {
289 - $formatted = '&nbsp;';
290 - }
291 - $class = 'TablePager_col_' . htmlspecialchars( $field );
292 - $s .= "<td class=\"$class\">$formatted</td>\n";
293 - }
294 - $s .= "</tr>\n";
295 - return $s;
296 - }
297 -
298 - function getTitle() {
299 - return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() );
300 - }
301 -}
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionAuthorLink.php
@@ -0,0 +1,89 @@
 2+<?php
 3+
 4+// Special:Code/MediaWiki/author/johndoe/link
 5+
 6+class CodeRevisionAuthorLink extends CodeRevisionAuthorView {
 7+ function __construct( $repoName, $author ) {
 8+ global $wgRequest;
 9+ parent::__construct( $repoName, $author );
 10+ $this->mTarget = $wgRequest->getVal( 'linktouser' );
 11+ }
 12+
 13+ function getTitle() {
 14+ $repo = $this->mRepo->getName();
 15+ $auth = $this->mAuthor;
 16+ return SpecialPage::getTitleFor( 'Code', "$repo/author/$auth/link" );
 17+ }
 18+
 19+ function execute() {
 20+ global $wgOut, $wgRequest, $wgUser;
 21+ if ( !$wgUser->isAllowed( 'codereview-link-user' ) ) {
 22+ $wgOut->permissionRequired( 'codereview-link-user' );
 23+ return;
 24+ }
 25+ if ( $wgRequest->wasPosted() ) {
 26+ $this->doSubmit();
 27+ } else {
 28+ $this->doForm();
 29+ }
 30+ }
 31+
 32+ function doForm() {
 33+ global $wgOut;
 34+ $form = Xml::openElement( 'form', array( 'method' => 'post',
 35+ 'action' => $this->getTitle()->getLocalUrl(),
 36+ 'name' => 'uluser', 'id' => 'mw-codeauthor-form1' ) );
 37+ $form .= Xml::openElement( 'fieldset' );
 38+
 39+ $additional = '';
 40+ // Is there already a user linked to this author?
 41+ if ( $this->mUser ) {
 42+ $form .= Xml::element( 'legend', array(), wfMsg( 'code-author-alterlink' ) );
 43+ $additional = Xml::openElement( 'fieldset' ) .
 44+ Xml::element( 'legend', array(), wfMsg( 'code-author-orunlink' ) ) .
 45+ Xml::submitButton( wfMsg( 'code-author-unlink' ), array( 'name' => 'unlink' ) ) .
 46+ Xml::closeElement( 'fieldset' );
 47+ } else {
 48+ $form .= Xml::element( 'legend', array(), wfMsg( 'code-author-dolink' ) );
 49+ }
 50+
 51+ $form .= Xml::inputLabel( wfMsg( 'code-author-name' ), 'linktouser', 'username', 30, '' ) . ' ' .
 52+ Xml::submitButton( wfMsg( 'ok' ), array( 'name' => 'newname' ) ) .
 53+ Xml::closeElement( 'fieldset' ) .
 54+ $additional .
 55+ Xml::closeElement( 'form' ) . "\n";
 56+
 57+ $wgOut->addHTML( $this->linkStatus() . $form );
 58+ }
 59+
 60+ function doSubmit() {
 61+ global $wgOut, $wgRequest;
 62+ // Link an author to a wiki user
 63+ if ( strlen( $this->mTarget ) && $wgRequest->getCheck( 'newname' ) ) {
 64+ $user = User::newFromName( $this->mTarget, false );
 65+ if ( !$user || !$user->getId() ) {
 66+ $wgOut->addWikiMsg( 'nosuchusershort', $this->mTarget );
 67+ return;
 68+ }
 69+ $this->mRepo->linkUser( $this->mAuthor, $user );
 70+ $userlink = $this->mSkin->userLink( $user->getId(), $user->getName() );
 71+ $wgOut->addHTML(
 72+ '<div class="successbox">' .
 73+ wfMsgHtml( 'code-author-success', $this->authorLink( $this->mAuthor ), $userlink ) .
 74+ '</div>'
 75+ );
 76+ // Unlink an author to a wiki users
 77+ } else if ( $wgRequest->getVal( 'unlink' ) ) {
 78+ if ( !$this->mUser ) {
 79+ $wgOut->addHTML( wfMsg( 'code-author-orphan' ) );
 80+ return;
 81+ }
 82+ $this->mRepo->unlinkUser( $this->mAuthor );
 83+ $wgOut->addHTML(
 84+ '<div class="successbox">' .
 85+ wfMsgHtml( 'code-author-unlinksuccess', $this->authorLink( $this->mAuthor ) ) .
 86+ '</div>'
 87+ );
 88+ }
 89+ }
 90+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionAuthorLink.php
___________________________________________________________________
Added: svn:eol-style
191 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeTagListView.php
@@ -0,0 +1,20 @@
 2+<?php
 3+
 4+// Special:Code/MediaWiki/tag
 5+class CodeTagListView extends CodeView {
 6+ function __construct( $repoName ) {
 7+ parent::__construct();
 8+ $this->mRepo = CodeRepository::newFromName( $repoName );
 9+ }
 10+
 11+ function execute() {
 12+ global $wgOut;
 13+ $tags = $this->mRepo->getTagList();
 14+ $name = $this->mRepo->getName();
 15+ $text = '';
 16+ foreach ( $tags as $tag ) {
 17+ $text .= "* [[Special:Code/$name/tag/$tag|$tag]]\n";
 18+ }
 19+ $wgOut->addWikiText( $text );
 20+ }
 21+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeTagListView.php
___________________________________________________________________
Added: svn:eol-style
122 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeStatusListView.php
@@ -0,0 +1,20 @@
 2+<?php
 3+
 4+// Special:Code/MediaWiki/status
 5+class CodeStatusListView extends CodeView {
 6+ function __construct( $repoName ) {
 7+ parent::__construct();
 8+ $this->mRepo = CodeRepository::newFromName( $repoName );
 9+ }
 10+
 11+ function execute() {
 12+ global $wgOut;
 13+ $name = $this->mRepo->getName();
 14+ $states = CodeRevision::getPossibleStates();
 15+ $text = "== " . wfMsg ( "code-field-status" ) . " ==\n";
 16+ foreach ( $states as $state ) {
 17+ $text .= "* [[Special:Code/$name/status/$state|" . wfMsg ( "code-status-" . $state ) . "]]\n";
 18+ }
 19+ $wgOut->addWikiText( $text );
 20+ }
 21+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeStatusListView.php
___________________________________________________________________
Added: svn:eol-style
122 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeCommentsListView.php
@@ -0,0 +1,113 @@
 2+<?php
 3+
 4+// Special:Code/MediaWiki
 5+class CodeCommentsListView extends CodeView {
 6+ public $mRepo;
 7+
 8+ function __construct( $repoName ) {
 9+ parent::__construct();
 10+ $this->mRepo = CodeRepository::newFromName( $repoName );
 11+ }
 12+
 13+ function execute() {
 14+ global $wgOut;
 15+ $pager = $this->getPager();
 16+ $wgOut->addHTML(
 17+ $pager->getNavigationBar() .
 18+ $pager->getLimitForm() .
 19+ $pager->getBody() .
 20+ $pager->getNavigationBar()
 21+ );
 22+ }
 23+
 24+ function getPager() {
 25+ return new CodeCommentsTablePager( $this );
 26+ }
 27+ function getRepo() {
 28+ return $this->mRepo;
 29+ }
 30+}
 31+
 32+// Pager for CodeRevisionListView
 33+class CodeCommentsTablePager extends TablePager {
 34+
 35+ function __construct( CodeCommentsListView $view ) {
 36+ global $IP;
 37+ $this->mView = $view;
 38+ $this->mRepo = $view->mRepo;
 39+ $this->mDefaultDirection = true;
 40+ $this->mCurSVN = SpecialVersion::getSvnRevision( $IP );
 41+ parent::__construct();
 42+ }
 43+
 44+ function isFieldSortable( $field ) {
 45+ return $field == 'cr_timestamp';
 46+ }
 47+
 48+ function getDefaultSort() { return 'cc_timestamp'; }
 49+
 50+ function getQueryInfo() {
 51+ return array(
 52+ 'tables' => array( 'code_comment', 'code_rev' ),
 53+ 'fields' => array_keys( $this->getFieldNames() ),
 54+ 'conds' => array( 'cc_repo_id' => $this->mRepo->getId() ),
 55+ 'join_conds' => array(
 56+ 'code_rev' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' )
 57+ )
 58+ );
 59+ }
 60+
 61+ function getFieldNames() {
 62+ return array(
 63+ 'cc_timestamp' => wfMsg( 'code-field-timestamp' ),
 64+ 'cc_user_text' => wfMsg( 'code-field-user' ),
 65+ 'cc_rev_id' => wfMsg( 'code-field-id' ),
 66+ 'cr_status' => wfMsg( 'code-field-status' ),
 67+ 'cr_message' => wfMsg( 'code-field-message' ),
 68+ 'cc_text' => wfMsg( 'code-field-text' ),
 69+ );
 70+ }
 71+
 72+ function formatValue( $name, $value ) {
 73+ global $wgUser, $wgLang;
 74+ switch( $name ) {
 75+ case 'cc_rev_id':
 76+ return $this->mView->mSkin->link(
 77+ SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value . '#code-comments' ),
 78+ htmlspecialchars( $value ) );
 79+ case 'cr_status':
 80+ return $this->mView->mSkin->link(
 81+ SpecialPage::getTitleFor( 'Code',
 82+ $this->mRepo->getName() . '/status/' . $value ),
 83+ htmlspecialchars( $this->mView->statusDesc( $value ) ) );
 84+ case 'cc_user_text':
 85+ return $this->mView->mSkin->userLink( -1, $value );
 86+ case 'cr_message':
 87+ return $this->mView->messageFragment( $value );
 88+ case 'cc_text':
 89+ # Truncate this, blah blah...
 90+ return htmlspecialchars( $wgLang->truncate( $value, 300 ) );
 91+ case 'cc_timestamp':
 92+ global $wgLang;
 93+ return $wgLang->timeanddate( $value, true );
 94+ }
 95+ }
 96+
 97+ // Note: this function is poorly factored in the parent class
 98+ function formatRow( $row ) {
 99+ global $wgWikiSVN;
 100+ $class = "mw-codereview-status-{$row->cr_status}";
 101+ if ( $this->mRepo->mName == $wgWikiSVN ) {
 102+ $class .= " mw-codereview-" . ( $row->cc_rev_id <= $this->mCurSVN ? 'live' : 'notlive' );
 103+ }
 104+ return str_replace(
 105+ '<tr>',
 106+ Xml::openElement( 'tr',
 107+ array( 'class' => $class ) ),
 108+ parent::formatRow( $row ) );
 109+ }
 110+
 111+ function getTitle() {
 112+ return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/comments' );
 113+ }
 114+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeCommentsListView.php
___________________________________________________________________
Added: svn:eol-style
1115 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionAuthorView.php
@@ -0,0 +1,65 @@
 2+<?php
 3+
 4+class CodeRevisionAuthorView extends CodeRevisionListView {
 5+ function __construct( $repoName, $author ) {
 6+ parent::__construct( $repoName );
 7+ $this->mAuthor = $author;
 8+ $this->mUser = $this->authorWikiUser( $author );
 9+ }
 10+
 11+ function getPager() {
 12+ return new SvnRevAuthorTablePager( $this, $this->mAuthor );
 13+ }
 14+
 15+ function linkStatus() {
 16+ if ( !$this->mUser )
 17+ return wfMsg( 'code-author-orphan' );
 18+
 19+ return wfMsgHtml( 'code-author-haslink',
 20+ $this->mSkin->userLink( $this->mUser->getId(), $this->mUser->getName() ) .
 21+ $this->mSkin->userToolLinks( $this->mUser->getId(), $this->mUser->getName() ) );
 22+ }
 23+
 24+ function execute() {
 25+ global $wgOut, $wgUser;
 26+
 27+ $linkInfo = $this->linkStatus();
 28+
 29+ if ( $wgUser->isAllowed( 'codereview-link-user' ) ) {
 30+ $repo = $this->mRepo->getName();
 31+ $page = SpecialPage::getTitleFor( 'Code', "$repo/author/$this->mAuthor/link" );
 32+ $linkInfo .= ' (' . $this->mSkin->link( $page,
 33+ wfMsg( 'code-author-' . ( $this->mUser ? 'un':'' ) . 'link' ) ) . ')' ;
 34+ }
 35+
 36+ $repoLink = $wgUser->getSkin()->link( SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ),
 37+ htmlspecialchars( $this->mRepo->getName() ) );
 38+ $fields = array(
 39+ 'code-rev-repo' => $repoLink,
 40+ 'code-rev-author' => $this->authorLink( $this->mAuthor ),
 41+ );
 42+
 43+ $wgOut->addHTML( $this->formatMetaData( $fields ) . $linkInfo );
 44+
 45+ parent::execute();
 46+ }
 47+
 48+}
 49+
 50+class SvnRevAuthorTablePager extends SvnRevTablePager {
 51+ function __construct( $view, $author ) {
 52+ parent::__construct( $view );
 53+ $this->mAuthor = $author;
 54+ }
 55+
 56+ function getQueryInfo() {
 57+ $info = parent::getQueryInfo();
 58+ $info['conds']['cr_author'] = $this->mAuthor; // fixme: normalize input?
 59+ return $info;
 60+ }
 61+
 62+ function getTitle() {
 63+ $repo = $this->mRepo->getName();
 64+ return SpecialPage::getTitleFor( 'Code', "$repo/author/$this->mAuthor" );
 65+ }
 66+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionAuthorView.php
___________________________________________________________________
Added: svn:eol-style
167 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeStatusChangeListView.php
@@ -0,0 +1,113 @@
 2+<?php
 3+
 4+// Special:Code/MediaWiki
 5+class CodeStatusChangeListView extends CodeView {
 6+ public $mRepo;
 7+
 8+ function __construct( $repoName ) {
 9+ parent::__construct();
 10+ $this->mRepo = CodeRepository::newFromName( $repoName );
 11+ }
 12+
 13+ function execute() {
 14+ global $wgOut;
 15+ $pager = $this->getPager();
 16+ $wgOut->addHTML(
 17+ $pager->getNavigationBar() .
 18+ $pager->getLimitForm() .
 19+ $pager->getBody() .
 20+ $pager->getNavigationBar()
 21+ );
 22+ }
 23+
 24+ function getPager() {
 25+ return new CodeStatusChangeTablePager( $this );
 26+ }
 27+ function getRepo() {
 28+ return $this->mRepo;
 29+ }
 30+}
 31+
 32+// Pager for CodeRevisionListView
 33+class CodeStatusChangeTablePager extends TablePager {
 34+
 35+ function __construct( CodeStatusChangeListView $view ) {
 36+ global $IP;
 37+ $this->mView = $view;
 38+ $this->mRepo = $view->mRepo;
 39+ $this->mDefaultDirection = true;
 40+ $this->mCurSVN = SpecialVersion::getSvnRevision( $IP );
 41+ parent::__construct();
 42+ }
 43+
 44+ function isFieldSortable( $field ) {
 45+ return $field == 'cpc_timestamp';
 46+ }
 47+
 48+ function getDefaultSort() { return 'cpc_timestamp'; }
 49+
 50+ function getQueryInfo() {
 51+ return array(
 52+ 'tables' => array( 'code_prop_changes', 'code_rev' ),
 53+ 'fields' => array_keys( $this->getFieldNames() ),
 54+ 'conds' => array( 'cpc_repo_id' => $this->mRepo->getId() ),
 55+ 'join_conds' => array(
 56+ 'code_rev' => array( 'LEFT JOIN', 'cpc_repo_id = cr_repo_id AND cpc_rev_id = cr_id' )
 57+ )
 58+ );
 59+ }
 60+
 61+ function getFieldNames() {
 62+ return array(
 63+ 'cpc_timestamp' => wfMsg( 'code-field-timestamp' ),
 64+ 'cpc_user_text' => wfMsg( 'code-field-user' ),
 65+ 'cpc_rev_id' => wfMsg( 'code-field-id' ),
 66+ 'cpc_removed' => wfMsg( 'code-field-text' ),
 67+ 'cpc_removed' => wfMsg( 'code-old-status' ),
 68+ 'cpc_added' => wfMsg( 'code-new-status' ),
 69+ 'cr_status' => wfMsg( 'code-field-status' ),
 70+ );
 71+ }
 72+
 73+ function formatValue( $name, $value ) {
 74+ global $wgUser, $wgLang;
 75+ switch( $name ) {
 76+ case 'cpc_rev_id':
 77+ return $this->mView->mSkin->link(
 78+ SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value . '#code-changes' ),
 79+ htmlspecialchars( $value ) );
 80+ case 'cr_status':
 81+ return $this->mView->mSkin->link(
 82+ SpecialPage::getTitleFor( 'Code',
 83+ $this->mRepo->getName() . '/status/' . $value ),
 84+ htmlspecialchars( $this->mView->statusDesc( $value ) ) );
 85+ case 'cpc_user_text':
 86+ return $this->mView->mSkin->userLink( -1, $value );
 87+ case 'cpc_removed':
 88+ return wfMsgHtml( $value ? "code-status-$value" : "code-status-new" );
 89+ case 'cpc_added':
 90+ return wfMsgHtml( "code-status-$value" );
 91+ case 'cpc_timestamp':
 92+ global $wgLang;
 93+ return $wgLang->timeanddate( $value, true );
 94+ }
 95+ }
 96+
 97+ // Note: this function is poorly factored in the parent class
 98+ function formatRow( $row ) {
 99+ global $wgWikiSVN;
 100+ $class = "mw-codereview-status-{$row->cr_status}";
 101+ if ( $this->mRepo->mName == $wgWikiSVN ) {
 102+ $class .= " mw-codereview-" . ( $row->cpc_rev_id <= $this->mCurSVN ? 'live' : 'notlive' );
 103+ }
 104+ return str_replace(
 105+ '<tr>',
 106+ Xml::openElement( 'tr',
 107+ array( 'class' => $class ) ),
 108+ parent::formatRow( $row ) );
 109+ }
 110+
 111+ function getTitle() {
 112+ return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/statuschanges' );
 113+ }
 114+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeStatusChangeListView.php
___________________________________________________________________
Added: svn:eol-style
1115 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/SpecialCode.php
@@ -0,0 +1,240 @@
 2+<?php
 3+if ( !defined( 'MEDIAWIKI' ) ) die();
 4+
 5+class SpecialCode extends SpecialPage {
 6+ function __construct() {
 7+ parent::__construct( 'Code' , 'codereview-use' );
 8+ }
 9+
 10+ function execute( $subpage ) {
 11+ global $wgOut, $wgRequest, $wgUser, $wgScriptPath, $wgCodeReviewStyleVersion;
 12+
 13+ wfLoadExtensionMessages( 'CodeReview' );
 14+
 15+ if( !$this->userCanExecute( $wgUser ) ) {
 16+ $this->displayRestrictionError();
 17+ return;
 18+ }
 19+
 20+ $this->setHeaders();
 21+ $wgOut->addStyle( "$wgScriptPath/extensions/CodeReview/codereview.css?$wgCodeReviewStyleVersion" );
 22+ # Remove stray slashes
 23+ $subpage = preg_replace( '/\/$/', '', $subpage );
 24+
 25+ if ( $subpage == '' ) {
 26+ $view = new CodeRepoListView();
 27+ } else {
 28+ $params = explode( '/', $subpage );
 29+ switch( count( $params ) ) {
 30+ case 1:
 31+ $view = new CodeRevisionListView( $params[0] );
 32+ break;
 33+ case 2:
 34+ if ( $params[1] === 'tag' ) {
 35+ $view = new CodeTagListView( $params[0] );
 36+ break;
 37+ } elseif ( $params[1] === 'author' ) {
 38+ $view = new CodeAuthorListView( $params[0] );
 39+ break;
 40+ } elseif ( $params[1] === 'status' ) {
 41+ $view = new CodeStatusListView( $params[0] );
 42+ break;
 43+ } elseif ( $params[1] === 'comments' ) {
 44+ $view = new CodeCommentsListView( $params[0] );
 45+ break;
 46+ } elseif ( $params[1] === 'statuschanges' ) {
 47+ $view = new CodeStatusChangeListView( $params[0] );
 48+ break;
 49+ } elseif ( $params[1] === 'releasenotes' ) {
 50+ $view = new CodeReleaseNotes( $params[0] );
 51+ break;
 52+ } else if ( $wgRequest->wasPosted() && !$wgRequest->getCheck( 'wpPreview' ) ) {
 53+ # Add any tags, Set status, Adds comments
 54+ $submit = new CodeRevisionCommitter( $params[0], $params[1] );
 55+ $submit->execute();
 56+ return;
 57+ } else { // revision details
 58+ $view = new CodeRevisionView( $params[0], $params[1] );
 59+ break;
 60+ }
 61+ case 3:
 62+ if ( $params[1] === 'tag' ) {
 63+ $view = new CodeRevisionTagView( $params[0], $params[2] );
 64+ break;
 65+ } elseif ( $params[1] === 'author' ) {
 66+ $view = new CodeRevisionAuthorView( $params[0], $params[2] );
 67+ break;
 68+ } elseif ( $params[1] === 'status' ) {
 69+ $view = new CodeRevisionStatusView( $params[0], $params[2] );
 70+ break;
 71+ } elseif ( $params[1] === 'comments' ) {
 72+ $view = new CodeCommentsListView( $params[0] );
 73+ break;
 74+ } else {
 75+ # Nonsense parameters, back out
 76+ if ( empty( $params[1] ) )
 77+ $view = new CodeRevisionListView( $params[0] );
 78+ else
 79+ $view = new CodeRevisionView( $params[0], $params[1] );
 80+ break;
 81+ }
 82+ case 4:
 83+ if ( $params[1] == 'author' && $params[3] == 'link' ) {
 84+ $view = new CodeRevisionAuthorLink( $params[0], $params[2] );
 85+ break;
 86+ }
 87+ default:
 88+ if ( $params[2] == 'reply' ) {
 89+ $view = new CodeRevisionView( $params[0], $params[1], $params[3] );
 90+ break;
 91+ }
 92+ $wgOut->addWikiText( wfMsg( 'nosuchactiontext' ) );
 93+ $wgOut->returnToMain( null, SpecialPage::getTitleFor( 'Code' ) );
 94+ return;
 95+ }
 96+ }
 97+ $view->execute();
 98+
 99+ // Add subtitle for easy navigation
 100+ global $wgOut;
 101+ if ( $view instanceof CodeView && ( $repo = $view->getRepo() ) ) {
 102+ $wgOut->setSubtitle( wfMsgExt( 'codereview-subtitle', 'parse', CodeRepoListView::getNavItem( $repo->getName() ) ) );
 103+ }
 104+ }
 105+}
 106+
 107+/**
 108+ * Extended by CodeRevisionListView and CodeRevisionView
 109+ */
 110+abstract class CodeView {
 111+ var $mRepo;
 112+
 113+ function __construct() {
 114+ global $wgUser;
 115+ $this->mSkin = $wgUser->getSkin();
 116+ }
 117+
 118+ function validPost( $permission ) {
 119+ global $wgRequest, $wgUser;
 120+ return $wgRequest->wasPosted()
 121+ && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) )
 122+ && $wgUser->isAllowed( $permission );
 123+ }
 124+
 125+ abstract function execute();
 126+
 127+ /*
 128+ * returns a User object if $author has a wikiuser associated,
 129+ * of false
 130+ */
 131+ function authorWikiUser( $author ) {
 132+ return $this->mRepo->authorWikiUser( $author );
 133+ }
 134+
 135+ function authorLink( $author ) {
 136+ $repo = $this->mRepo->getName();
 137+ $special = SpecialPage::getTitleFor( 'Code', "$repo/author/$author" );
 138+ return $this->mSkin->link( $special, htmlspecialchars( $author ) );
 139+ }
 140+
 141+ function statusDesc( $status ) {
 142+ return wfMsg( "code-status-$status" );
 143+ }
 144+
 145+ function formatMessage( $text ) {
 146+ $text = nl2br( htmlspecialchars( $text ) );
 147+ $linker = new CodeCommentLinkerHtml( $this->mRepo );
 148+ return $linker->link( $text );
 149+ }
 150+
 151+ function messageFragment( $value ) {
 152+ global $wgLang;
 153+ $message = trim( $value );
 154+ $lines = explode( "\n", $message, 2 );
 155+ $first = $lines[0];
 156+ $trimmed = $wgLang->truncate( $first, 80 );
 157+ return $this->formatMessage( $trimmed );
 158+ }
 159+ /*
 160+ * Formatted HTML array for properties display
 161+ * @param array fields : 'propname' => HTML data
 162+ */
 163+ function formatMetaData( $fields ) {
 164+ $html = '<table class="mw-codereview-meta">';
 165+ foreach ( $fields as $label => $data ) {
 166+ $html .= "<tr><td>" . wfMsgHtml( $label ) . "</td><td>$data</td></tr>\n";
 167+ }
 168+ return $html . "</table>\n";
 169+ }
 170+
 171+ function getRepo() {
 172+ if ( $this->mRepo )
 173+ return $this->mRepo;
 174+ return false;
 175+ }
 176+}
 177+
 178+class CodeCommentLinker {
 179+ function __construct( $repo ) {
 180+ global $wgUser;
 181+ $this->mSkin = $wgUser->getSkin();
 182+ $this->mRepo = $repo;
 183+ }
 184+
 185+ function link( $text ) {
 186+ # Catch links like http://www.mediawiki.org/wiki/Special:Code/MediaWiki/44245#c829
 187+ # Ended by space or brackets (like those pesky <br/> tags)
 188+ $text = preg_replace_callback( '/(\b)(' . wfUrlProtocols() . ')([^ <>]+)(\b)/', array( $this, 'generalLink' ), $text );
 189+ $text = preg_replace_callback( '/\br(\d+)\b/', array( $this, 'messageRevLink' ), $text );
 190+ $text = preg_replace_callback( '/\bbug #?(\d+)\b/i', array( $this, 'messageBugLink' ), $text );
 191+ return $text;
 192+ }
 193+
 194+ function generalLink( $arr ) {
 195+ $url = $arr[2] . $arr[3];
 196+ // Re-add the surrounding space/punctuation
 197+ return $arr[1] . $this->makeExternalLink( $url, $url ) . $arr[4];
 198+ }
 199+
 200+ function messageBugLink( $arr ) {
 201+ $text = $arr[0];
 202+ $bugNo = intval( $arr[1] );
 203+ $url = $this->mRepo->getBugPath( $bugNo );
 204+ if ( $url ) {
 205+ return $this->makeExternalLink( $url, $text );
 206+ } else {
 207+ return $text;
 208+ }
 209+ }
 210+
 211+ function messageRevLink( $matches ) {
 212+ $text = $matches[0];
 213+ $rev = intval( $matches[1] );
 214+
 215+ $repo = $this->mRepo->getName();
 216+ $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" );
 217+
 218+ return $this->makeInternalLink( $title, $text );
 219+ }
 220+
 221+}
 222+
 223+class CodeCommentLinkerHtml extends CodeCommentLinker {
 224+ function makeExternalLink( $url, $text ) {
 225+ return $this->mSkin->makeExternalLink( $url, $text );
 226+ }
 227+
 228+ function makeInternalLink( $title, $text ) {
 229+ return $this->mSkin->link( $title, $text );
 230+ }
 231+}
 232+
 233+class CodeCommentLinkerWiki extends CodeCommentLinker {
 234+ function makeExternalLink( $url, $text ) {
 235+ return "[$url $text]";
 236+ }
 237+
 238+ function makeInternalLink( $title, $text ) {
 239+ return "[[" . $title->getPrefixedText() . "|$text]]";
 240+ }
 241+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/SpecialCode.php
___________________________________________________________________
Added: svn:eol-style
1242 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionCommitter.php
@@ -0,0 +1,76 @@
 2+<?php
 3+
 4+class CodeRevisionCommitter extends CodeRevisionView {
 5+
 6+ function __construct( $repoName, $rev ) {
 7+ // Parent should set $this->mRepo, $this->mRev, $this->mReplyTarget
 8+ parent::__construct( $repoName, $rev );
 9+ }
 10+
 11+ function execute() {
 12+ global $wgRequest, $wgOut, $wgUser;
 13+
 14+ if ( !$wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
 15+ $wgOut->addHTML( '<strong>' . wfMsg( 'sessionfailure' ) . '</strong>' );
 16+ parent::execute();
 17+ return;
 18+ }
 19+ if ( !$this->mRev ) {
 20+ parent::execute();
 21+ return;
 22+ }
 23+
 24+ $redirTarget = null;
 25+ $dbw = wfGetDB( DB_MASTER );
 26+
 27+ $dbw->begin();
 28+ // Change the status if allowed
 29+ if ( $this->validPost( 'codereview-set-status' ) && $this->mRev->isValidStatus( $this->mStatus ) ) {
 30+ $this->mRev->setStatus( $this->mStatus, $wgUser );
 31+ }
 32+ $addTags = $removeTags = array();
 33+ if ( $this->validPost( 'codereview-add-tag' ) && count( $this->mAddTags ) ) {
 34+ $addTags = $this->mAddTags;
 35+ }
 36+ if ( $this->validPost( 'codereview-remove-tag' ) && count( $this->mRemoveTags ) ) {
 37+ $removeTags = $this->mRemoveTags;
 38+ }
 39+ // If allowed to change any tags, then do so
 40+ if ( count( $addTags ) || count( $removeTags ) ) {
 41+ $this->mRev->changeTags( $addTags, $removeTags, $wgUser );
 42+ }
 43+ // Add any comments
 44+ if ( $this->validPost( 'codereview-post-comment' ) && strlen( $this->text ) ) {
 45+ $parent = $wgRequest->getIntOrNull( 'wpParent' );
 46+ $review = $wgRequest->getInt( 'wpReview' );
 47+ $isPreview = $wgRequest->getCheck( 'wpPreview' );
 48+ $id = $this->mRev->saveComment( $this->text, $review, $parent );
 49+ // For comments, take us back to the rev page focused on the new comment
 50+ if ( !$this->jumpToNext ) {
 51+ $redirTarget = $this->commentLink( $id );
 52+ }
 53+ }
 54+ $dbw->commit();
 55+
 56+ // Return to rev page
 57+ if ( !$redirTitle ) {
 58+ if ( $this->jumpToNext ) {
 59+ if ( $next = $this->mRev->getNextUnresolved() ) {
 60+ $redirTitle = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $next );
 61+ } else {
 62+ $redirTitle = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() );
 63+ }
 64+ } else {
 65+ # $redirTarget already set for comments
 66+ $redirTitle = $redirTarget ? $redirTarget : $this->revLink();
 67+ }
 68+ }
 69+ $wgOut->redirect( $redirTitle->getFullUrl() );
 70+ }
 71+
 72+ public function validPost( $permission ) {
 73+ global $wgUser, $wgRequest;
 74+ return parent::validPost( $permission ) && $wgRequest->wasPosted()
 75+ && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
 76+ }
 77+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionCommitter.php
___________________________________________________________________
Added: svn:eol-style
178 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/SpecialRepoAdmin.php
@@ -0,0 +1,123 @@
 2+<?php
 3+if ( !defined( 'MEDIAWIKI' ) ) die();
 4+
 5+class SpecialRepoAdmin extends SpecialPage {
 6+ function __construct() {
 7+ parent::__construct( 'RepoAdmin', 'repoadmin' );
 8+ }
 9+
 10+ function execute( $subpage ) {
 11+ global $wgRequest, $wgUser;
 12+
 13+ wfLoadExtensionMessages( 'CodeReview' );
 14+ $this->setHeaders();
 15+
 16+ if ( !$this->userCanExecute( $wgUser ) ) {
 17+ $this->displayRestrictionError();
 18+ return;
 19+ }
 20+
 21+ $repo = $wgRequest->getVal( 'repo', $subpage );
 22+ if ( $repo == '' ) {
 23+ $view = new RepoAdminListView( $this );
 24+ } else {
 25+ $view = new RepoAdminRepoView( $this, $repo );
 26+ }
 27+ $view->execute();
 28+ }
 29+}
 30+
 31+class RepoAdminListView {
 32+ var $mPage;
 33+
 34+ function __construct( $page ) {
 35+ $this->mPage = $page;
 36+ }
 37+
 38+ function getForm() {
 39+ global $wgScript;
 40+ return Xml::fieldset( wfMsg( 'repoadmin-new-legend' ) ) .
 41+ Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ) .
 42+ Xml::hidden( 'title', $this->mPage->getTitle()->getPrefixedDBKey() ) .
 43+ Xml::inputLabel( wfMsg( 'repoadmin-new-label' ), 'repo', 'repo' ) .
 44+ Xml::submitButton( wfMsg( 'repoadmin-new-button' ) ) .
 45+ '</form></fieldset>';
 46+ }
 47+
 48+ function execute() {
 49+ global $wgOut;
 50+ $wgOut->addHTML( $this->getForm() );
 51+ $repos = CodeRepository::getRepoList();
 52+ if ( !count( $repos ) ) {
 53+ return;
 54+ }
 55+ $text = '';
 56+ foreach ( $repos as $repo ) {
 57+ $name = $repo->getName();
 58+ $text .= "* [[Special:RepoAdmin/$name|$name]]\n";
 59+ }
 60+ $wgOut->addWikiText( $text );
 61+ }
 62+}
 63+
 64+class RepoAdminRepoView {
 65+ var $mPage;
 66+
 67+ function __construct( $page, $repo ) {
 68+ $this->mPage = $page;
 69+ $this->mRepoName = $repo;
 70+ $this->mRepo = CodeRepository::newFromName( $repo );
 71+ }
 72+
 73+ function execute() {
 74+ global $wgOut, $wgRequest, $wgUser;
 75+ $repoExists = (bool)$this->mRepo;
 76+ $repoPath = $wgRequest->getVal( 'wpRepoPath', $repoExists ? $this->mRepo->mPath : '' );
 77+ $bugPath = $wgRequest->getVal( 'wpBugPath', $repoExists ? $this->mRepo->mBugzilla : '' );
 78+ $viewPath = $wgRequest->getVal( 'wpViewPath', $repoExists ? $this->mRepo->mViewVc : '' );
 79+ if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ), $this->mRepoName ) ) {
 80+ // @todo log
 81+ $dbw = wfGetDB( DB_MASTER );
 82+ if ( $repoExists ) {
 83+ $dbw->update(
 84+ 'code_repo',
 85+ array(
 86+ 'repo_path' => $repoPath,
 87+ 'repo_viewvc' => $viewPath,
 88+ 'repo_bugzilla' => $bugPath
 89+ ),
 90+ array( 'repo_id' => $this->mRepo->getId() ),
 91+ __METHOD__
 92+ );
 93+ } else {
 94+ $dbw->insert(
 95+ 'code_repo',
 96+ array(
 97+ 'repo_name' => $this->mRepoName,
 98+ 'repo_path' => $repoPath,
 99+ 'repo_viewvc' => $viewPath,
 100+ 'repo_bugzilla' => $bugPath
 101+ ),
 102+ __METHOD__
 103+ );
 104+ }
 105+ $wgOut->wrapWikiMsg( '<div class="successbox">$1</div>', array( 'repoadmin-edit-sucess', $this->mRepoName ) );
 106+ return;
 107+ }
 108+ $wgOut->addHTML(
 109+ Xml::fieldset( wfMsg( 'repoadmin-edit-legend', $this->mRepoName ) ) .
 110+ Xml::openElement( 'form', array( 'method' => 'post', 'action' => $this->mPage->getTitle( $this->mRepoName )->getLocalURL() ) ) .
 111+ Xml::buildForm(
 112+ array(
 113+ 'repoadmin-edit-path' =>
 114+ Xml::input( 'wpRepoPath', 60, $repoPath ),
 115+ 'repoadmin-edit-bug' =>
 116+ Xml::input( 'wpBugPath', 60, $bugPath ),
 117+ 'repoadmin-edit-view' =>
 118+ Xml::input( 'wpViewPath', 60, $viewPath ) ) ) .
 119+ Xml::hidden( 'wpEditToken', $wgUser->editToken( $this->mRepoName ) ) .
 120+ Xml::submitButton( wfMsg( 'repoadmin-edit-button' ) ) .
 121+ '</form></fieldset>'
 122+ );
 123+ }
 124+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/SpecialRepoAdmin.php
___________________________________________________________________
Added: svn:eol-style
1125 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionListView.php
@@ -0,0 +1,342 @@
 2+<?php
 3+
 4+// Special:Code/MediaWiki
 5+class CodeRevisionListView extends CodeView {
 6+ public $mRepo, $mPath;
 7+ function __construct( $repoName ) {
 8+ global $wgRequest;
 9+ parent::__construct();
 10+ $this->mRepo = CodeRepository::newFromName( $repoName );
 11+ $this->mPath = htmlspecialchars( trim( $wgRequest->getVal( 'path' ) ) );
 12+ if ( strlen( $this->mPath ) && $this->mPath[0] !== '/' ) {
 13+ $this->mPath = "/{$this->mPath}"; // make sure this is a valid path
 14+ }
 15+ $this->mAuthor = null;
 16+ }
 17+
 18+ function execute() {
 19+ global $wgOut, $wgUser, $wgRequest;
 20+ if ( !$this->mRepo ) {
 21+ $view = new CodeRepoListView();
 22+ $view->execute();
 23+ return;
 24+ }
 25+
 26+ // Check for batch change requests.
 27+ $editToken = $wgRequest->getVal( 'wpBatchChangeEditToken' );
 28+ if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $editToken ) ) {
 29+ $this->doBatchChange();
 30+ return;
 31+ }
 32+
 33+ $this->showForm();
 34+ $pager = $this->getPager();
 35+
 36+ // Build batch change interface as needed
 37+ $this->batchForm = $wgUser->isAllowed( 'codereview-set-status' ) ||
 38+ $wgUser->isAllowed( 'codereview-add-tag' );
 39+
 40+ $wgOut->addHTML(
 41+ $pager->getNavigationBar() .
 42+ $pager->getLimitForm() .
 43+ Xml::openElement( 'form',
 44+ array( 'action' => $pager->getTitle()->getLocalURL(), 'method' => 'post' )
 45+ ) .
 46+ $pager->getBody() .
 47+ $pager->getNavigationBar() .
 48+ ( $this->batchForm ? $this->buildBatchInterface( $pager ) : "" ) .
 49+ Xml::closeElement( 'form' )
 50+ );
 51+ }
 52+
 53+ function doBatchChange() {
 54+ global $wgRequest;
 55+
 56+ $revisions = $wgRequest->getArray( 'wpRevisionSelected' );
 57+ $removeTags = $wgRequest->getVal( 'wpRemoveTag' );
 58+ $addTags = $wgRequest->getVal( 'wpTag' );
 59+ $status = $wgRequest->getVal( 'wpStatus' );
 60+
 61+ // Grab data from the DB
 62+ $dbr = wfGetDB( DB_SLAVE );
 63+ $revObjects = array();
 64+ $res = $dbr->select( 'code_rev', '*', array( 'cr_id' => $revisions ), __METHOD__ );
 65+ while ( $row = $dbr->fetchObject( $res ) ) {
 66+ $revObjects[] = CodeRevision::newFromRow( $this->mRepo, $row );
 67+ }
 68+
 69+ global $wgUser;
 70+ if ( $wgUser->isAllowed( 'codereview-add-tag' ) &&
 71+ $addTags || $removeTags ) {
 72+ $addTags = array_map( 'trim', explode( ",", $addTags ) );
 73+ $removeTags = array_map( 'trim', explode( ",", $removeTags ) );
 74+
 75+ foreach ( $revObjects as $id => $rev ) {
 76+ $rev->changeTags( $addTags, $removeTags, $wgUser );
 77+ }
 78+ }
 79+
 80+ if ( $wgUser->isAllowed( 'codereview-set-status' ) &&
 81+ $revObjects[0]->isValidStatus( $status ) ) {
 82+ foreach ( $revObjects as $id => $rev ) {
 83+ $rev->setStatus( $status, $wgUser );
 84+ }
 85+ }
 86+
 87+ // Automatically refresh
 88+ // This way of getting GET parameters is horrible, but effective.
 89+ $fields = array_merge( $_GET, $_POST );
 90+ foreach ( array_keys( $fields ) as $key ) {
 91+ if ( substr( $key, 0, 2 ) == 'wp' || $key == 'title' )
 92+ unset( $fields[$key] );
 93+ }
 94+
 95+ global $wgOut;
 96+ $wgOut->redirect( $this->getPager()->getTitle()->getFullURL( $fields ) );
 97+ }
 98+
 99+ protected function buildBatchInterface( $pager ) {
 100+ global $wgUser;
 101+
 102+ $changeInterface = '';
 103+ $changeFields = array();
 104+
 105+ if ( $wgUser->isAllowed( 'codereview-set-status' ) ) {
 106+ $changeFields['code-batch-status'] =
 107+ Xml::tags( 'select', array( 'name' => 'wpStatus' ),
 108+ Xml::tags( 'option',
 109+ array( 'value' => '', 'selected' => 'selected' ), ' '
 110+ ) .
 111+ CodeRevisionView::buildStatusList( null, $this )
 112+ );
 113+ }
 114+
 115+ if ( $wgUser->isAllowed( 'codereview-add-tag' ) ) {
 116+ $changeFields['code-batch-tags'] = CodeRevisionView::addTagForm( '', '' );
 117+ }
 118+
 119+ if ( !count( $changeFields ) ) return ''; // nothing to do here
 120+
 121+ $changeInterface = Xml::fieldset( wfMsg( 'codereview-batch-title' ),
 122+ Xml::buildForm( $changeFields, 'codereview-batch-submit' ) );
 123+
 124+ $changeInterface .= $pager->getHiddenFields();
 125+ $changeInterface .= Xml::hidden( 'wpBatchChangeEditToken', $wgUser->editToken() );
 126+
 127+ return $changeInterface;
 128+ }
 129+
 130+ function showForm( $path = '' ) {
 131+ global $wgOut, $wgScript;
 132+ if ( $this->mAuthor ) {
 133+ $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/author/' . $this->mAuthor );
 134+ } else {
 135+ $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/path' );
 136+ }
 137+ $wgOut->addHTML(
 138+ Xml::openElement( 'form', array( 'action' => $wgScript, 'method' => 'get' ) ) .
 139+ "<fieldset><legend>" . wfMsgHtml( 'code-pathsearch-legend' ) . "</legend>" .
 140+ Xml::hidden( 'title', $special->getPrefixedDBKey() ) .
 141+ Xml::inputlabel( wfMsg( "code-pathsearch-path" ), 'path', 'path', 55, $this->mPath ) .
 142+ '&nbsp;' . Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
 143+ "</fieldset>" . Xml::closeElement( 'form' )
 144+ );
 145+ }
 146+
 147+ function getPager() {
 148+ return new SvnRevTablePager( $this );
 149+ }
 150+}
 151+
 152+// Pager for CodeRevisionListView
 153+class SvnRevTablePager extends TablePager {
 154+
 155+ function __construct( $view ) {
 156+ global $IP;
 157+ $this->mView = $view;
 158+ $this->mRepo = $view->mRepo;
 159+ $this->mDefaultDirection = true;
 160+ $this->mCurSVN = SpecialVersion::getSvnRevision( $IP );
 161+ parent::__construct();
 162+ }
 163+
 164+ function getSVNPath() {
 165+ return $this->mView->mPath;
 166+ }
 167+
 168+ function isFieldSortable( $field ) {
 169+ return $field == $this->getDefaultSort();
 170+ }
 171+
 172+ function getDefaultSort() {
 173+ return strlen( $this->mView->mPath ) ? 'cp_rev_id' : 'cr_id';
 174+ }
 175+
 176+ function getQueryInfo() {
 177+ // Path-based query...
 178+ if ( $this->getDefaultSort() === 'cp_rev_id' ) {
 179+ return array(
 180+ 'tables' => array( 'code_paths', 'code_rev', 'code_comment' ),
 181+ 'fields' => $this->getSelectFields(),
 182+ 'conds' => array(
 183+ 'cp_repo_id' => $this->mRepo->getId(),
 184+ 'cp_path LIKE ' . $this->mDb->addQuotes( $this->mDb->escapeLike( $this->getSVNPath() ) . '%' ),
 185+ // performance
 186+ 'cp_rev_id > ' . $this->mRepo->getLastStoredRev() - 20000
 187+ ),
 188+ 'options' => array( 'GROUP BY' => 'cp_rev_id', 'USE INDEX' => array( 'code_path' => 'cp_repo_id' ) ),
 189+ 'join_conds' => array(
 190+ 'code_rev' => array( 'INNER JOIN', 'cr_repo_id = cp_repo_id AND cr_id = cp_rev_id' ),
 191+ 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cp_repo_id AND cc_rev_id = cp_rev_id' ),
 192+ )
 193+ );
 194+ // No path; entire repo...
 195+ } else {
 196+ return array(
 197+ 'tables' => array( 'code_rev', 'code_comment' ),
 198+ 'fields' => $this->getSelectFields(),
 199+ 'conds' => array( 'cr_repo_id' => $this->mRepo->getId() ),
 200+ 'options' => array( 'GROUP BY' => 'cr_id' ),
 201+ 'join_conds' => array(
 202+ 'code_comment' => array( 'LEFT JOIN', 'cc_repo_id = cr_repo_id AND cc_rev_id = cr_id' ),
 203+ )
 204+ );
 205+ }
 206+ return false;
 207+ }
 208+
 209+ function getSelectFields() {
 210+ return array( $this->getDefaultSort(),
 211+ 'cr_id',
 212+ 'cr_repo_id',
 213+ 'cr_status',
 214+ 'COUNT(DISTINCT cc_id) AS comments',
 215+ 'cr_path',
 216+ 'cr_message',
 217+ 'cr_author',
 218+ 'cr_timestamp' );
 219+ }
 220+
 221+ function getFieldNames() {
 222+ $fields = array(
 223+ $this->getDefaultSort() => wfMsg( 'code-field-id' ),
 224+ 'cr_status' => wfMsg( 'code-field-status' ),
 225+ 'comments' => wfMsg( 'code-field-comments' ),
 226+ 'tests' => wfMsg( 'code-field-tests' ),
 227+ 'cr_path' => wfMsg( 'code-field-path' ),
 228+ 'cr_message' => wfMsg( 'code-field-message' ),
 229+ 'cr_author' => wfMsg( 'code-field-author' ),
 230+ 'cr_timestamp' => wfMsg( 'code-field-timestamp' ),
 231+ );
 232+ # Only show checkboxen as needed
 233+ if ( !empty( $this->mView->batchForm ) ) {
 234+ $fields = array( 'selectforchange' => wfMsg( 'code-field-select' ) ) + $fields;
 235+ }
 236+ return $fields;
 237+ }
 238+
 239+ function formatValue( $name, $value ) { } // unused
 240+
 241+ function formatRevValue( $name, $value, $row ) {
 242+ global $wgUser, $wgLang;
 243+ switch( $name ) {
 244+ case 'selectforchange':
 245+ $sort = $this->getDefaultSort();
 246+ return Xml::check( "wpRevisionSelected[]", false, array( 'value' => $row->$sort ) );
 247+ case 'cp_rev_id':
 248+ case 'cr_id':
 249+ return $this->mView->mSkin->link(
 250+ SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $value ),
 251+ htmlspecialchars( $value ) );
 252+ case 'cr_status':
 253+ return $this->mView->mSkin->link(
 254+ SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/status/' . $value ),
 255+ htmlspecialchars( $this->mView->statusDesc( $value ) )
 256+ );
 257+ case 'cr_author':
 258+ return $this->mView->authorLink( $value );
 259+ case 'cr_message':
 260+ return $this->mView->messageFragment( $value );
 261+ case 'cr_timestamp':
 262+ global $wgLang;
 263+ return $wgLang->timeanddate( $value, true );
 264+ case 'comments':
 265+ if ( $value ) {
 266+ $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $row-> { $this->getDefaultSort() } );
 267+ $special->setFragment( '#code-comments' );
 268+ return $this->mView->mSkin->link( $special, htmlspecialchars( $value ) );
 269+ } else {
 270+ return intval( $value );
 271+ }
 272+ case 'tests':
 273+ // fixme -- this still isn't too efficient...
 274+ $rev = CodeRevision::newFromRow( $this->mRepo, $row );
 275+ $runs = $rev->getTestRuns();
 276+ if( empty( $runs ) ) {
 277+ return '&nbsp;';
 278+ } else {
 279+ $total = 0;
 280+ $success = 0;
 281+ $progress = false;
 282+ foreach( $runs as $run ) {
 283+ $total += $run->countTotal;
 284+ $success += $run->countSuccess;
 285+ if( $run->status == 'running' ) {
 286+ $progress = true;
 287+ }
 288+ }
 289+ if( $progress ) {
 290+ global $wgStylePath;
 291+ return Xml::element( 'img', array(
 292+ 'src' => "$wgStylePath/common/images/spinner.gif",
 293+ 'width' => 20,
 294+ 'height' => 20,
 295+ 'alt' => "...",
 296+ 'title' => "Tests in progress...",
 297+ ));
 298+ }
 299+ if( $success == $total ) {
 300+ $class = 'mw-codereview-success';
 301+ } else {
 302+ $class = 'mw-codereview-fail';
 303+ }
 304+ return "<span class='$class'><strong>$success</strong>/$total</span>";
 305+ }
 306+ case 'cr_path':
 307+ return Xml::openElement( 'div', array( 'title' => (string)$value ) ) .
 308+ $this->mView->mSkin->link(
 309+ SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/path' ),
 310+ $wgLang->truncate( (string)$value, 30 ),
 311+ array( 'title' => (string)$value ),
 312+ array( 'path' => (string)$value ) ) . "</div>";
 313+ }
 314+ }
 315+
 316+ // Note: this function is poorly factored in the parent class
 317+ function formatRow( $row ) {
 318+ global $wgWikiSVN;
 319+ $css = "mw-codereview-status-{$row->cr_status}";
 320+ if ( $this->mRepo->mName == $wgWikiSVN ) {
 321+ $css .= " mw-codereview-" . ( $row-> { $this->getDefaultSort() } <= $this->mCurSVN ? 'live' : 'notlive' );
 322+ }
 323+ $s = "<tr class=\"$css\">\n";
 324+ // Some of this stolen from Pager.php...sigh
 325+ $fieldNames = $this->getFieldNames();
 326+ $this->mCurrentRow = $row; # In case formatValue needs to know
 327+ foreach ( $fieldNames as $field => $name ) {
 328+ $value = isset( $row->$field ) ? $row->$field : null;
 329+ $formatted = strval( $this->formatRevValue( $field, $value, $row ) );
 330+ if ( $formatted == '' ) {
 331+ $formatted = '&nbsp;';
 332+ }
 333+ $class = 'TablePager_col_' . htmlspecialchars( $field );
 334+ $s .= "<td class=\"$class\">$formatted</td>\n";
 335+ }
 336+ $s .= "</tr>\n";
 337+ return $s;
 338+ }
 339+
 340+ function getTitle() {
 341+ return SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() );
 342+ }
 343+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionListView.php
___________________________________________________________________
Added: svn:eol-style
1344 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionTagView.php
@@ -0,0 +1,33 @@
 2+<?php
 3+
 4+class CodeRevisionTagView extends CodeRevisionListView {
 5+ function __construct( $repoName, $tag ) {
 6+ parent::__construct( $repoName );
 7+ $this->mTag = $tag;
 8+ }
 9+
 10+ function getPager() {
 11+ return new SvnRevTagTablePager( $this, $this->mTag );
 12+ }
 13+}
 14+
 15+class SvnRevTagTablePager extends SvnRevTablePager {
 16+ function __construct( $view, $tag ) {
 17+ parent::__construct( $view );
 18+ $this->mTag = $tag;
 19+ }
 20+
 21+ function getQueryInfo() {
 22+ $info = parent::getQueryInfo();
 23+ $info['tables'][] = 'code_tags';
 24+ $info['conds'][] = 'cr_repo_id=ct_repo_id';
 25+ $info['conds'][] = 'cr_id=ct_rev_id';
 26+ $info['conds']['ct_tag'] = $this->mTag; // fixme: normalize input?
 27+ return $info;
 28+ }
 29+
 30+ function getTitle() {
 31+ $repo = $this->mRepo->getName();
 32+ return SpecialPage::getTitleFor( 'Code', "$repo/tag/$this->mTag" );
 33+ }
 34+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionTagView.php
___________________________________________________________________
Added: svn:eol-style
135 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeAuthorListView.php
@@ -0,0 +1,21 @@
 2+<?php
 3+
 4+// Special:Code/MediaWiki/author
 5+class CodeAuthorListView extends CodeView {
 6+ function __construct( $repoName ) {
 7+ parent::__construct();
 8+ $this->mRepo = CodeRepository::newFromName( $repoName );
 9+ }
 10+
 11+ function execute() {
 12+ global $wgOut;
 13+ $authors = $this->mRepo->getAuthorList();
 14+ $name = $this->mRepo->getName();
 15+ $text = wfMsg( 'code-authors-text' ) . "\n";
 16+ foreach ( $authors as $user ) {
 17+ if ( $user )
 18+ $text .= "* [[Special:Code/$name/author/$user|$user]]\n";
 19+ }
 20+ $wgOut->addWikiText( $text );
 21+ }
 22+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeAuthorListView.php
___________________________________________________________________
Added: svn:eol-style
123 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeReleaseNotes.php
@@ -0,0 +1,168 @@
 2+<?php
 3+
 4+class CodeReleaseNotes extends CodeView {
 5+ function __construct( $repoName ) {
 6+ global $wgRequest, $wgWikiSVN, $IP;
 7+ parent::__construct( $repoName );
 8+ $this->mRepo = CodeRepository::newFromName( $repoName );
 9+ $this->mPath = htmlspecialchars( trim( $wgRequest->getVal( 'path' ) ) );
 10+ if ( strlen( $this->mPath ) && $this->mPath[0] !== '/' ) {
 11+ $this->mPath = "/{$this->mPath}"; // make sure this is a valid path
 12+ }
 13+ $this->mPath = preg_replace( '/\/$/', '', $this->mPath ); // kill last slash
 14+ $this->mStartRev = $wgRequest->getIntOrNull( 'startrev' );
 15+ $this->mEndRev = $wgRequest->getIntOrNull( 'endrev' );
 16+ # Default start rev to last live one if possible
 17+ if ( !$this->mStartRev && $this->mRepo && $this->mRepo->getName() == $wgWikiSVN ) {
 18+ $this->mStartRev = SpecialVersion::getSvnRevision( $IP ) + 1;
 19+ }
 20+ }
 21+
 22+ function execute() {
 23+ if ( !$this->mRepo ) {
 24+ $view = new CodeRepoListView();
 25+ $view->execute();
 26+ return;
 27+ }
 28+ $this->showForm();
 29+ # Sanity/performance check...
 30+ $lastRev = $this->mRepo->getLastStoredRev();
 31+ if ( $this->mStartRev < ( $lastRev - 3000 ) )
 32+ $this->mStartRev = NULL;
 33+ # Show notes if we have at least a starting revision
 34+ if ( $this->mStartRev ) {
 35+ $this->showReleaseNotes();
 36+ }
 37+ }
 38+
 39+ protected function showForm() {
 40+ global $wgOut, $wgScript, $wgUser;
 41+ $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/releasenotes' );
 42+ $wgOut->addHTML(
 43+ Xml::openElement( 'form', array( 'action' => $wgScript, 'method' => 'get' ) ) .
 44+ "<fieldset><legend>" . wfMsgHtml( 'code-release-legend' ) . "</legend>" .
 45+ Xml::hidden( 'title', $special->getPrefixedDBKey() ) . '<b>' .
 46+ Xml::inputlabel( wfMsg( "code-release-startrev" ), 'startrev', 'startrev', 10, $this->mStartRev ) .
 47+ '</b>&nbsp;' .
 48+ Xml::inputlabel( wfMsg( "code-release-endrev" ), 'endrev', 'endrev', 10, $this->mEndRev ) .
 49+ '&nbsp;' .
 50+ Xml::inputlabel( wfMsg( "code-pathsearch-path" ), 'path', 'path', 45, $this->mPath ) .
 51+ '&nbsp;' .
 52+ Xml::submitButton( wfMsg( 'allpagessubmit' ) ) . "\n" .
 53+ "</fieldset>" . Xml::closeElement( 'form' )
 54+ );
 55+ }
 56+
 57+ protected function showReleaseNotes() {
 58+ global $wgOut;
 59+ $linker = new CodeCommentLinkerHtml( $this->mRepo );
 60+ $dbr = wfGetDB( DB_SLAVE );
 61+ if ( $this->mEndRev ) {
 62+ $where = 'cr_id BETWEEN ' . intval( $this->mStartRev ) . ' AND ' . intval( $this->mEndRev );
 63+ } else {
 64+ $where = 'cr_id >= ' . intval( $this->mStartRev );
 65+ }
 66+ if ( $this->mPath ) {
 67+ $where .= ' AND (cr_path LIKE ' . $dbr->addQuotes( $dbr->escapeLike( "{$this->mPath}/" ) . '%' );
 68+ $where .= ' OR cr_path = ' . $dbr->addQuotes( $this->mPath ) . ')';
 69+ }
 70+ # Select commits within this range...
 71+ $res = $dbr->select( array( 'code_rev', 'code_tags' ),
 72+ array( 'cr_message', 'cr_author', 'cr_id', 'ct_tag AS rnotes' ),
 73+ array(
 74+ 'cr_repo_id' => $this->mRepo->getId(), // this repo
 75+ "cr_status NOT IN('reverted','deferred','fixme')", // not reverted/deferred/fixme
 76+ "cr_message != ''",
 77+ $where // in range
 78+ ),
 79+ __METHOD__,
 80+ array( 'ORDER BY' => 'cr_id DESC' ),
 81+ array( 'code_tags' => array( 'LEFT JOIN', # Tagged for release notes?
 82+ 'ct_repo_id = cr_repo_id AND ct_rev_id = cr_id AND ct_tag = "release-notes"' )
 83+ )
 84+ );
 85+ $wgOut->addHTML( '<ul>' );
 86+ # Output any relevant seeming commits...
 87+ while ( $row = $dbr->fetchObject( $res ) ) {
 88+ $summary = htmlspecialchars( $row->cr_message );
 89+ # Add this commit summary if needed.
 90+ if ( $row->rnotes || $this->isRelevant( $summary ) ) {
 91+ # Keep it short if possible...
 92+ $summary = $this->shortenSummary( $summary );
 93+ # Anything left? (this can happen with some heuristics)
 94+ if ( $summary ) {
 95+ $summary = str_replace( "\n", "<br/>", $summary ); // Newlines -> <br/>
 96+ $wgOut->addHTML( "<li>" );
 97+ $wgOut->addHTML(
 98+ $linker->link( $summary ) . " <i>(" . htmlspecialchars( $row->cr_author ) .
 99+ ', ' . $linker->link( "r{$row->cr_id}" ) . ")</i>"
 100+ );
 101+ $wgOut->addHTML( "</li>\n" );
 102+ }
 103+ }
 104+ }
 105+ $wgOut->addHTML( '</ul>' );
 106+ }
 107+
 108+ private function shortenSummary( $summary, $first = true ) {
 109+ # Astericks often used for point-by-point bullets
 110+ if ( preg_match( '/(^|\n) ?\*/', $summary ) ) {
 111+ $blurbs = explode( '*', $summary );
 112+ # Double newlines separate importance generally
 113+ } else if ( strpos( $summary, "\n\n" ) !== false ) {
 114+ $blurbs = explode( "\n\n", $summary );
 115+ } else {
 116+ return trim( $summary );
 117+ }
 118+ $blurbs = array_map( 'trim', $blurbs ); # Clean up items
 119+ $blurbs = array_filter( $blurbs ); # Filter out any garbage
 120+ # Doesn't start with '*' and has some length?
 121+ # If so, then assume that the top bit is important.
 122+ if ( count( $blurbs ) ) {
 123+ $header = strpos( ltrim( $summary ), '*' ) !== 0 && str_word_count( $blurbs[0] ) >= 5;
 124+ } else {
 125+ $header = false;
 126+ }
 127+ # Keep it short if possible...
 128+ if ( count( $blurbs ) > 1 ) {
 129+ $summary = array();
 130+ foreach ( $blurbs as $blurb ) {
 131+ # Always show the first bit
 132+ if ( $header && $first && count( $summary ) == 0 ) {
 133+ $summary[] = $this->shortenSummary( $blurb, true );
 134+ # Is this bit important? Does it mention a revision?
 135+ } else if ( $this->isRelevant( $blurb ) || preg_match( '/\br(\d+)\b/', $blurb ) ) {
 136+ $bit = $this->shortenSummary( $blurb, false );
 137+ if ( $bit ) $summary[] = $bit;
 138+ }
 139+ }
 140+ $summary = implode( "\n", $summary );
 141+ } else {
 142+ $summary = implode( "\n", $blurbs );
 143+ }
 144+ return $summary;
 145+ }
 146+
 147+ // Quick relevance tests (these *should* be over-inclusive a little if anything)
 148+ private function isRelevant( $summary, $whole = true ) {
 149+ # Fixed a bug? Mentioned a config var?
 150+ if ( preg_match( '/\b(bug #?(\d+)|\$[we]g[0-9a-z]{3,50})\b/i', $summary ) )
 151+ return true;
 152+ # Sanity check: summary cannot be *too* short to be useful
 153+ $words = str_word_count( $summary );
 154+ if ( mb_strlen( $summary ) < 40 || $words <= 5 )
 155+ return false;
 156+ # All caps words (like "BREAKING CHANGE"/magic words)?
 157+ if ( preg_match( '/\b[A-Z]{6,30}\b/', $summary ) )
 158+ return true;
 159+ # Random keywords
 160+ if ( preg_match( '/\b(wiki|HTML\d|CSS\d|UTF-?8|(Apache|PHP|CGI|Java|Perl|Python|\w+SQL) ?\d?\.?\d?)\b/i', $summary ) )
 161+ return true;
 162+ # Are we looking at the whole summary or an aspect of it?
 163+ if ( $whole ) {
 164+ return preg_match( '/(^|\n) ?\*/', $summary ); # List of items?
 165+ } else {
 166+ return true;
 167+ }
 168+ }
 169+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeReleaseNotes.php
___________________________________________________________________
Added: svn:eol-style
1170 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionView.php
@@ -0,0 +1,628 @@
 2+<?php
 3+
 4+// Special:Code/MediaWiki/40696
 5+class CodeRevisionView extends CodeView {
 6+
 7+ function __construct( $repoName, $rev, $replyTarget = null ) {
 8+ global $wgRequest;
 9+ parent::__construct();
 10+ $this->mRepo = CodeRepository::newFromName( $repoName );
 11+ $this->mRev = $this->mRepo ? $this->mRepo->getRevision( intval( $rev ) ) : null;
 12+ $this->mPreviewText = false;
 13+ # URL params...
 14+ $this->mAddTags = $wgRequest->getText( 'wpTag' );
 15+ $this->mRemoveTags = $wgRequest->getText( 'wpRemoveTag' );
 16+ $this->mStatus = $wgRequest->getText( 'wpStatus' );
 17+ $this->jumpToNext = $wgRequest->getCheck( 'wpSaveAndNext' );
 18+ $this->mReplyTarget = $replyTarget ?
 19+ (int)$replyTarget : $wgRequest->getIntOrNull( 'wpParent' );
 20+ $this->text = $wgRequest->getText( "wpReply{$this->mReplyTarget}" );
 21+ $this->mSkipCache = ( $wgRequest->getVal( 'action' ) == 'purge' );
 22+ # Make tag arrays
 23+ $this->mAddTags = $this->splitTags( $this->mAddTags );
 24+ $this->mRemoveTags = $this->splitTags( $this->mRemoveTags );
 25+ }
 26+
 27+ function execute() {
 28+ global $wgOut, $wgUser, $wgLang;
 29+ if ( !$this->mRepo ) {
 30+ $view = new CodeRepoListView();
 31+ $view->execute();
 32+ return;
 33+ }
 34+ if ( !$this->mRev ) {
 35+ $view = new CodeRevisionListView( $this->mRepo->getName() );
 36+ $view->execute();
 37+ return;
 38+ }
 39+ if( $this->mStatus == '' )
 40+ $this->mStatus = $this->mRev->getStatus();
 41+
 42+ $redirectOnPost = $this->checkPostings();
 43+ if ( $redirectOnPost ) {
 44+ $wgOut->redirect( $redirectOnPost );
 45+ return;
 46+ }
 47+
 48+ $wgOut->setPageTitle( wfMsgHtml('code-rev-title',$this->mRev->getId()) );
 49+
 50+ $repoLink = $this->mSkin->link( SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() ),
 51+ htmlspecialchars( $this->mRepo->getName() ) );
 52+ $revText = $this->navigationLinks();
 53+ $paths = '';
 54+ $modifiedPaths = $this->mRev->getModifiedPaths();
 55+ foreach ( $modifiedPaths as $row ) {
 56+ $paths .= $this->formatPathLine( $row->cp_path, $row->cp_action );
 57+ }
 58+ if ( $paths ) {
 59+ $paths = "<div class='mw-codereview-paths'><ul>\n$paths</ul></div>\n";
 60+ }
 61+ $comments = $this->formatComments();
 62+ $commentsLink = "";
 63+ if ( $comments ) {
 64+ $commentsLink = " (<a href=\"#code-comments\">" . wfMsgHtml( 'code-comments' ) . "</a>)\n";
 65+ }
 66+ $fields = array(
 67+ 'code-rev-repo' => $repoLink,
 68+ 'code-rev-rev' => $revText,
 69+ 'code-rev-date' => $wgLang->timeanddate( $this->mRev->getTimestamp(), true ),
 70+ 'code-rev-author' => $this->authorLink( $this->mRev->getAuthor() ),
 71+ 'code-rev-status' => $this->statusForm() . $commentsLink,
 72+ 'code-rev-tags' => $this->tagForm(),
 73+ 'code-rev-message' => $this->formatMessage( $this->mRev->getMessage() ),
 74+ 'code-rev-paths' => $paths,
 75+ );
 76+ $special = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mRev->getId() );
 77+
 78+ $html = Xml::openElement( 'form', array( 'action' => $special->getLocalUrl(), 'method' => 'post' ) );
 79+
 80+ if ( $wgUser->isAllowed( 'codereview-post-comment' ) ) {
 81+ $html .= $this->addActionButtons();
 82+ }
 83+
 84+ $html .= $this->formatMetaData( $fields );
 85+ # Show test case info
 86+ $tests = $this->formatTests();
 87+ if( $tests ) {
 88+ $html .= "<h2 id='code-tests'>" . wfMsgHtml( 'code-tests' ) .
 89+ "</h2>\n" . $tests;
 90+ }
 91+ # Output diff
 92+ if ( $this->mRev->isDiffable() ) {
 93+ $diffHtml = $this->formatDiff();
 94+ $html .=
 95+ "<h2>" . wfMsgHtml( 'code-rev-diff' ) .
 96+ ' <small>[' . $this->mSkin->makeLinkObj( $special,
 97+ wfMsg( 'code-rev-purge-link' ), 'action=purge' ) . ']</small></h2>' .
 98+ "<div class='mw-codereview-diff' id='mw-codereview-diff'>" . $diffHtml . "</div>\n";
 99+ $html .= $this->formatImgDiff();
 100+ }
 101+ # Show code relations
 102+ $relations = $this->formatReferences();
 103+ if ( $relations ) {
 104+ $html .= "<h2 id='code-references'>" . wfMsgHtml( 'code-references' ) .
 105+ "</h2>\n" . $relations;
 106+ }
 107+ # Add revision comments
 108+ if ( $comments ) {
 109+ $html .= "<h2 id='code-comments'>" . wfMsgHtml( 'code-comments' ) .
 110+ "</h2>\n" . $comments;
 111+ }
 112+
 113+ if ( $this->mReplyTarget ) {
 114+ global $wgJsMimeType;
 115+ $id = intval( $this->mReplyTarget );
 116+ $html .= "<script type=\"$wgJsMimeType\">addOnloadHook(function(){" .
 117+ "document.getElementById('wpReplyTo$id').focus();" .
 118+ "});</script>\n";
 119+ }
 120+
 121+ if ( $wgUser->isAllowed( 'codereview-post-comment' ) ) {
 122+ $html .= $this->addActionButtons();
 123+ }
 124+
 125+ $changes = $this->formatPropChanges();
 126+ if ( $changes ) {
 127+ $html .= "<h2 id='code-changes'>" . wfMsgHtml( 'code-prop-changes' ) . "</h2>\n" . $changes;
 128+ }
 129+ $html .= xml::closeElement( 'form' );
 130+
 131+ $wgOut->addHTML( $html );
 132+ }
 133+
 134+ protected function navigationLinks() {
 135+ global $wgLang;
 136+
 137+ $rev = $this->mRev->getId();
 138+ $prev = $this->mRev->getPrevious();
 139+ $next = $this->mRev->getNext();
 140+ $repo = $this->mRepo->getName();
 141+
 142+ $links = array();
 143+
 144+ if ( $prev ) {
 145+ $prevTarget = SpecialPage::getTitleFor( 'Code', "$repo/$prev" );
 146+ $links[] = '&lt;&nbsp;' . $this->mSkin->link( $prevTarget, "r$prev" );
 147+ }
 148+
 149+ $revText = "<b>r$rev</b>";
 150+ $viewvc = $this->mRepo->getViewVcBase();
 151+ if ( $viewvc ) {
 152+ $url = htmlspecialchars( "$viewvc/?view=rev&revision=$rev" );
 153+ $viewvcTxt = wfMsgHtml( 'code-rev-rev-viewvc' );
 154+ $revText .= " (<a href=\"$url\" title=\"revision $rev\">$viewvcTxt</a>)";
 155+ }
 156+ $links[] = $revText;
 157+
 158+ if ( $next ) {
 159+ $nextTarget = SpecialPage::getTitleFor( 'Code', "$repo/$next" );
 160+ $links[] = $this->mSkin->link( $nextTarget, "r$next" ) . '&nbsp;&gt;';
 161+ }
 162+
 163+ return $wgLang->pipeList( $links );
 164+ }
 165+
 166+ protected function checkPostings() {
 167+ global $wgRequest, $wgUser;
 168+ if ( $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ) ) {
 169+ // Look for a posting...
 170+ $text = $wgRequest->getText( "wpReply{$this->mReplyTarget}" );
 171+ $parent = $wgRequest->getIntOrNull( 'wpParent' );
 172+ $review = $wgRequest->getInt( 'wpReview' );
 173+ $isPreview = $wgRequest->getCheck( 'wpPreview' );
 174+ if ( $isPreview ) {
 175+ // Save the text for reference on later comment display...
 176+ $this->mPreviewText = $text;
 177+ }
 178+ }
 179+ return false;
 180+ }
 181+
 182+ protected function formatPathLine( $path, $action ) {
 183+ // Uses messages 'code-rev-modified-a', 'code-rev-modified-r', 'code-rev-modified-d', 'code-rev-modified-m'
 184+ $desc = wfMsgHtml( 'code-rev-modified-' . strtolower( $action ) );
 185+ // Find any ' (from x)' from rename comment in the path.
 186+ preg_match( '/ \([^\)]+\)$/', $path, $matches );
 187+ $from = isset( $matches[0] ) ? $matches[0] : '';
 188+ // Remove ' (from x)' from rename comment in the path.
 189+ $path = preg_replace( '/ \([^\)]+\)$/', '', $path );
 190+ $viewvc = $this->mRepo->getViewVcBase();
 191+ $diff = '';
 192+ if ( $viewvc ) {
 193+ $rev = $this->mRev->getId();
 194+ $prev = $rev - 1;
 195+ $safePath = wfUrlEncode( $path );
 196+ if ( $action !== 'D' ) {
 197+ $link = $this->mSkin->makeExternalLink(
 198+ "$viewvc$safePath?view=markup&pathrev=$rev",
 199+ $path . $from );
 200+ } else {
 201+ $link = $safePath;
 202+ }
 203+ if ( $action !== 'A' && $action !== 'D' ) {
 204+ $diff = ' (' .
 205+ $this->mSkin->makeExternalLink(
 206+ "$viewvc$safePath?&pathrev=$rev&r1=$prev&r2=$rev",
 207+ wfMsg( 'code-rev-diff-link' ) ) .
 208+ ')';
 209+ }
 210+ } else {
 211+ $link = $safePath;
 212+ }
 213+ return "<li>$link ($desc)$diff</li>\n";
 214+ }
 215+
 216+ protected function tagForm() {
 217+ global $wgUser;
 218+ $tags = $this->mRev->getTags();
 219+ $list = '';
 220+ if ( count( $tags ) ) {
 221+ $list = implode( ", ",
 222+ array_map(
 223+ array( $this, 'formatTag' ),
 224+ $tags )
 225+ ) . '&nbsp;';
 226+ }
 227+ if ( $wgUser->isAllowed( 'codereview-add-tag' ) ) {
 228+ $list .= $this->addTagForm( $this->mAddTags, $this->mRemoveTags );
 229+ }
 230+ return $list;
 231+ }
 232+
 233+ protected function splitTags( $input ) {
 234+ if ( !$this->mRev ) return array();
 235+ $tags = array_map( 'trim', explode( ",", $input ) );
 236+ foreach ( $tags as $key => $tag ) {
 237+ $normal = $this->mRev->normalizeTag( $tag );
 238+ if ( $normal === false ) {
 239+ return null;
 240+ }
 241+ $tags[$key] = $normal;
 242+ }
 243+ return $tags;
 244+ }
 245+
 246+ static function listTags( $tags ) {
 247+ if ( empty( $tags ) )
 248+ return "";
 249+ return implode( ",", $tags );
 250+ }
 251+
 252+ protected function statusForm() {
 253+ global $wgUser;
 254+ if ( $wgUser->isAllowed( 'codereview-set-status' ) ) {
 255+ $repo = $this->mRepo->getName();
 256+ $rev = $this->mRev->getId();
 257+ return Xml::openElement( 'select', array( 'name' => 'wpStatus' ) ) .
 258+ self::buildStatusList( $this->mStatus, $this ) .
 259+ xml::closeElement( 'select' );
 260+ } else {
 261+ return htmlspecialchars( $this->statusDesc( $this->mRev->getStatus() ) );
 262+ }
 263+ }
 264+
 265+ static function buildStatusList( $status, $view ) {
 266+ $states = CodeRevision::getPossibleStates();
 267+ $out = '';
 268+ foreach ( $states as $state ) {
 269+ $out .= Xml::option( $view->statusDesc( $state ), $state,
 270+ $status === $state );
 271+ }
 272+ return $out;
 273+ }
 274+
 275+ /** Parameters are the tags to be added/removed sent with the request */
 276+ static function addTagForm( $addTags, $removeTags ) {
 277+ global $wgUser;
 278+ return '<div><table><tr><td>' .
 279+ Xml::inputLabel( wfMsg( 'code-rev-tag-add' ), 'wpTag', 'wpTag', 20,
 280+ self::listTags( $addTags ) ) . '</td><td>&nbsp;</td><td>' .
 281+ Xml::inputLabel( wfMsg( 'code-rev-tag-remove' ), 'wpRemoveTag', 'wpRemoveTag', 20,
 282+ self::listTags( $removeTags ) ) . '</td></tr></table></div>';
 283+ }
 284+
 285+ protected function formatTag( $tag ) {
 286+ global $wgUser;
 287+ $repo = $this->mRepo->getName();
 288+ $special = SpecialPage::getTitleFor( 'Code', "$repo/tag/$tag" );
 289+ return $this->mSkin->link( $special, htmlspecialchars( $tag ) );
 290+ }
 291+
 292+ protected function formatTests() {
 293+ $runs = $this->mRev->getTestRuns();
 294+ $html = '';
 295+ if( count( $runs ) ) {
 296+ foreach( $runs as $run ) {
 297+ $html .= "<h3>" . htmlspecialchars( $run->suite->name ) . "</h3>\n";
 298+ if( $run->status == 'complete' ) {
 299+ $total = $run->countTotal;
 300+ $success = $run->countSuccess;
 301+ $failed = $total - $success;
 302+ if( $failed ) {
 303+ $html .= "<p><span class='mw-codereview-success'>$success</span> succeeded tests, " .
 304+ "<span class='mw-codereview-fail'>$failed</span> failed tests:</p>";
 305+
 306+ $tests = $run->getResults( false );
 307+ $html .= "<ul>\n";
 308+ foreach( $tests as $test ) {
 309+ $html .= "<li>" . htmlspecialchars( $test->caseName ) . "</li>\n";
 310+ }
 311+ $html .= "</ul>\n";
 312+ } else {
 313+ $html .= "<p><span class='mw-codereview-success'>$success</span> succeeded tests.</p>";
 314+
 315+ }
 316+ } elseif( $run->status == "running" ) {
 317+ $html .= "<p>Test cases are running...</p>";
 318+ } elseif( $run->status == "abort" ) {
 319+ $html .= "<p>Test run aborted.</p>";
 320+ } else {
 321+ // Err, this shouldn't happen?
 322+ }
 323+ }
 324+ }
 325+ return $html;
 326+ }
 327+
 328+ protected function formatDiff() {
 329+ global $wgEnableAPI;
 330+
 331+ // Asynchronous diff loads will require the API
 332+ // And JS in the client, but tough shit eh? ;)
 333+ $deferDiffs = $wgEnableAPI;
 334+
 335+ if ( $this->mSkipCache ) {
 336+ // We're purging the cache on purpose, probably
 337+ // because the cached data was corrupt.
 338+ $cache = 'skipcache';
 339+ } elseif ( $deferDiffs ) {
 340+ // If data is already cached, we'll take it now;
 341+ // otherwise defer the load to an AJAX request.
 342+ // This lets the page be manipulable even if the
 343+ // SVN connection is slow or uncooperative.
 344+ $cache = 'cached';
 345+ } else {
 346+ $cache = '';
 347+ }
 348+ $diff = $this->mRepo->getDiff( $this->mRev->getId(), $cache );
 349+ if ( !$diff && $deferDiffs ) {
 350+ // We'll try loading it by AJAX...
 351+ return $this->stubDiffLoader();
 352+ } else {
 353+ $hilite = new CodeDiffHighlighter();
 354+ return $hilite->render( $diff );
 355+ }
 356+ }
 357+
 358+ protected function formatImgDiff() {
 359+ global $wgCodeReviewImgRegex;
 360+ // Get image diffs
 361+ $imgDiffs = $html = '';
 362+ $modifiedPaths = $this->mRev->getModifiedPaths();
 363+ foreach ( $modifiedPaths as $row ) {
 364+ // Typical image file?
 365+ if( preg_match($wgCodeReviewImgRegex,$row->cp_path) ) {
 366+ $imgDiffs .= 'Index: '.htmlspecialchars( $row->cp_path )."\n";
 367+ $imgDiffs .= '<table border="1px" style="background:white;"><tr>';
 368+ if( $row->cp_action !== 'A' ) { // old
 369+ // What was done to it?
 370+ $action = $row->cp_action == 'D' ? 'code-rev-modified-d' : 'code-rev-modified-r';
 371+ // Link to old image
 372+ $imgDiffs .= $this->formatImgCell( $row->cp_path, $this->mRev->getPrevious(), $action );
 373+ }
 374+ if( $row->cp_action !== 'D' ) { // new
 375+ // What was done to it?
 376+ $action = $row->cp_action == 'A' ? 'code-rev-modified-a' : 'code-rev-modified-m';
 377+ // Link to new image
 378+ $imgDiffs .= $this->formatImgCell( $row->cp_path, $this->mRev->getId(), $action );
 379+ }
 380+ $imgDiffs .= "</tr></table>\n";
 381+ }
 382+ }
 383+ if( $imgDiffs ) {
 384+ $html = '<h2>'.wfMsgHtml('code-rev-imagediff').'</h2>';
 385+ $html .= "<div class='mw-codereview-imgdiff'>$imgDiffs</div>\n";
 386+ }
 387+ return $html;
 388+ }
 389+
 390+ protected function formatImgCell( $path, $rev, $message ) {
 391+ $viewvc = $this->mRepo->getViewVcBase();
 392+ $safePath = wfUrlEncode( $path );
 393+ $url = "{$viewvc}{$safePath}?&pathrev=$rev&revision=$rev";
 394+
 395+ $alt = wfMsg( $message );
 396+
 397+ return Xml::tags( 'td',
 398+ array(),
 399+ Xml::tags( 'a',
 400+ array( 'href' => $url ),
 401+ Xml::element( 'img',
 402+ array(
 403+ 'src' => $url,
 404+ 'alt' => $alt,
 405+ 'title' => $alt,
 406+ 'border' => '0' ) ) ) );
 407+ }
 408+
 409+ protected function stubDiffLoader() {
 410+ global $wgOut, $wgScriptPath, $wgCodeReviewStyleVersion;
 411+ $encRepo = Xml::encodeJsVar( $this->mRepo->getName() );
 412+ $encRev = Xml::encodeJsVar( $this->mRev->getId() );
 413+ $wgOut->addScriptFile( "$wgScriptPath/extensions/CodeReview/codereview.js?$wgCodeReviewStyleVersion" );
 414+ $wgOut->addInlineScript(
 415+ "addOnloadHook(
 416+ function() {
 417+ CodeReview.loadDiff($encRepo,$encRev);
 418+ }
 419+ );" );
 420+ return wfMsg( 'code-load-diff' );
 421+ }
 422+
 423+ protected function formatComments() {
 424+ $comments = implode( "\n",
 425+ array_map( array( $this, 'formatCommentInline' ), $this->mRev->getComments() )
 426+ );
 427+ if ( !$this->mReplyTarget ) {
 428+ $comments .= $this->postCommentForm();
 429+ }
 430+ if ( !$comments ) {
 431+ return false;
 432+ }
 433+ return "<div class='mw-codereview-comments'>$comments</div>";
 434+ }
 435+
 436+ protected function formatPropChanges() {
 437+ $changes = implode( "\n",
 438+ array_map( array( $this, 'formatChangeInline' ), $this->mRev->getPropChanges() )
 439+ );
 440+ if ( !$changes ) {
 441+ return false;
 442+ }
 443+ return "<ul class='mw-codereview-changes'>$changes</ul>";
 444+ }
 445+
 446+ protected function formatReferences() {
 447+ $refs = implode( "\n",
 448+ array_map( array( $this, 'formatReferenceInline' ), $this->mRev->getReferences() )
 449+ );
 450+ if ( !$refs ) {
 451+ return false;
 452+ }
 453+ $header = '<th>'.wfMsg( 'code-field-id' ).'</th>';
 454+ $header .= '<th>'.wfMsg( 'code-field-message' ) .'</th>';
 455+ $header .= '<th>'.wfMsg( 'code-field-author' ).'</th>';
 456+ $header .= '<th>'.wfMsg( 'code-field-timestamp' ).'</th>';
 457+ return "<table border='1' class='TablePager'><tr>{$header}</tr>{$refs}</table>";
 458+ }
 459+
 460+ protected function formatCommentInline( $comment ) {
 461+ if ( $comment->id === $this->mReplyTarget ) {
 462+ return $this->formatComment( $comment,
 463+ $this->postCommentForm( $comment->id ) );
 464+ } else {
 465+ return $this->formatComment( $comment );
 466+ }
 467+ }
 468+
 469+ protected function formatChangeInline( $change ) {
 470+ global $wgLang;
 471+ $revId = $change->rev->getId();
 472+ $line = $wgLang->timeanddate( $change->timestamp, true );
 473+ $line .= '&nbsp;' . $this->mSkin->userLink( $change->user, $change->userText );
 474+ $line .= $this->mSkin->userToolLinks( $change->user, $change->userText );
 475+ // Uses messages 'code-change-status', 'code-change-tags'
 476+ $line .= '&nbsp;' . wfMsgExt( "code-change-{$change->attrib}", 'parseinline', $revId );
 477+ $line .= " <i>[";
 478+ // Items that were changed or set...
 479+ if ( $change->removed ) {
 480+ $line .= '<b>' . wfMsg( 'code-change-removed' ) . '</b> ';
 481+ // Status changes...
 482+ if( $change->attrib == 'status' ) {
 483+ $line .= wfMsgHtml( 'code-status-'.$change->removed );
 484+ $line .= $change->added ? "&nbsp;" : ""; // spacing
 485+ // Tag changes
 486+ } else if( $change->attrib == 'tags' ) {
 487+ $line .= htmlspecialchars( $change->removed );
 488+ $line .= $change->added ? "&nbsp;" : ""; // spacing
 489+ }
 490+ }
 491+ // Items that were changed to something else...
 492+ if ( $change->added ) {
 493+ $line .= '<b>' . wfMsg( 'code-change-added' ) . '</b> ';
 494+ // Status changes...
 495+ if( $change->attrib == 'status' ) {
 496+ $line .= wfMsgHtml( 'code-status-'.$change->added );
 497+ // Tag changes...
 498+ } else {
 499+ $line .= htmlspecialchars( $change->added );
 500+ }
 501+ }
 502+ $line .= "]</i>";
 503+ return "<li>$line</li>";
 504+ }
 505+
 506+ protected function formatReferenceInline( $row ) {
 507+ global $wgLang;
 508+ $rev = intval( $row->cr_id );
 509+ $repo = $this->mRepo->getName();
 510+ // Borrow the code revision list css
 511+ $css = 'mw-codereview-status-' . htmlspecialchars( $row->cr_status );
 512+ $date = $wgLang->timeanddate( $row->cr_timestamp, true );
 513+ $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" );
 514+ $revLink = $this->mSkin->link( $title, "r$rev" );
 515+ $summary = $this->messageFragment( $row->cr_message );
 516+ $author = $this->authorLink( $row->cr_author );
 517+ return "<tr class='$css'><td>$revLink</td><td>$summary</td><td>$author</td><td>$date</td></tr>";
 518+ }
 519+
 520+ protected function commentLink( $commentId ) {
 521+ $repo = $this->mRepo->getName();
 522+ $rev = $this->mRev->getId();
 523+ $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" );
 524+ $title->setFragment( "#c{$commentId}" );
 525+ return $title;
 526+ }
 527+
 528+ protected function revLink() {
 529+ $repo = $this->mRepo->getName();
 530+ $rev = $this->mRev->getId();
 531+ $title = SpecialPage::getTitleFor( 'Code', "$repo/$rev" );
 532+ return $title;
 533+ }
 534+
 535+ protected function previewComment( $text, $review = 0 ) {
 536+ $comment = $this->mRev->previewComment( $text, $review );
 537+ return $this->formatComment( $comment );
 538+ }
 539+
 540+ protected function formatComment( $comment, $replyForm = '' ) {
 541+ global $wgOut, $wgLang;
 542+ $linker = new CodeCommentLinkerWiki( $this->mRepo );
 543+
 544+ if ( $comment->id === null ) {
 545+ $linkId = 'cpreview';
 546+ $permaLink = "<b>Preview:</b> ";
 547+ } else {
 548+ $linkId = 'c' . intval( $comment->id );
 549+ $permaLink = $this->mSkin->link( $this->commentLink( $comment->id ), "#" );
 550+ }
 551+
 552+ return Xml::openElement( 'div',
 553+ array(
 554+ 'class' => 'mw-codereview-comment',
 555+ 'id' => $linkId,
 556+ 'style' => $this->commentStyle( $comment ) ) ) .
 557+ '<div class="mw-codereview-comment-meta">' .
 558+ $permaLink .
 559+ wfMsgHtml( 'code-rev-comment-by',
 560+ $this->mSkin->userLink( $comment->user, $comment->userText ) .
 561+ $this->mSkin->userToolLinks( $comment->user, $comment->userText ) ) .
 562+ ' &nbsp; ' .
 563+ $wgLang->timeanddate( $comment->timestamp, true ) .
 564+ ' ' .
 565+ $this->commentReplyLink( $comment->id ) .
 566+ '</div>' .
 567+ '<div class="mw-codereview-comment-text">' .
 568+ $wgOut->parse( $linker->link( $comment->text ) ) .
 569+ '</div>' .
 570+ $replyForm .
 571+ '</div>';
 572+ }
 573+
 574+ protected function commentStyle( $comment ) {
 575+ $depth = $comment->threadDepth();
 576+ $margin = ( $depth - 1 ) * 48;
 577+ return "margin-left: ${margin}px";
 578+ }
 579+
 580+ protected function commentReplyLink( $id ) {
 581+ global $wgUser;
 582+ if ( !$wgUser->isAllowed( 'codereview-post-comment' ) ) return '';
 583+ $repo = $this->mRepo->getName();
 584+ $rev = $this->mRev->getId();
 585+ $self = SpecialPage::getTitleFor( 'Code', "$repo/$rev/reply/$id" );
 586+ $self->setFragment( "#c$id" );
 587+ return '[' . $this->mSkin->link( $self, wfMsg( 'codereview-reply-link' ) ) . ']';
 588+ }
 589+
 590+ protected function postCommentForm( $parent = null ) {
 591+ global $wgUser;
 592+ if ( $this->mPreviewText !== false && $parent === $this->mReplyTarget ) {
 593+ $preview = $this->previewComment( $this->mPreviewText );
 594+ $text = htmlspecialchars( $this->mPreviewText );
 595+ } else {
 596+ $preview = '';
 597+ $text = $this->text;
 598+ }
 599+ $repo = $this->mRepo->getName();
 600+ $rev = $this->mRev->getId();
 601+ if ( !$wgUser->isAllowed( 'codereview-post-comment' ) ) {
 602+ return '';
 603+ }
 604+ return '<div class="mw-codereview-post-comment">' .
 605+ $preview .
 606+ Xml::hidden( 'wpEditToken', $wgUser->editToken() ) .
 607+ ( $parent ? Xml::hidden( 'wpParent', $parent ) : '' ) .
 608+ '<div>' .
 609+ Xml::openElement( 'textarea', array(
 610+ 'name' => "wpReply{$parent}",
 611+ 'id' => "wpReplyTo{$parent}",
 612+ 'cols' => 40,
 613+ 'rows' => 5 ) ) .
 614+ $text .
 615+ '</textarea>' .
 616+ '</div>' .
 617+ '</div>';
 618+ }
 619+
 620+ protected function addActionButtons() {
 621+ return '<div>' .
 622+ Xml::submitButton( wfMsg( 'code-rev-submit' ), array( 'name' => 'wpSave' ) ) .
 623+ ' ' .
 624+ Xml::submitButton( wfMsg( 'code-rev-submit-next' ), array( 'name' => 'wpSaveAndNext' ) ) .
 625+ ' ' .
 626+ Xml::submitButton( wfMsg( 'code-rev-comment-preview' ), array( 'name' => 'wpPreview' ) ) .
 627+ '</div>';
 628+ }
 629+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionView.php
___________________________________________________________________
Added: svn:eol-style
1630 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRepoListView.php
@@ -0,0 +1,34 @@
 2+<?php
 3+
 4+// Special:Code
 5+class CodeRepoListView {
 6+
 7+ function execute() {
 8+ global $wgOut;
 9+ $repos = CodeRepository::getRepoList();
 10+ if ( !count( $repos ) ) {
 11+ $wgOut->addWikiMsg( 'code-no-repo' );
 12+ return;
 13+ }
 14+ $text = '';
 15+ foreach ( $repos as $repo ) {
 16+ $name = $repo->getName();
 17+ $text .= "* " . self::getNavItem( $name ) . "\n";
 18+ }
 19+ $wgOut->addWikiText( $text );
 20+ }
 21+
 22+ public static function getNavItem( $name ) {
 23+ global $wgLang;
 24+ $text = "'''[[Special:Code/$name|$name]]''' (";
 25+ $links[] = "[[Special:Code/$name/comments|" . wfMsgHtml( 'code-notes' ) . "]]";
 26+ $links[] = "[[Special:Code/$name/statuschanges|" . wfMsgHtml( 'code-statuschanges' ) . "]]";
 27+ $links[] = "[[Special:Code/$name/tag|" . wfMsgHtml( 'code-tags' ) . "]]";
 28+ $links[] = "[[Special:Code/$name/author|" . wfMsgHtml( 'code-authors' ) . "]]";
 29+ $links[] = "[[Special:Code/$name/status|" . wfMsgHtml( 'code-status' ) . "]]";
 30+ $links[] = "[[Special:Code/$name/releasenotes|" . wfMsgHtml( 'code-releasenotes' ) . "]]";
 31+ $text .= $wgLang->pipeList( $links );
 32+ $text .= ")";
 33+ return $text;
 34+ }
 35+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRepoListView.php
___________________________________________________________________
Added: svn:eol-style
136 + native
Index: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionStatusView.php
@@ -0,0 +1,30 @@
 2+<?php
 3+
 4+class CodeRevisionStatusView extends CodeRevisionListView {
 5+ function __construct( $repoName, $status ) {
 6+ parent::__construct( $repoName );
 7+ $this->mStatus = $status;
 8+ }
 9+
 10+ function getPager() {
 11+ return new SvnRevStatusTablePager( $this, $this->mStatus );
 12+ }
 13+}
 14+
 15+class SvnRevStatusTablePager extends SvnRevTablePager {
 16+ function __construct( $view, $status ) {
 17+ parent::__construct( $view );
 18+ $this->mStatus = $status;
 19+ }
 20+
 21+ function getQueryInfo() {
 22+ $info = parent::getQueryInfo();
 23+ $info['conds']['cr_status'] = $this->mStatus; // FIXME: normalize input?
 24+ return $info;
 25+ }
 26+
 27+ function getTitle() {
 28+ $repo = $this->mRepo->getName();
 29+ return SpecialPage::getTitleFor( 'Code', "$repo/status/$this->mStatus" );
 30+ }
 31+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/ui/CodeRevisionStatusView.php
___________________________________________________________________
Added: svn:eol-style
132 + native
Index: branches/wmf-deployment/extensions/CodeReview/CodeReview.i18n.php
@@ -17,14 +17,18 @@
1818 'code-change-tags' => 'changed the \'\'\'tags\'\'\' for r$1',
1919 'code-change-removed' => 'removed:',
2020 'code-change-added' => 'added:',
 21+ 'code-old-status' => 'Old status',
 22+ 'code-new-status' => 'New status',
2123 'code-prop-changes' => 'Status & tagging log',
2224 'code-desc' => '[[Special:Code|Code review tool]] with [[Special:RepoAdmin|Subversion support]]',
2325 'code-no-repo' => 'No repository configured!',
2426 'code-load-diff' => 'Loading diff…',
2527 'code-notes' => 'recent comments',
 28+ 'code-statuschanges' => 'status changes',
2629 'code-authors' => 'authors',
27 - 'code-status' => 'status',
 30+ 'code-status' => 'states',
2831 'code-tags' => 'tags',
 32+ 'code-tests' => 'Test cases',
2933 'code-authors-text' => 'Below is a list of repo authors in order of recent commits.',
3034 'code-author-haslink' => 'This author is linked to the wiki user $1',
3135 'code-author-orphan' => 'This author has no link with a wiki account',
@@ -43,6 +47,7 @@
4448 'code-field-status' => 'Status',
4549 'code-field-timestamp' => 'Date',
4650 'code-field-comments' => 'Notes',
 51+ 'code-field-tests' => 'Tests',
4752 'code-field-path' => 'Path',
4853 'code-field-text' => 'Note',
4954 'code-field-select' => 'Select',
@@ -149,11 +154,10 @@
150155 */
151156 $messages['qqq'] = array(
152157 'code-comments' => '{{Identical|Comments}}',
153 - 'code-desc' => 'Short description of the Code extension, shown on [[Special:Version]]',
 158+ 'code-desc' => '{{desc}}',
154159 'code-field-id' => '{{Identical|Revision}}',
155160 'code-field-author' => '{{Identical|Author}}',
156 - 'code-field-message' => 'This is probably a noun instead of verb, a column header.
157 -{{Identical|Comment}}',
 161+ 'code-field-message' => 'This is probably a noun instead of verb, a column header.',
158162 'code-field-status' => '{{Identical|Status}}',
159163 'code-field-timestamp' => '{{Identical|Date}}',
160164 'code-field-comments' => '{{Identical|Notes}}',
@@ -587,7 +591,7 @@
588592 'code-load-diff' => 'Загрузка розьніцы…',
589593 'code-notes' => 'апошнія камэнтары',
590594 'code-authors' => 'аўтары',
591 - 'code-status' => 'статус',
 595+ 'code-status' => 'станы',
592596 'code-tags' => 'тэгі',
593597 'code-authors-text' => 'Ніжэй пададзены сьпіс аўтараў коду ў сховішчы ў парадку апошніх зьменаў.',
594598 'code-author-haslink' => 'Гэты аўтар асацыяваны з удзельнікам $1',
@@ -709,6 +713,7 @@
710714 'code-field-comments' => 'Бележки',
711715 'code-field-path' => 'Път',
712716 'code-field-text' => 'Бележка',
 717+ 'code-field-select' => 'Избиране',
713718 'code-rev-author' => 'Автор:',
714719 'code-rev-date' => 'Дата:',
715720 'code-rev-message' => 'Коментар:',
@@ -764,14 +769,18 @@
765770 'code-change-tags' => "promijenjeni ''tagovi'' za r$1",
766771 'code-change-removed' => 'uklonjeno:',
767772 'code-change-added' => 'dodano:',
 773+ 'code-old-status' => 'Staro stanje',
 774+ 'code-new-status' => 'Novo stanje',
768775 'code-prop-changes' => 'Zapis stanja i oznaka',
769776 'code-desc' => '[[Special:Code|Alat za provjeru koda]] sa [[Special:RepoAdmin|podrškom za subverzije]]',
770777 'code-no-repo' => 'Nijedan repozitorijum nije konfigurisan!',
771778 'code-load-diff' => 'Punim diff...',
772779 'code-notes' => 'nedavni komentari',
 780+ 'code-statuschanges' => 'izmjene stanja',
773781 'code-authors' => 'autori',
774 - 'code-status' => 'status',
 782+ 'code-status' => 'statusi',
775783 'code-tags' => 'oznake',
 784+ 'code-tests' => 'Probni slučajevi',
776785 'code-authors-text' => 'Ispod je spisak autora repozitorijuma poredanih po nedavnim doprinosima.',
777786 'code-author-haslink' => 'Ovaj autor je povezan wiki korisničkim računom $1',
778787 'code-author-orphan' => 'Ovaj autor nije povezan sa wiki računom',
@@ -790,6 +799,7 @@
791800 'code-field-status' => 'Stanje',
792801 'code-field-timestamp' => 'Datum',
793802 'code-field-comments' => 'Bilješke',
 803+ 'code-field-tests' => 'Probe',
794804 'code-field-path' => 'Putanja',
795805 'code-field-text' => 'Bilješka',
796806 'code-field-select' => 'Odaberi',
@@ -915,13 +925,16 @@
916926 'code-change-tags' => "změnil '''značky''' revize $1",
917927 'code-change-removed' => 'odebráno:',
918928 'code-change-added' => 'přidáno:',
 929+ 'code-old-status' => 'Původní stav',
 930+ 'code-new-status' => 'Nový stav',
919931 'code-prop-changes' => 'Záznam změn stavu a značek',
920932 'code-desc' => '[[Special:Code|Nástroj pro kontrolu kódu]] s [[Special:RepoAdmin|podporou Subversion]]',
921933 'code-no-repo' => 'Nebylo nastaveno žádné úložiště!',
922934 'code-load-diff' => 'Nahrávám diff…',
923935 'code-notes' => 'nedávné poznámky',
 936+ 'code-statuschanges' => 'změny stavu',
924937 'code-authors' => 'autoři',
925 - 'code-status' => 'stav',
 938+ 'code-status' => 'stavy',
926939 'code-tags' => 'značky',
927940 'code-authors-text' => 'Toto je seznam autorů v úložišti seřazený podle posledních commitů.',
928941 'code-author-haslink' => 'Tento autor je spojen s wiki uživatelem $1',
@@ -1038,14 +1051,18 @@
10391052 'code-change-tags' => "änderte die '''Tags''' für Revision $1",
10401053 'code-change-removed' => 'entfernt:',
10411054 'code-change-added' => 'hinzugefügt:',
 1055+ 'code-old-status' => 'Alter Status',
 1056+ 'code-new-status' => 'Neuer Status',
10421057 'code-prop-changes' => 'Status- und Tagging-Logbuch',
10431058 'code-desc' => '[[Special:Code|Codeprüfungs-Werkzeug]] mit [[Special:RepoAdmin|Subversion-Unterstützung]]',
10441059 'code-no-repo' => 'Kein Repositorium konfiguriert.',
10451060 'code-load-diff' => 'Lade Diff …',
10461061 'code-notes' => 'Jüngste Prüfnotizen',
 1062+ 'code-statuschanges' => 'Änderung des Status',
10471063 'code-authors' => 'Autoren',
10481064 'code-status' => 'Status',
10491065 'code-tags' => 'Tags',
 1066+ 'code-tests' => 'Testfälle',
10501067 'code-authors-text' => 'Dies ist die Liste der Autoren in der Reihefolge der Einspielungen.',
10511068 'code-author-haslink' => 'Dieser Autor ist mit dem Wiki-Benutzer $1 verlinkt',
10521069 'code-author-orphan' => 'Dieser Autor hat keinen Link zu einem Wiki-Benutzerkonto',
@@ -1064,6 +1081,7 @@
10651082 'code-field-status' => 'Status',
10661083 'code-field-timestamp' => 'Datum',
10671084 'code-field-comments' => 'Notizen',
 1085+ 'code-field-tests' => 'Tests',
10681086 'code-field-path' => 'Pfad',
10691087 'code-field-text' => 'Notiz',
10701088 'code-field-select' => 'Auswählen',
@@ -1159,14 +1177,18 @@
11601178 'code-change-tags' => "jo '''toflicki''' za wersiju r$1 změnił",
11611179 'code-change-removed' => 'wótpórany:',
11621180 'code-change-added' => 'pśidany:',
 1181+ 'code-old-status' => 'Stary status',
 1182+ 'code-new-status' => 'Nowy status',
11631183 'code-prop-changes' => 'Protokol statusa & toflickow',
11641184 'code-desc' => '[[Special:Code|Rěd za kodowu kontrolu]] z [[Special:RepoAdmin|pódpěru za Subversion]]',
11651185 'code-no-repo' => 'Žeden repozitorium konfigurěrowany!',
11661186 'code-load-diff' => 'Rozdźěl se zacytujo...',
11671187 'code-notes' => 'aktualne komentary',
 1188+ 'code-statuschanges' => 'změny statusa',
11681189 'code-authors' => 'awtory',
1169 - 'code-status' => 'status',
 1190+ 'code-status' => 'statusy',
11701191 'code-tags' => 'toflicki',
 1192+ 'code-tests' => 'Testowe pady',
11711193 'code-authors-text' => 'to jo lisćina awtorow repozitoriuma w rěźe aktualnych nagraśow.',
11721194 'code-author-haslink' => 'Awtor jo z wikijowym wužywarjom $1 zwězany',
11731195 'code-author-orphan' => 'Toś ten awtor njama wótkaz k wikijowemu kontoju',
@@ -1185,6 +1207,7 @@
11861208 'code-field-status' => 'Status',
11871209 'code-field-timestamp' => 'Datum',
11881210 'code-field-comments' => 'Pśipiski',
 1211+ 'code-field-tests' => 'Testy',
11891212 'code-field-path' => 'Sćažka',
11901213 'code-field-text' => 'Pśipisk',
11911214 'code-field-select' => 'Wubraś',
@@ -1817,6 +1840,7 @@
18181841 );
18191842
18201843 /** Finnish (Suomi)
 1844+ * @author Cimon Avaro
18211845 * @author Crt
18221846 * @author Nike
18231847 * @author Str4nd
@@ -1824,19 +1848,25 @@
18251849 */
18261850 $messages['fi'] = array(
18271851 'code' => 'Koodintarkistus',
 1852+ 'code-rev-title' => 'r$1 – Koodintarkistus',
18281853 'code-comments' => 'Kommentit',
 1854+ 'code-references' => 'Myöhemmät versiot',
18291855 'code-change-status' => "muutti version $1 '''tilaa'''",
18301856 'code-change-tags' => "muutti version $1 '''merkintöjä'''",
18311857 'code-change-removed' => 'poistettu:',
18321858 'code-change-added' => 'lisätty:',
 1859+ 'code-old-status' => 'Vanha tila',
 1860+ 'code-new-status' => 'Uusi tila',
18331861 'code-prop-changes' => 'Tila- ja merkintäloki',
18341862 'code-desc' => '[[Special:Code|Koodintarkistustyökalu]], jossa [[Special:RepoAdmin|Subversion-tuki]].',
18351863 'code-no-repo' => 'Varastoa ei ole määritetty!',
18361864 'code-load-diff' => 'Ladataan eroavaisuuksia…',
18371865 'code-notes' => 'tuoreet kommentit',
 1866+ 'code-statuschanges' => 'tilan muutokset',
18381867 'code-authors' => 'tekijät',
1839 - 'code-status' => 'tila',
 1868+ 'code-status' => 'tilat',
18401869 'code-tags' => 'merkinnät',
 1870+ 'code-tests' => 'Kokeilu tapauksia',
18411871 'code-authors-text' => 'Alla on luettelo varastoon kirjoittaneista viimeisimpien lisäysten mukaisessa järjestyksessä.',
18421872 'code-author-haslink' => 'Tämä tekijä on kytketty wikikäyttäjään $1',
18431873 'code-author-orphan' => 'Tätä tekijää ei ole kytketty wiki-tunnukseen',
@@ -1855,6 +1885,7 @@
18561886 'code-field-status' => 'Tila',
18571887 'code-field-timestamp' => 'Päiväys',
18581888 'code-field-comments' => 'Huomiot',
 1889+ 'code-field-tests' => 'Testit',
18591890 'code-field-path' => 'Polku',
18601891 'code-field-text' => 'Huomio',
18611892 'code-field-select' => 'Valitse',
@@ -1869,6 +1900,7 @@
18701901 'code-rev-modified-r' => 'korvattu',
18711902 'code-rev-modified-d' => 'poistettu',
18721903 'code-rev-modified-m' => 'muutettu',
 1904+ 'code-rev-imagediff' => 'Kuvamuutokset',
18731905 'code-rev-status' => 'Tila:',
18741906 'code-rev-status-set' => 'Vaihda tilaa',
18751907 'code-rev-tags' => 'Merkinnät:',
@@ -1891,7 +1923,15 @@
18921924 'code-pathsearch-path' => 'Polku',
18931925 'code-rev-submit' => 'Tallenna muutokset',
18941926 'code-rev-submit-next' => 'Tallenna ja seuraava avoin',
 1927+ 'code-batch-status' => 'Muuta tilaa',
 1928+ 'code-batch-tags' => 'Muuta tägejä',
 1929+ 'codereview-batch-title' => 'Muuta kaikkia valittuja versioita',
 1930+ 'codereview-batch-submit' => 'Lähetä',
18951931 'code-releasenotes' => 'julkaisutiedot',
 1932+ 'code-release-legend' => 'Luo julkaisuhuomautukset',
 1933+ 'code-release-startrev' => 'Ensimmäinen versio',
 1934+ 'code-release-endrev' => 'Viimeinen versio',
 1935+ 'codereview-subtitle' => 'Varastolle $1',
18961936 'codereview-reply-link' => 'vastaa',
18971937 'codereview-email-subj' => '[$1] [r$2]: Uusi kommentti lisätty',
18981938 'codereview-email-body' => 'Käyttäjä $1 jätti kommentin versioon r$3.
@@ -1901,6 +1941,14 @@
19021942 Kommentti:
19031943
19041944 $4',
 1945+ 'codereview-email-subj2' => '[$1] [r$2]: Myöhemmät muutokset',
 1946+ 'codereview-email-body2' => 'Käyttäjä ”$1” teki myöhemmän muutoksen versioon r$2.
 1947+
 1948+Täydellinen URL: $3
 1949+
 1950+Toimituksen yhteenveto:
 1951+
 1952+$4',
19051953 'repoadmin' => 'Varaston hallinta',
19061954 'repoadmin-new-legend' => 'Luo uusi varasto',
19071955 'repoadmin-new-label' => 'Varaston nimi:',
@@ -1912,6 +1960,7 @@
19131961 'repoadmin-edit-button' => 'OK',
19141962 'repoadmin-edit-sucess' => 'Muutokset varastoon [[Special:Code/$1|$1]] on tehty.',
19151963 'right-repoadmin' => 'Hallita koodivarastoja',
 1964+ 'right-codereview-use' => 'Special:Code:n käyttö',
19161965 'right-codereview-add-tag' => 'Lisätä uusia merkintöjä versioihin',
19171966 'right-codereview-remove-tag' => 'Poistaa merkintöjä versioista',
19181967 'right-codereview-post-comment' => 'Lisätä kommentteja versioihin',
@@ -1939,13 +1988,16 @@
19401989 'code-change-tags' => "a modifié les '''balises''' de r$1",
19411990 'code-change-removed' => 'retiré :',
19421991 'code-change-added' => 'ajouté :',
 1992+ 'code-old-status' => 'ancien statut',
 1993+ 'code-new-status' => 'nouveau statut',
19431994 'code-prop-changes' => 'Journal des états et du balisage',
19441995 'code-desc' => '[[Special:Code|Outils de révision du code]] avec le [[Special:RepoAdmin|support de Subversion]]',
19451996 'code-no-repo' => 'Pas de dépôt configuré !',
19461997 'code-load-diff' => 'Chargement du diff en cours...',
19471998 'code-notes' => 'commentaires récents',
 1999+ 'code-statuschanges' => 'modifications de statut',
19482000 'code-authors' => 'auteurs',
1949 - 'code-status' => 'état',
 2001+ 'code-status' => 'états',
19502002 'code-tags' => 'balises',
19512003 'code-authors-text' => 'Ci-dessous se trouve une liste des auteurs de dépôts dans l’ordre des publications récentes.',
19522004 'code-author-haslink' => 'Cet auteur est lié au compte $1 de ce wiki',
@@ -2076,14 +2128,18 @@
20772129 'code-change-tags' => "cambiou as '''etiquetas''' da r$1",
20782130 'code-change-removed' => 'eliminado:',
20792131 'code-change-added' => 'engadido:',
 2132+ 'code-old-status' => 'Estado vello',
 2133+ 'code-new-status' => 'Novo estado',
20802134 'code-prop-changes' => 'Estado e rexistro de etiquetas',
20812135 'code-desc' => '[[Special:Code|Ferramenta de revisión do código]] con [[Special:RepoAdmin|apoio da subversión]]',
20822136 'code-no-repo' => 'Non hai ningún repositorio configurado!',
20832137 'code-load-diff' => 'Cargando as diferenzas…',
20842138 'code-notes' => 'comentarios recentes',
 2139+ 'code-statuschanges' => 'cambios de estado',
20852140 'code-authors' => 'autores',
2086 - 'code-status' => 'estado',
 2141+ 'code-status' => 'estados',
20872142 'code-tags' => 'etiquetas',
 2143+ 'code-tests' => 'Casos de proba',
20882144 'code-authors-text' => 'Embaixo hai unha lista dos autores das respostas por orde de tarefas recentes.',
20892145 'code-author-haslink' => 'O autor é ligado co usuario do wiki chamado $1',
20902146 'code-author-orphan' => 'Este autor non ten ningunha ligazón con algunha conta do wiki',
@@ -2102,6 +2158,7 @@
21032159 'code-field-status' => 'Estado',
21042160 'code-field-timestamp' => 'Data',
21052161 'code-field-comments' => 'Notas',
 2162+ 'code-field-tests' => 'Probas',
21062163 'code-field-path' => 'Ruta',
21072164 'code-field-text' => 'Nota',
21082165 'code-field-select' => 'Seleccionar',
@@ -2205,13 +2262,13 @@
22062263 'code-status' => 'κατάστασις',
22072264 'code-tags' => 'προσαρτήματα',
22082265 'code-authors-text' => 'Κάτωθι ἐστὶ καταλογή τις δημιουργῶν τῆς ἀποθήκης κατατάξει τῶν πλείω προσφάτων καταθέσεων.',
2209 - 'code-author-haslink' => 'Ὅδε ὁ δημιουργὸς συνδεδεμένος ἐστὶ μετὰ τοῦ ϝίκι-χρωμένου $1',
2210 - 'code-author-orphan' => 'Ὅδε ὁ χρώμενος οὐκ ἔχει σύνδεσμον μετὰ ϝίκι-λογισμοῦ τινός',
2211 - 'code-author-dolink' => 'Συνδεῖσθαι τόνδε τὸν δημιουργὸν μετὰ ϝικι-χρωμένου τινὸς:',
2212 - 'code-author-alterlink' => 'Ἀλλάττειν ϝίκι-χρώμενον τὸν συνδεδεμένον μετὰ τοῦδε τοῦ χρωμένου:',
 2266+ 'code-author-haslink' => 'Ὅδε ὁ δημιουργὸς συνδεδεμένος ἐστὶ μετὰ τοῦ βικι-χρωμένου $1',
 2267+ 'code-author-orphan' => 'Ὅδε ὁ χρώμενος οὐκ ἔχει σύνδεσμον μετὰ βικι-λογισμοῦ τινός',
 2268+ 'code-author-dolink' => 'Συνδεῖσθαι τόνδε τὸν δημιουργὸν μετὰ βικι-χρωμένου τινός:',
 2269+ 'code-author-alterlink' => 'Ἀλλάττειν βικι-χρώμενον τὸν συνδεδεμένον μετὰ τοῦδε τοῦ χρωμένου:',
22132270 'code-author-orunlink' => 'Ἢ ἀποδιασυνδεῖσθαι τόνδε τὸν χρώμενον:',
22142271 'code-author-name' => 'Εὶσάγειν ὄνομα χρωμένου τι:',
2215 - 'code-author-success' => 'Ὁ δημιουργὸς $1 διασυνδεδεμένος ἐστὶ μετὰ τοῦ ϝικι-χρωμένου $2',
 2272+ 'code-author-success' => 'Ὁ δημιουργὸς $1 διασυνδεδεμένος ἐστὶ μετὰ τοῦ βικι-χρωμένου $2',
22162273 'code-author-link' => 'συνδεῖσθαι;',
22172274 'code-author-unlink' => 'ἀσυνδεῖσθαι;',
22182275 'code-author-unlinksuccess' => 'Ὁ δημιουργὸς $1 ἀποδιασυνδεδεμένος ἐστίν',
@@ -2224,6 +2281,7 @@
22252282 'code-field-comments' => 'Σημειώματα',
22262283 'code-field-path' => 'Ἀτραπός',
22272284 'code-field-text' => 'Σημείωμα',
 2285+ 'code-field-select' => 'Ἐπιλέγειν',
22282286 'code-rev-author' => 'Δημιουργός:',
22292287 'code-rev-date' => 'Ἡμερομηνία:',
22302288 'code-rev-message' => 'Σχόλιον:',
@@ -2282,7 +2340,7 @@
22832341 'right-codereview-remove-tag' => 'Ἀφαιρεῖν προσαρτήματα ὑπὸ τὰς ἀναθεωρήσεις',
22842342 'right-codereview-post-comment' => 'Προστιθέναι νέα σχόλια ταῖς ἀναθεωρήσεσιν',
22852343 'right-codereview-set-status' => 'Μεταβάλλειν τὸ καθεστὼς τῶν ἀναθεωρήσεων',
2286 - 'right-codereview-link-user' => 'Συνδεῖσθαι τοὺς δημιουργοὺς μετὰ ϝικι-χρωμένων',
 2344+ 'right-codereview-link-user' => 'Συνδεῖσθαι τοὺς δημιουργοὺς μετὰ βικι-χρωμένων',
22872345 'specialpages-group-developer' => 'Ἐργαλεῖα ἀναπτυκτῶν',
22882346 );
22892347
@@ -2298,11 +2356,14 @@
22992357 'code-change-tags' => "het d '''Tag''' vu r$1 gänderet",
23002358 'code-change-removed' => 'usegnuh:',
23012359 'code-change-added' => 'zuegfiegt:',
 2360+ 'code-old-status' => 'Alte Status',
 2361+ 'code-new-status' => 'Neje Status',
23022362 'code-prop-changes' => 'Status- un Tagging-Logbuech',
23032363 'code-desc' => '[[Special:Code|Codepriefigs-Wärchzyyg]] mit [[Special:RepoAdmin|Subversion-Unterstitzig]]',
23042364 'code-no-repo' => 'Kei Repositorium konfiguriert.',
23052365 'code-load-diff' => 'Diff am Lade…',
23062366 'code-notes' => 'Priefnotize',
 2367+ 'code-statuschanges' => 'Statusänderige',
23072368 'code-authors' => 'Autore',
23082369 'code-status' => 'Status',
23092370 'code-tags' => 'Tag',
@@ -2427,13 +2488,16 @@
24282489 'code-change-tags' => "שינה את ה'''תגיות''' של גרסה $1",
24292490 'code-change-removed' => 'הוסרו:',
24302491 'code-change-added' => 'נוספו:',
 2492+ 'code-old-status' => 'המצב הישן',
 2493+ 'code-new-status' => 'המצב החדש',
24312494 'code-prop-changes' => 'יומן מצב ותגיות',
24322495 'code-desc' => '[[Special:Code|כלי בדיקת קוד]] עם [[Special:RepoAdmin|תמיכה ב־Subversion]]',
24332496 'code-no-repo' => 'לא הוגדר מאגר!',
24342497 'code-load-diff' => 'ההבדל בין הגרסאות בטעינה…',
24352498 'code-notes' => 'הערות אחרונות',
 2499+ 'code-statuschanges' => 'שינויי מצב',
24362500 'code-authors' => 'כותבים',
2437 - 'code-status' => 'מצב',
 2501+ 'code-status' => 'מצבים',
24382502 'code-tags' => 'תגיות',
24392503 'code-authors-text' => 'להלן רשימת הכותבים במאגר לפי סדר השינויים האחרונים בקוד.',
24402504 'code-author-haslink' => 'כותב זה מקושר למשתמש הוויקי $1',
@@ -2668,14 +2732,18 @@
26692733 'code-change-tags' => "změni '''taflički''' za wersiju r$1",
26702734 'code-change-removed' => 'wotstronjeny:',
26712735 'code-change-added' => 'přidaty:',
 2736+ 'code-old-status' => 'Stary status',
 2737+ 'code-new-status' => 'Nowy status',
26722738 'code-prop-changes' => 'Protokol wo statusu & tafličkach',
26732739 'code-desc' => '[[Special:Code|Nastroj za kodowu kontrolu]] z [[Special:RepoAdmin|podpěru za Subversion]]',
26742740 'code-no-repo' => 'Žadyn repozitorij konfigurowany',
26752741 'code-load-diff' => 'Rozdźěl so začituje...',
26762742 'code-notes' => 'aktualne komentary',
 2743+ 'code-statuschanges' => 'změny statusa',
26772744 'code-authors' => 'awtorojo',
2678 - 'code-status' => 'status',
 2745+ 'code-status' => 'statusy',
26792746 'code-tags' => 'taflički',
 2747+ 'code-tests' => 'Testowe pady',
26802748 'code-authors-text' => 'To je lisćina awtorojo repozitorija po porjedźe aktualnych nahraćow.',
26812749 'code-author-haslink' => 'Tutón awtor ma wotkaz na wikijoweho wužiwarja $1',
26822750 'code-author-orphan' => 'Tutón awtor nima wotkaz k wikijowemu kontu',
@@ -2694,6 +2762,7 @@
26952763 'code-field-status' => 'Status',
26962764 'code-field-timestamp' => 'Datum',
26972765 'code-field-comments' => 'Přispomnjenki',
 2766+ 'code-field-tests' => 'Testy',
26982767 'code-field-path' => 'Šćežka',
26992768 'code-field-text' => 'Přispomnjenka',
27002769 'code-field-select' => 'Wubrać',
@@ -2882,13 +2951,16 @@
28832952 'code-change-tags' => "cambiava le '''etiquettas''' de v$1",
28842953 'code-change-removed' => 'removeva:',
28852954 'code-change-added' => 'addeva:',
 2955+ 'code-old-status' => 'Stato ancian',
 2956+ 'code-new-status' => 'Stato nove',
28862957 'code-prop-changes' => 'Registro de stato e de etiquettage',
28872958 'code-desc' => '[[Special:Code|Instrumento pro revider le codice]] con [[Special:RepoAdmin|supporto de Subversion]]',
28882959 'code-no-repo' => 'Nulle deposito configurate!',
28892960 'code-load-diff' => 'Cargamento del diff in curso…',
28902961 'code-notes' => 'commentos recente',
 2962+ 'code-statuschanges' => 'cambios de stato',
28912963 'code-authors' => 'autores',
2892 - 'code-status' => 'stato',
 2964+ 'code-status' => 'statos',
28932965 'code-tags' => 'etiquettas',
28942966 'code-authors-text' => 'Infra es un lista de autores del deposito in ordine de publicationes recente.',
28952967 'code-author-haslink' => 'Iste autor es ligate al usator $1 de iste wiki',
@@ -2991,6 +3063,34 @@
29923064 'specialpages-group-developer' => 'Instrumentos pro disveloppatores',
29933065 );
29943066
 3067+/** Indonesian (Bahasa Indonesia)
 3068+ * @author Bennylin
 3069+ */
 3070+$messages['id'] = array(
 3071+ 'code-comments' => 'Komentar',
 3072+ 'code-status' => 'status',
 3073+ 'code-field-id' => 'Revisi',
 3074+ 'code-field-author' => 'Pembuat',
 3075+ 'code-field-status' => 'Status',
 3076+ 'code-field-timestamp' => 'Tanggal',
 3077+ 'code-field-comments' => 'Catatan',
 3078+ 'code-field-path' => 'Jalan',
 3079+ 'code-rev-author' => 'Pembuat:',
 3080+ 'code-rev-date' => 'Tanggal:',
 3081+ 'code-rev-message' => 'Komentar:',
 3082+ 'code-rev-rev' => 'Revisi:',
 3083+ 'code-rev-status' => 'Status:',
 3084+ 'code-rev-comment-preview' => 'Pratayang',
 3085+ 'code-status-new' => 'baru',
 3086+ 'code-status-reverted' => 'telah dikembalikan',
 3087+ 'code-status-ok' => 'ok',
 3088+ 'code-pathsearch-path' => 'Jalan:',
 3089+ 'codereview-batch-submit' => 'Kirim',
 3090+ 'codereview-subtitle' => 'Untuk $1',
 3091+ 'repoadmin-new-button' => 'Buat',
 3092+ 'repoadmin-edit-button' => 'OK',
 3093+);
 3094+
29953095 /** Ido (Ido)
29963096 * @author Malafaya
29973097 */
@@ -3126,21 +3226,25 @@
31273227 */
31283228 $messages['ja'] = array(
31293229 'code' => 'コードレビュー',
3130 - 'code-rev-title' => '$1版 - コードレビュー',
 3230+ 'code-rev-title' => '第$1版 - コードレビュー',
31313231 'code-comments' => 'コメント',
31323232 'code-references' => '追補版',
3133 - 'code-change-status' => "r$1 の'''ステータス'''を変更しました",
 3233+ 'code-change-status' => "r$1 の'''状態'''を変更しました",
31343234 'code-change-tags' => "r$1 の'''タグ'''を変更しました",
31353235 'code-change-removed' => '除去:',
31363236 'code-change-added' => '追加:',
3137 - 'code-prop-changes' => 'ステータスとタグ付けのログ',
 3237+ 'code-old-status' => '古い状態',
 3238+ 'code-new-status' => '新しい状態',
 3239+ 'code-prop-changes' => '状態とタグ付けのログ',
31383240 'code-desc' => '[[Special:RepoAdmin|Subversion サポート]]付きの[[Special:Code|コードレビュー・ツール]]',
3139 - 'code-no-repo' => '設定されたリポジトリはありません!',
 3241+ 'code-no-repo' => '設定されたリポジトリはありません!',
31403242 'code-load-diff' => '差分を読み込み中…',
31413243 'code-notes' => '最近のコメント',
 3244+ 'code-statuschanges' => '状態の変更',
31423245 'code-authors' => '著者',
3143 - 'code-status' => 'ステータス',
 3246+ 'code-status' => '状態',
31443247 'code-tags' => 'タグ',
 3248+ 'code-tests' => 'テストケース',
31453249 'code-authors-text' => '以下は最新コミット順のリポジトリ作成者一覧です。',
31463250 'code-author-haslink' => 'この著者はウィキの利用者 $1 と対応付けられています。',
31473251 'code-author-orphan' => 'この著者にはウィキのアカウントとの対応付けがありません。',
@@ -3156,9 +3260,10 @@
31573261 'code-field-author' => '著者',
31583262 'code-field-user' => 'コメンター',
31593263 'code-field-message' => 'コミット要約',
3160 - 'code-field-status' => '状況',
 3264+ 'code-field-status' => '状態',
31613265 'code-field-timestamp' => '日付',
31623266 'code-field-comments' => 'コメント',
 3267+ 'code-field-tests' => 'テスト',
31633268 'code-field-path' => 'パス',
31643269 'code-field-text' => 'コメント',
31653270 'code-field-select' => '選択',
@@ -3174,8 +3279,8 @@
31753280 'code-rev-modified-d' => '削除',
31763281 'code-rev-modified-m' => '変更',
31773282 'code-rev-imagediff' => '画像の変更',
3178 - 'code-rev-status' => 'ステータス:',
3179 - 'code-rev-status-set' => 'ステータスを変更する',
 3283+ 'code-rev-status' => '状態:',
 3284+ 'code-rev-status-set' => '状態を変更する',
31803285 'code-rev-tags' => 'タグ:',
31813286 'code-rev-tag-add' => 'タグを追加:',
31823287 'code-rev-tag-remove' => 'タグを除去:',
@@ -3187,7 +3292,7 @@
31883293 'code-rev-purge-link' => 'パージ',
31893294 'code-status-new' => '新規',
31903295 'code-status-fixme' => '要修正',
3191 - 'code-status-reverted' => '取消済',
 3296+ 'code-status-reverted' => '差し戻し済み',
31923297 'code-status-resolved' => '解決済',
31933298 'code-status-ok' => '問題なし',
31943299 'code-status-verified' => '検証済',
@@ -3196,7 +3301,7 @@
31973302 'code-pathsearch-path' => 'パス:',
31983303 'code-rev-submit' => '変更を保存',
31993304 'code-rev-submit-next' => '保存し、次の未解決に移る',
3200 - 'code-batch-status' => 'ステータスを変更:',
 3305+ 'code-batch-status' => '状態を変更:',
32013306 'code-batch-tags' => 'タグを変更:',
32023307 'codereview-batch-title' => '選択したリビジョンをすべて変更する',
32033308 'codereview-batch-submit' => '送信',
@@ -3237,7 +3342,7 @@
32383343 'right-codereview-add-tag' => 'リビジョンに新しいタグを追加する',
32393344 'right-codereview-remove-tag' => 'リビジョンからタグを除去する',
32403345 'right-codereview-post-comment' => 'リビジョンにコメントを追加する',
3241 - 'right-codereview-set-status' => 'リビジョンのステータスを変更する',
 3346+ 'right-codereview-set-status' => 'リビジョンの状態を変更する',
32423347 'right-codereview-link-user' => '著者とウィキ利用者を対応付ける',
32433348 'specialpages-group-developer' => '開発者用ツール',
32443349 );
@@ -3248,14 +3353,14 @@
32493354 $messages['jv'] = array(
32503355 'code' => 'Pamriksan kodhe',
32513356 'code-comments' => 'Komentar',
3252 - 'code-change-status' => "Ngowahi '''status''' révisi iki",
3253 - 'code-change-tags' => "ngowahi ''tag'' révisi iki",
 3357+ 'code-change-status' => "ngowahi '''status''' saka r$1",
 3358+ 'code-change-tags' => "ngowahi '''tag''' saka r$1",
32543359 'code-change-removed' => 'busak:',
32553360 'code-change-added' => 'ditambahaké:',
32563361 'code-prop-changes' => "Log pamasangan ''tag'' & status",
32573362 'code-desc' => '[[Special:Code|piranti pamriksan kodhe]] kanthi [[Special:RepoAdmin|dhukungan anak-vèrsi]]',
32583363 'code-no-repo' => "Ora ana panyimpenan (''repository'') kang dipilih (''configured'')",
3259 - 'code-notes' => 'Cathetan pamriksan',
 3364+ 'code-notes' => 'Komentar anyar',
32603365 'code-authors' => 'pangarang',
32613366 'code-tags' => "tandha (''tag'')",
32623367 'code-authors-text' => "Ing ngisor dhaptar pangarang repo miturut ''commit'' sing anyar iki.",
@@ -3308,8 +3413,8 @@
33093414 'code-status-deferred' => 'ditundha',
33103415 'code-pathsearch-legend' => 'Golèki révisi ing jalur repo iki',
33113416 'code-pathsearch-path' => 'Jalur/lintasan',
3312 - 'code-rev-submit' => "Owah-owahan ''commit''",
3313 - 'code-rev-submit-next' => "''Commit'' lan durung bèrès (''unresolved'')",
 3417+ 'code-rev-submit' => 'Simpen owah-owahan',
 3418+ 'code-rev-submit-next' => 'Simpen & durung-bèrès sabanjuré',
33143419 'codereview-reply-link' => 'wales/walesan',
33153420 'codereview-email-subj' => '[$1] [r$2]: Komentar anyar ditambahaké',
33163421 'codereview-email-body' => 'Panganggo "$1" awèh komentar ing r$3.
@@ -3530,14 +3635,18 @@
35313636 'code-change-tags' => "hät de '''Makeerunge''' vun dä Version $1 verändert",
35323637 'code-change-removed' => 'eruß jenomme:',
35333638 'code-change-added' => 'dobei jedonn:',
 3639+ 'code-old-status' => 'Der ahle Stattus',
 3640+ 'code-new-status' => 'Der neue Shtattus',
35343641 'code-prop-changes' => 'Logboch för Shtattus un Makeerunge',
35353642 'code-desc' => 'Werkzüch för [[Special:Code|Projramm-Änderunge ze verwallde]] met [[Special:RepoAdmin|Ongershtözung för <i lang="en">Subversion</i>]]',
35363643 'code-no-repo' => 'Et es kei Repositorijum enjeshtallt.',
35373644 'code-load-diff' => 'Ben de Ungerscheide aam Lade&nbsp;…',
35383645 'code-notes' => 'De neuste Bemerkunge',
 3646+ 'code-statuschanges' => 'Änderunge aam Stattus',
35393647 'code-authors' => 'de Schriiver',
3540 - 'code-status' => 'Shtattus',
 3648+ 'code-status' => 'Shtattuße',
35413649 'code-tags' => 'Makeerunge',
 3650+ 'code-tests' => 'Prööf-Fäll',
35423651 'code-authors-text' => 'Hee kütt en Leß met dä Schriever aan dämm Repositorijum, en dä Reijefolch, wie se jespeichert hann.',
35433652 'code-author-haslink' => 'Dä Schriiver es em Wiki mem Metmaacher $1 verlengk',
35443653 'code-author-orphan' => 'Dä Schriiver es nit met enem Metmaacher em Wiki verlengk',
@@ -3556,6 +3665,7 @@
35573666 'code-field-status' => 'Shtattus',
35583667 'code-field-timestamp' => 'Zick un Dattum',
35593668 'code-field-comments' => 'Bemerkunge',
 3669+ 'code-field-tests' => 'Pröfunge',
35603670 'code-field-path' => 'Pad',
35613671 'code-field-text' => 'Notiz',
35623672 'code-field-select' => 'Ußsöke',
@@ -3640,6 +3750,7 @@
36413751 );
36423752
36433753 /** Luxembourgish (Lëtzebuergesch)
 3754+ * @author Les Meloures
36443755 * @author Robby
36453756 */
36463757 $messages['lb'] = array(
@@ -3651,12 +3762,15 @@
36523763 'code-change-tags' => "huet '''Taggen''' fir $1 geännert",
36533764 'code-change-removed' => 'ewech geholl:',
36543765 'code-change-added' => 'derbäi gesat:',
 3766+ 'code-old-status' => 'Ale Status',
 3767+ 'code-new-status' => 'Neie Status',
36553768 'code-prop-changes' => 'Logbuch vum Status an den Taggen',
3656 - 'code-desc' => "[[Special:Code|Tool fir de Code nozekucken]] matt [[Special:RepoAdmin|Subversioun's Ënnerstëtzung]]",
 3769+ 'code-desc' => "[[Special:Code|Tool fir de Code nozekucken]] mat [[Special:RepoAdmin|Subversioun's Ënnerstëtzung]]",
36573770 'code-load-diff' => 'Lude vun Diff…',
36583771 'code-notes' => 'rezent Bemierkungen',
 3772+ 'code-statuschanges' => 'Ännerunge vum Status',
36593773 'code-authors' => 'Auteuren',
3660 - 'code-status' => 'Status',
 3774+ 'code-status' => 'Statussen',
36613775 'code-tags' => 'Tagen',
36623776 'code-author-haslink' => 'Dësen Auteur ass mam Wiki-Benotzer $1 verbonn',
36633777 'code-author-orphan' => 'Dëse Benotzer huet kee Link mat engem Wiki-Benotzerkont',
@@ -3683,6 +3797,7 @@
36843798 'code-rev-message' => 'Bemierkung:',
36853799 'code-rev-rev' => 'Versioun:',
36863800 'code-rev-rev-viewvc' => 'op ViewVC',
 3801+ 'code-rev-paths' => 'Geännert Pied:',
36873802 'code-rev-modified-a' => 'derbäigesat',
36883803 'code-rev-modified-r' => 'ersat',
36893804 'code-rev-modified-d' => 'geläscht',
@@ -3733,8 +3848,8 @@
37343849 'repoadmin-edit-button' => 'OK',
37353850 'repoadmin-edit-sucess' => 'De \'\'Repositoire\'\' "[[Special:Code/$1|$1]]" gouf geännert.',
37363851 'right-codereview-use' => 'Spezial benotzen: Code',
3737 - 'right-codereview-add-tag' => "Nei Tagen bäi d'Versiounen derbäisetzen",
3738 - 'right-codereview-remove-tag' => 'Taggen aus Versiounen eraushuelen',
 3852+ 'right-codereview-add-tag' => "Nei Markéierunge bei d'Versiounen derbäisetzen",
 3853+ 'right-codereview-remove-tag' => 'Markéierungen aus Versiounen eraushuelen',
37393854 'right-codereview-post-comment' => "Bemierkunge Bài d'Versiounen derbäisetzen",
37403855 'right-codereview-set-status' => 'Ännere vum Status vun de Versiounen',
37413856 'right-codereview-link-user' => 'Auteure mat Wiki-Benotzer verbannen (verlinken)',
@@ -3874,6 +3989,14 @@
38753990 'specialpages-group-developer' => 'Hölpmiddele veur óntwikkeleers',
38763991 );
38773992
 3993+/** Lithuanian (Lietuvių)
 3994+ * @author Matasg
 3995+ */
 3996+$messages['lt'] = array(
 3997+ 'repoadmin-new-button' => 'Sukurti',
 3998+ 'repoadmin-edit-button' => 'Gerai',
 3999+);
 4000+
38784001 /** Macedonian (Македонски)
38794002 * @author Brest
38804003 */
@@ -4189,14 +4312,18 @@
41904313 'code-change-tags' => "heeft de '''labels''' voor versie r$1 gewijzigd",
41914314 'code-change-removed' => 'verwijderd:',
41924315 'code-change-added' => 'toegevoegd:',
 4316+ 'code-old-status' => 'Oude status',
 4317+ 'code-new-status' => 'Nieuwe status',
41934318 'code-prop-changes' => 'Logboek status en labels',
41944319 'code-desc' => '[[Special:Code|Hulpprogramma voor codecontrole]] met [[Special:RepoAdmin|ondersteuning voor Subversion]]',
41954320 'code-no-repo' => 'Er is geen repository ingesteld!',
41964321 'code-load-diff' => 'Bezig met het laden van de veranderingen…',
41974322 'code-notes' => 'recente opmerkingen',
 4323+ 'code-statuschanges' => 'statuswijzigingen',
41984324 'code-authors' => 'auteurs',
4199 - 'code-status' => 'status',
 4325+ 'code-status' => 'statussen',
42004326 'code-tags' => 'labels',
 4327+ 'code-tests' => 'Testcases',
42014328 'code-authors-text' => 'Hieronder staat een lijst met auteurs uit de repository, degene met de meest recente commit bovenaan.',
42024329 'code-author-haslink' => 'Deze auteur is gekoppeld aan de wikigebruiker $1',
42034330 'code-author-orphan' => 'Deze auteur is niet gekoppeld aan een wikigebruiker',
@@ -4215,6 +4342,7 @@
42164343 'code-field-status' => 'Status',
42174344 'code-field-timestamp' => 'Datum',
42184345 'code-field-comments' => 'Aantekeningen',
 4346+ 'code-field-tests' => 'Tests',
42194347 'code-field-path' => 'Pad',
42204348 'code-field-text' => 'Opmerking',
42214349 'code-field-select' => 'Selecteren',
@@ -4681,7 +4809,16 @@
46824810 * @author Xqt
46834811 */
46844812 $messages['pdc'] = array(
 4813+ 'code-comments' => 'Comments',
 4814+ 'code-references' => 'Neegschte Versione',
 4815+ 'code-change-removed' => 'gelöscht:',
 4816+ 'code-change-added' => 'dezu geduh:',
 4817+ 'code-rev-message' => 'Comment:',
 4818+ 'code-rev-modified-d' => 'gelöscht',
 4819+ 'code-status-new' => 'nei',
 4820+ 'code-status-ok' => 'OK',
46854821 'codereview-subtitle' => 'Fer $1',
 4822+ 'repoadmin-edit-button' => 'OK',
46864823 );
46874824
46884825 /** Polish (Polski)
@@ -4699,13 +4836,16 @@
47004837 'code-change-tags' => "zmieniono '''znaczniki''' r$1",
47014838 'code-change-removed' => 'usunięto:',
47024839 'code-change-added' => 'dodano:',
 4840+ 'code-old-status' => 'Poprzedni status',
 4841+ 'code-new-status' => 'Nowy status',
47034842 'code-prop-changes' => 'Rejestr zmian statusu i znaczników',
47044843 'code-desc' => '[[Special:Code|Narzędzie do przeglądania]] oraz [[Special:RepoAdmin|zarządzania wersjami]] kodu źródłowego',
47054844 'code-no-repo' => 'Brak skonfigurowanego repozytorium!',
47064845 'code-load-diff' => 'Ładowanie różnic…',
47074846 'code-notes' => 'ostatnie komentarze',
 4847+ 'code-statuschanges' => 'zmiany statusu',
47084848 'code-authors' => 'autorzy',
4709 - 'code-status' => 'status',
 4849+ 'code-status' => 'statusy',
47104850 'code-tags' => 'znaczniki',
47114851 'code-authors-text' => 'Poniżej znajduje się lista autorów repozytorium w kolejności ostatnio dodanej poprawki do kodu źródłowego.',
47124852 'code-author-haslink' => 'Ten autor jest podlinkowany do konta użytkownika na wiki jako $1',
@@ -5106,9 +5246,13 @@
51075247 * @author Joetaras
51085248 */
51095249 $messages['roa-tara'] = array(
 5250+ 'code' => 'Revisore de Codece',
 5251+ 'code-rev-title' => 'r$1 - Revisore de Codece',
51105252 'code-comments' => 'Commende',
51115253 'code-change-removed' => 'luete:',
51125254 'code-change-added' => 'aggiunde:',
 5255+ 'code-old-status' => 'State vecchije',
 5256+ 'code-new-status' => 'State nuève',
51135257 'code-authors' => 'le autore',
51145258 'code-status' => 'state',
51155259 'code-tags' => 'le tag',
@@ -5157,6 +5301,7 @@
51585302 );
51595303
51605304 /** Russian (Русский)
 5305+ * @author Ferrer
51615306 * @author Kaganer
51625307 * @author Putnik
51635308 * @author Александр Сигачёв
@@ -5170,14 +5315,18 @@
51715316 'code-change-tags' => "изменил '''метки''' для r$1",
51725317 'code-change-removed' => 'удалено:',
51735318 'code-change-added' => 'добавлено:',
 5319+ 'code-old-status' => 'Старый статус',
 5320+ 'code-new-status' => 'Новый статус',
51745321 'code-prop-changes' => 'Журнал статусов и меток',
51755322 'code-desc' => '[[Special:Code|Инструмент проверки кода]] с [[Special:RepoAdmin|поддержкой Subversion]]',
51765323 'code-no-repo' => 'Отсутствует настроенное хранилище!',
51775324 'code-load-diff' => 'Загрузка сравнения…',
51785325 'code-notes' => 'последние замечания',
 5326+ 'code-statuschanges' => 'изменения статуса',
51795327 'code-authors' => 'авторы',
5180 - 'code-status' => 'состояние',
 5328+ 'code-status' => 'состояния',
51815329 'code-tags' => 'метки',
 5330+ 'code-tests' => 'Тестовые запросы',
51825331 'code-authors-text' => 'Ниже находится список авторов в порядке свежести вносимых ими изменений (более новые — сверху).',
51835332 'code-author-haslink' => 'Этот автор ассоциирован с участником $1',
51845333 'code-author-orphan' => 'Для этого автора не установлена связь с учётной записью вики-проекта',
@@ -5196,6 +5345,7 @@
51975346 'code-field-status' => 'Статус',
51985347 'code-field-timestamp' => 'Дата',
51995348 'code-field-comments' => 'Комментариев',
 5349+ 'code-field-tests' => 'Тесты',
52005350 'code-field-path' => 'Путь',
52015351 'code-field-text' => 'Замечание',
52025352 'code-field-select' => 'Выбрать',
@@ -5291,13 +5441,16 @@
52925442 'code-change-tags' => "'''бэлиэлэрин''' $1 уларыппыт (бэлиэлэрэ уларыйбыт)",
52935443 'code-change-removed' => 'сотулунна:',
52945444 'code-change-added' => 'эбилиннэ:',
 5445+ 'code-old-status' => 'Урукку туруга',
 5446+ 'code-new-status' => 'Саҥа туруга',
52955447 'code-prop-changes' => 'Статус уонна бэлиэлэр сурунааллара',
52965448 'code-desc' => '[[Special:RepoAdmin|Subversion]] өйүүр [[Special:Code|куоду көрдөрөр үнүстүрүмүөн]]',
52975449 'code-no-repo' => 'Анаан оҥоһуллубут ыскылаат суох',
52985450 'code-load-diff' => 'Тэҥнээһин...',
52995451 'code-notes' => 'соторутааҥҥы бэлиэтээһиннэр',
 5452+ 'code-statuschanges' => 'турук уларыйыылара',
53005453 'code-authors' => 'ааптардар',
5301 - 'code-status' => 'туруга (стаатуһа)',
 5454+ 'code-status' => 'туруктара (статустара)',
53025455 'code-tags' => 'бэлиэлэр',
53035456 'code-authors-text' => 'Аллараа ааптардар тиһиктэрэ киллэрбит уларытыыларын кэминэн наарданан (саҥалар — үөһэ) бэриллэр.',
53045457 'code-author-haslink' => 'Бу ааптар $1 кыттааччыга сигэнэр',
@@ -5517,13 +5670,16 @@
55185671 'code-change-tags' => "zmenil '''značky''' r$1",
55195672 'code-change-removed' => 'odstránené:',
55205673 'code-change-added' => 'pridané:',
 5674+ 'code-old-status' => 'Starý stav',
 5675+ 'code-new-status' => 'Nový stav',
55215676 'code-prop-changes' => 'Záznam stavu a značiek',
55225677 'code-desc' => '[[Special:Code|Nástroj na kontrolu kódu]] s [[Special:RepoAdmin|podporou Subversion]]',
55235678 'code-no-repo' => 'Nebolo nastavené žiadne úložisko',
55245679 'code-load-diff' => 'Načítava sa rozdiel…',
55255680 'code-notes' => 'posledné komentáre',
 5681+ 'code-statuschanges' => 'zmeny stavu',
55265682 'code-authors' => 'autori',
5527 - 'code-status' => 'stav',
 5683+ 'code-status' => 'stavy',
55285684 'code-tags' => 'značky',
55295685 'code-authors-text' => 'Toto je zoznam autorov v úložisku v poradí podľa posledných commitov.',
55305686 'code-author-haslink' => 'Tento autor je zviazaný s používateľom wiki $1',
@@ -6725,7 +6881,7 @@
67266882 */
67276883 $messages['vo'] = array(
67286884 'code-comments' => 'Küpets',
6729 - 'code-change-status' => "evotükon '''stadi''' revida at",
 6885+ 'code-change-status' => "evotükon '''stadi''' ela r$1",
67306886 'code-change-removed' => 'pemoükon:',
67316887 'code-change-added' => 'peläükon:',
67326888 'code-no-repo' => 'No dabinon kipedöp labü paramets pegivülöl!',
Index: branches/wmf-deployment/extensions/CodeReview/CodeReview.php
@@ -37,32 +37,41 @@
3838
3939 $dir = dirname( __FILE__ ) . '/';
4040
41 -$wgAutoloadClasses['ApiCodeUpdate'] = $dir . 'ApiCodeUpdate.php';
42 -$wgAutoloadClasses['ApiCodeDiff'] = $dir . 'ApiCodeDiff.php';
43 -$wgAutoloadClasses['ApiCodeComments'] = $dir . 'ApiCodeComments.php';
44 -$wgAutoloadClasses['CodeDiffHighlighter'] = $dir . 'DiffHighlighter.php';
45 -$wgAutoloadClasses['CodeRepository'] = $dir . 'CodeRepository.php';
46 -$wgAutoloadClasses['CodeRepoListView'] = $dir . 'CodeRepoListView.php';
47 -$wgAutoloadClasses['CodeRevision'] = $dir . 'CodeRevision.php';
48 -$wgAutoloadClasses['CodeRevisionAuthorView'] = $dir . 'CodeRevisionAuthorView.php';
49 -$wgAutoloadClasses['CodeRevisionAuthorLink'] = $dir . 'CodeRevisionAuthorLink.php';
50 -$wgAutoloadClasses['CodeRevisionListView'] = $dir . 'CodeRevisionListView.php';
51 -$wgAutoloadClasses['CodeRevisionCommitter'] = $dir . 'CodeRevisionCommitter.php';
52 -$wgAutoloadClasses['CodeRevisionStatusView'] = $dir . 'CodeRevisionStatusView.php';
53 -$wgAutoloadClasses['CodeRevisionTagView'] = $dir . 'CodeRevisionTagView.php';
54 -$wgAutoloadClasses['CodeRevisionView'] = $dir . 'CodeRevisionView.php';
55 -$wgAutoloadClasses['CodeAuthorListView'] = $dir . 'CodeAuthorListView.php';
56 -$wgAutoloadClasses['CodeStatusListView'] = $dir . 'CodeStatusListView.php';
57 -$wgAutoloadClasses['CodeTagListView'] = $dir . 'CodeTagListView.php';
58 -$wgAutoloadClasses['CodeCommentsListView'] = $dir . 'CodeCommentsListView.php';
59 -$wgAutoloadClasses['CodeReleaseNotes'] = $dir . 'CodeReleaseNotes.php';
60 -$wgAutoloadClasses['CodeComment'] = $dir . 'CodeComment.php';
61 -$wgAutoloadClasses['CodePropChange'] = $dir . 'CodePropChange.php';
62 -$wgAutoloadClasses['SpecialCode'] = $dir . 'SpecialCode.php';
63 -$wgAutoloadClasses['CodeView'] = $dir . 'SpecialCode.php';
64 -$wgAutoloadClasses['SpecialRepoAdmin'] = $dir . 'SpecialRepoAdmin.php';
65 -$wgAutoloadClasses['SubversionAdaptor'] = $dir . 'Subversion.php';
 41+$wgAutoloadClasses['ApiCodeUpdate'] = $dir . 'api/ApiCodeUpdate.php';
 42+$wgAutoloadClasses['ApiCodeDiff'] = $dir . 'api/ApiCodeDiff.php';
 43+$wgAutoloadClasses['ApiCodeComments'] = $dir . 'api/ApiCodeComments.php';
 44+$wgAutoloadClasses['ApiCodeTestUpload'] = $dir . 'api/ApiCodeTestUpload.php';
6645
 46+$wgAutoloadClasses['SubversionAdaptor'] = $dir . 'backend/Subversion.php';
 47+$wgAutoloadClasses['CodeDiffHighlighter'] = $dir . 'backend/DiffHighlighter.php';
 48+
 49+$wgAutoloadClasses['CodeRepository'] = $dir . 'backend/CodeRepository.php';
 50+$wgAutoloadClasses['CodeRevision'] = $dir . 'backend/CodeRevision.php';
 51+$wgAutoloadClasses['CodeComment'] = $dir . 'backend/CodeComment.php';
 52+$wgAutoloadClasses['CodePropChange'] = $dir . 'backend/CodePropChange.php';
 53+$wgAutoloadClasses['CodeTestSuite'] = $dir . 'backend/CodeTestSuite.php';
 54+$wgAutoloadClasses['CodeTestRun'] = $dir . 'backend/CodeTestRun.php';
 55+$wgAutoloadClasses['CodeTestResult'] = $dir . 'backend/CodeTestResult.php';
 56+
 57+$wgAutoloadClasses['CodeRepoListView'] = $dir . 'ui/CodeRepoListView.php';
 58+$wgAutoloadClasses['CodeRevisionAuthorView'] = $dir . 'ui/CodeRevisionAuthorView.php';
 59+$wgAutoloadClasses['CodeRevisionAuthorLink'] = $dir . 'ui/CodeRevisionAuthorLink.php';
 60+$wgAutoloadClasses['CodeRevisionCommitter'] = $dir . 'ui/CodeRevisionCommitter.php';
 61+$wgAutoloadClasses['CodeRevisionListView'] = $dir . 'ui/CodeRevisionListView.php';
 62+$wgAutoloadClasses['CodeRevisionStatusView'] = $dir . 'ui/CodeRevisionStatusView.php';
 63+$wgAutoloadClasses['CodeRevisionTagView'] = $dir . 'ui/CodeRevisionTagView.php';
 64+$wgAutoloadClasses['CodeRevisionView'] = $dir . 'ui/CodeRevisionView.php';
 65+$wgAutoloadClasses['CodeAuthorListView'] = $dir . 'ui/CodeAuthorListView.php';
 66+$wgAutoloadClasses['CodeStatusListView'] = $dir . 'ui/CodeStatusListView.php';
 67+$wgAutoloadClasses['CodeTagListView'] = $dir . 'ui/CodeTagListView.php';
 68+$wgAutoloadClasses['CodeCommentsListView'] = $dir . 'ui/CodeCommentsListView.php';
 69+$wgAutoloadClasses['CodeReleaseNotes'] = $dir . 'ui/CodeReleaseNotes.php';
 70+$wgAutoloadClasses['CodeStatusChangeListView'] = $dir . 'ui/CodeStatusChangeListView.php';
 71+$wgAutoloadClasses['SpecialCode'] = $dir . 'ui/SpecialCode.php';
 72+$wgAutoloadClasses['CodeView'] = $dir . 'ui/SpecialCode.php';
 73+$wgAutoloadClasses['SpecialRepoAdmin'] = $dir . 'ui/SpecialRepoAdmin.php';
 74+
 75+
6776 $wgSpecialPages['Code'] = 'SpecialCode';
6877 $wgSpecialPageGroups['Code'] = 'developer';
6978 $wgSpecialPages['RepoAdmin'] = 'SpecialRepoAdmin';
@@ -70,6 +79,7 @@
7180
7281 $wgAPIModules['codeupdate'] = 'ApiCodeUpdate';
7382 $wgAPIModules['codediff'] = 'ApiCodeDiff';
 83+$wgAPIModules['codetestupload'] = 'ApiCodeTestUpload';
7484 $wgAPIListModules['codecomments'] = 'ApiCodeComments';
7585
7686 $wgExtensionMessagesFiles['CodeReview'] = $dir . 'CodeReview.i18n.php';
@@ -99,6 +109,12 @@
100110 $wgSubversionProxy = false;
101111 $wgSubversionProxyTimeout = 30; // default 3 secs is too short :)
102112
 113+// Command-line options to pass on SVN command line if SVN PECL extension
 114+// isn't available and we're not using the proxy.
 115+// Defaults here should allow working with both http: and https: repos
 116+// as long as authentication isn't required.
 117+$wgSubversionOptions = "--non-interactive --trust-server-cert";
 118+
103119 // What is the default SVN import chunk size?
104120 $wgCodeReviewImportBatchSize = 400;
105121
@@ -119,3 +135,27 @@
120136
121137 // What images can be used for client-side side-by-side comparisons?
122138 $wgCodeReviewImgRegex = '/\.(png|jpg|jpeg|gif)$/i';
 139+
 140+// Set to a secret string for HMAC validation of test run data uploads.
 141+// Should match test runner's $wgParserTestRemote['secret'].
 142+$wgCodeReviewSharedSecret = false;
 143+
 144+# Schema changes
 145+$wgHooks['LoadExtensionSchemaUpdates'][] = 'efCodeReviewSchemaUpdates';
 146+
 147+function efCodeReviewSchemaUpdates() {
 148+ global $wgDBtype, $wgExtNewFields, $wgExtPGNewFields, $wgExtNewIndexes, $wgExtNewTables;
 149+ $base = dirname(__FILE__);
 150+ if( $wgDBtype == 'mysql' ) {
 151+ $wgExtNewTables[] = array( 'code_rev', "$base/codereview.sql" ); // Initial install tables
 152+ $wgExtNewFields[] = array( 'code_rev', 'cr_diff', "$base/archives/codereview-cr_diff.sql" );
 153+ $wgExtNewIndexes[] = array( 'code_relations', 'repo_to_from', "$base/archives/code_relations_index.sql" );
 154+ //$wgExtNewFields[] = array( 'code_rev', "$base/archives/codereview-cr_status.sql" ); // FIXME FIXME this is a change to options... don't know how
 155+ $wgExtNewTables[] = array( 'code_bugs', "$base/archives/code_bugs.sql" );
 156+ $wgExtNewTables[] = array( 'code_test_suite', "$base/archives/codereview-code_tests.sql" );
 157+ } elseif( $wgDBtype == 'postgres' ) {
 158+ // TODO
 159+ }
 160+ return true;
 161+}
 162+
Index: branches/wmf-deployment/extensions/CodeReview/codereview.css
@@ -78,6 +78,13 @@
7979 color: #666;
8080 }
8181
 82+.mw-codereview-success {
 83+ color: #1a2;
 84+}
 85+.mw-codereview-fail {
 86+ color: #d21;
 87+}
 88+
8289 /* Diffs */
8390 .mw-codereview-diff ins {
8491 text-decoration: none;
Index: branches/wmf-deployment/extensions/CodeReview/archives/code_relations_index.sql
@@ -1,2 +1,2 @@
22 ALTER TABLE /*$wgDBprefix*/code_relations
3 - ADD key (cf_repo_id, cf_to, cf_from);
 3+ ADD key repo_to_from (cf_repo_id, cf_to, cf_from);
Index: branches/wmf-deployment/extensions/CodeReview/archives/codereview-code_tests.sql
@@ -0,0 +1,66 @@
 2+--
 3+-- Information on available test suites
 4+DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_suite;
 5+CREATE TABLE /*$wgDBprefix*/code_test_suite (
 6+ -- Unique ID per test suite
 7+ ctsuite_id int auto_increment not null,
 8+
 9+ -- Repository ID of the code base this applies to
 10+ ctsuite_repo_id int not null,
 11+
 12+ -- Which branch path this applies to, eg '/trunk/phase3'
 13+ ctsuite_branch_path varchar(255) not null,
 14+
 15+ -- Pleasantly user-readable name, eg "ParserTests"
 16+ ctsuite_name varchar(255) not null,
 17+
 18+ -- Description...
 19+ ctsuite_desc varchar(255) not null,
 20+
 21+ primary key ctsuite_id (ctsuite_id)
 22+) /*$wgDBtableOptions*/;
 23+
 24+DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_case;
 25+CREATE TABLE /*$wgDBprefix*/code_test_case (
 26+ ctcase_id int auto_increment not null,
 27+ ctcase_suite_id int not null,
 28+ ctcase_name varchar(255) not null,
 29+
 30+ primary key ctc_id (ctcase_id),
 31+ key (ctcase_suite_id, ctcase_id)
 32+) /*$wgDBtableOptions*/;
 33+
 34+DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_run;
 35+CREATE TABLE /*$wgDBprefix*/code_test_run (
 36+ ctrun_id int auto_increment not null,
 37+
 38+ ctrun_suite_id int not null,
 39+ ctrun_rev_id int not null,
 40+
 41+ ctrun_status enum ('running', 'complete', 'abort'),
 42+
 43+ ctrun_count_total int,
 44+ ctrun_count_success int,
 45+
 46+ primary key ctrun_id (ctrun_id),
 47+ key suite_rev (ctrun_suite_id, ctrun_rev_id)
 48+) /*$wgDBtableOptions*/;
 49+
 50+
 51+DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_result;
 52+CREATE TABLE /*$wgDBprefix*/code_test_result (
 53+ ctresult_id int auto_increment not null,
 54+
 55+ -- Which test run and case are we on?
 56+ ctresult_run_id int not null,
 57+ ctresult_case_id int not null,
 58+
 59+ -- Did we succeed or fail?
 60+ ctresult_success bool not null,
 61+
 62+ -- Optional HTML chunk data
 63+ ctresult_details blob,
 64+
 65+ primary key ctr_id (ctresult_id),
 66+ key run_id (ctresult_run_id, ctresult_id)
 67+) /*$wgDBtableOptions*/;
Property changes on: branches/wmf-deployment/extensions/CodeReview/archives/codereview-code_tests.sql
___________________________________________________________________
Added: svn:eol-style
168 + native
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeComment.php
@@ -0,0 +1,29 @@
 2+<?php
 3+if ( !defined( 'MEDIAWIKI' ) ) die();
 4+
 5+class CodeComment {
 6+ function __construct( $rev ) {
 7+ $this->rev = $rev;
 8+ }
 9+
 10+ static function newFromRow( $rev, $row ) {
 11+ return self::newFromData( $rev, get_object_vars( $row ) );
 12+ }
 13+
 14+ static function newFromData( $rev, $data ) {
 15+ $comment = new CodeComment( $rev );
 16+ $comment->id = intval( $data['cc_id'] );
 17+ $comment->text = $data['cc_text']; // fixme
 18+ $comment->user = $data['cc_user'];
 19+ $comment->userText = $data['cc_user_text'];
 20+ $comment->timestamp = wfTimestamp( TS_MW, $data['cc_timestamp'] );
 21+ $comment->review = $data['cc_review'];
 22+ $comment->sortkey = $data['cc_sortkey'];
 23+ return $comment;
 24+ }
 25+
 26+ function threadDepth() {
 27+ $timestamps = explode( ",", $this->sortkey );
 28+ return count( $timestamps );
 29+ }
 30+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeComment.php
___________________________________________________________________
Added: svn:eol-style
131 + native
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestRun.php
@@ -0,0 +1,170 @@
 2+<?php
 3+
 4+class CodeTestRun {
 5+ public function __construct( CodeTestSuite $suite, $row ) {
 6+ if( is_object( $row ) ) {
 7+ $row = wfObjectToArray( $row );
 8+ }
 9+ $this->suite = $suite;
 10+ $this->id = intval( $row['ctrun_id'] );
 11+ $this->revId = intval( $row['ctrun_rev_id'] );
 12+ $this->status = $row['ctrun_status'];
 13+ $this->countTotal = $row['ctrun_count_total'];
 14+ $this->countSuccess = $row['ctrun_count_success'];
 15+
 16+ $this->mCaseMap = null; // Lazy-initialize...
 17+ }
 18+
 19+ public function getResults( $success=null ) {
 20+ $dbr = wfGetDB( DB_MASTER );
 21+ $conds = array(
 22+ 'ctresult_run_id' => $this->id,
 23+ 'ctresult_case_id=ctcase_id',
 24+ );
 25+ if( $success !== null ) {
 26+ $conds['ctresult_success'] = $success ? 1 : 0;
 27+ }
 28+
 29+ $result = $dbr->select(
 30+ array(
 31+ 'code_test_result',
 32+ 'code_test_case',
 33+ ),
 34+ '*',
 35+ $conds,
 36+ __METHOD__ );
 37+
 38+ $out = array();
 39+ foreach( $result as $row ) {
 40+ $out[] = new CodeTestResult( $this, $row );
 41+ }
 42+ return $out;
 43+ }
 44+
 45+ public static function newFromRevId( CodeTestSuite $suite, $revId ) {
 46+ $dbr = wfGetDB( DB_MASTER );
 47+ $row = $dbr->selectRow( 'code_test_run',
 48+ '*',
 49+ array(
 50+ 'ctrun_suite_id' => $suite->id,
 51+ 'ctrun_rev_id' => $revId,
 52+ ),
 53+ __METHOD__ );
 54+ if( $row ) {
 55+ return new CodeTestRun( $suite, $row );
 56+ } else {
 57+ return null;
 58+ }
 59+ }
 60+
 61+ public function setStatus( $status ) {
 62+ $this->status = $status;
 63+ $dbw = wfGetDB( DB_MASTER );
 64+ $dbw->update(
 65+ 'code_test_run',
 66+ array(
 67+ 'ctrun_status' => $status,
 68+ ),
 69+ array(
 70+ 'ctrun_id' => $this->id,
 71+ ),
 72+ __METHOD__ );
 73+ }
 74+
 75+ public static function insertRun( CodeTestSuite $suite, $revId, $status, $results=array() ) {
 76+ $dbw = wfGetDB( DB_MASTER );
 77+ $countTotal = count( $results );
 78+ $countSucceeded = count( array_filter( $results ) );
 79+
 80+ $insertData = array(
 81+ 'ctrun_suite_id' => $suite->id,
 82+ 'ctrun_rev_id' => $revId,
 83+ 'ctrun_status' => $status,
 84+ 'ctrun_count_total' => $countTotal,
 85+ 'ctrun_count_success' => $countSucceeded,
 86+ );
 87+ $dbw->insert( 'code_test_run',
 88+ $insertData,
 89+ __METHOD__ );
 90+
 91+ $insertData['ctrun_id'] = $dbw->insertId();
 92+ $run = new CodeTestRun( $suite, $insertData );
 93+ if( $status == 'complete' && $results ) {
 94+ $run->insertData( $results );
 95+ }
 96+ return $run;
 97+ }
 98+
 99+ public function getCaseId( $caseName ) {
 100+ $this->loadCaseMap();
 101+ if( isset( $this->mCaseMap[$caseName] ) ) {
 102+ return $this->mCaseMap[$caseName];
 103+ } else {
 104+ $dbw = wfGetDB( DB_MASTER );
 105+ $dbw->insert( 'code_test_case',
 106+ array(
 107+ 'ctcase_suite_id' => $this->id,
 108+ 'ctcase_name' => $caseName,
 109+ ),
 110+ __METHOD__ );
 111+ $id = intval( $dbw->insertId() );
 112+ $this->mCaseMap[$caseName] = $id;
 113+ return $id;
 114+ }
 115+ }
 116+
 117+ protected function loadCaseMap() {
 118+ if( is_null( $this->mCaseMap ) ) {
 119+ $this->mCaseMap = array();
 120+ $dbw = wfGetDB( DB_MASTER );
 121+ $result = $dbw->select( 'code_test_case',
 122+ array(
 123+ 'ctcase_id',
 124+ 'ctcase_name',
 125+ ),
 126+ array(
 127+ 'ctcase_suite_id' => $this->id,
 128+ ),
 129+ __METHOD__
 130+ );
 131+ foreach( $result as $row ) {
 132+ $this->mCaseMap[$row->ctcase_name] = intval( $row->ctcase_id );
 133+ }
 134+ }
 135+ }
 136+
 137+ public function saveResults( $results ) {
 138+ $this->insertResults( $results );
 139+ $this->status = "complete";
 140+ $dbw = wfGetDB( DB_MASTER );
 141+ $dbw->update(
 142+ 'code_test_run',
 143+ array(
 144+ 'ctrun_status' => $this->status,
 145+ 'ctrun_count_total' => $this->countTotal,
 146+ 'ctrun_count_success' => $this->countSuccess,
 147+ ),
 148+ array(
 149+ 'ctrun_id' => $this->id,
 150+ ),
 151+ __METHOD__ );
 152+ }
 153+
 154+ public function insertResults( $results ) {
 155+ $dbw = wfGetDB( DB_MASTER );
 156+ $this->countTotal = 0;
 157+ $this->countSuccess = 0;
 158+ foreach( $results as $caseName => $result ) {
 159+ $this->countTotal++;
 160+ if( $result ) {
 161+ $this->countSuccess++;
 162+ }
 163+ $insertData[] = array(
 164+ 'ctresult_run_id' => $this->id,
 165+ 'ctresult_case_id' => $this->getCaseId( $caseName ),
 166+ 'ctresult_success' => $result ? 1 : 0,
 167+ );
 168+ }
 169+ $dbw->insert( 'code_test_result', $insertData, __METHOD__ );
 170+ }
 171+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestRun.php
___________________________________________________________________
Added: svn:eol-style
1172 + native
Index: branches/wmf-deployment/extensions/CodeReview/backend/Subversion.php
@@ -0,0 +1,327 @@
 2+<?php
 3+if ( !defined( 'MEDIAWIKI' ) ) die();
 4+
 5+abstract class SubversionAdaptor {
 6+ protected $mRepo;
 7+
 8+ public static function newFromRepo( $repo ) {
 9+ global $wgSubversionProxy, $wgSubversionProxyTimeout;
 10+ if ( $wgSubversionProxy ) {
 11+ return new SubversionProxy( $repo, $wgSubversionProxy, $wgSubversionProxyTimeout );
 12+ } elseif ( function_exists( 'svn_log' ) ) {
 13+ return new SubversionPecl( $repo );
 14+ } else {
 15+ return new SubversionShell( $repo );
 16+ }
 17+ }
 18+
 19+ function __construct( $repo ) {
 20+ $this->mRepo = $repo;
 21+ }
 22+
 23+ abstract function getFile( $path, $rev = null );
 24+
 25+ abstract function getDiff( $path, $rev1, $rev2 );
 26+
 27+ abstract function getDirList( $path, $rev = null );
 28+
 29+ /*
 30+ array of array(
 31+ 'rev' => 123,
 32+ 'author' => 'myname',
 33+ 'msg' => 'log message'
 34+ 'date' => '8601 date',
 35+ 'paths' => array(
 36+ array(
 37+ 'action' => one of M, A, D, R
 38+ 'path' => repo URL of file,
 39+ ),
 40+ ...
 41+ )
 42+ */
 43+ abstract function getLog( $path, $startRev = null, $endRev = null );
 44+
 45+ protected function _rev( $rev, $default ) {
 46+ if ( $rev === null ) {
 47+ return $default;
 48+ } else {
 49+ return intval( $rev );
 50+ }
 51+ }
 52+}
 53+
 54+/**
 55+ * Using the SVN PECL extension...
 56+ */
 57+class SubversionPecl extends SubversionAdaptor {
 58+ function getFile( $path, $rev = null ) {
 59+ return svn_cat( $this->mRepo . $path, $rev );
 60+ }
 61+
 62+ function getDiff( $path, $rev1, $rev2 ) {
 63+ list( $fout, $ferr ) = svn_diff(
 64+ $this->mRepo . $path, $rev1,
 65+ $this->mRepo . $path, $rev2 );
 66+
 67+ if ( $fout ) {
 68+ // We have to read out the file descriptors. :P
 69+ $out = '';
 70+ while ( !feof( $fout ) ) {
 71+ $out .= fgets( $fout );
 72+ }
 73+ fclose( $fout );
 74+ fclose( $ferr );
 75+
 76+ return $out;
 77+ } else {
 78+ return new MWException( "Diffing error" );
 79+ }
 80+ }
 81+
 82+ function getDirList( $path, $rev = null ) {
 83+ return svn_ls( $this->mRepo . $path,
 84+ $this->_rev( $rev, SVN_REVISION_HEAD ) );
 85+ }
 86+
 87+ function getLog( $path, $startRev = null, $endRev = null ) {
 88+ return svn_log( $this->mRepo . $path,
 89+ $this->_rev( $startRev, SVN_REVISION_INITIAL ),
 90+ $this->_rev( $endRev, SVN_REVISION_HEAD ) );
 91+ }
 92+}
 93+
 94+/**
 95+ * Using the thingy-bobber
 96+ */
 97+class SubversionShell extends SubversionAdaptor {
 98+ function getFile( $path, $rev = null ) {
 99+ if ( $rev )
 100+ $path .= "@$rev";
 101+ $command = sprintf(
 102+ "svn cat %s %s",
 103+ $this->getExtraArgs(),
 104+ wfEscapeShellArg( $this->mRepo . $path ) );
 105+
 106+ return wfShellExec( $command );
 107+ }
 108+
 109+ function getDiff( $path, $rev1, $rev2 ) {
 110+ $command = sprintf(
 111+ "svn diff -r%d:%d %s %s",
 112+ intval( $rev1 ),
 113+ intval( $rev2 ),
 114+ $this->getExtraArgs(),
 115+ wfEscapeShellArg( $this->mRepo . $path ) );
 116+
 117+ return wfShellExec( $command );
 118+ }
 119+
 120+ function getLog( $path, $startRev = null, $endRev = null ) {
 121+ $lang = wfIsWindows() ? "" : "LC_ALL=en_US.utf-8 ";
 122+ $command = sprintf(
 123+ "{$lang}svn log -v -r%s:%s %s %s",
 124+ wfEscapeShellArg( $this->_rev( $startRev, 'BASE' ) ),
 125+ wfEscapeShellArg( $this->_rev( $endRev, 'HEAD' ) ),
 126+ $this->getExtraArgs(),
 127+ wfEscapeShellArg( $this->mRepo . $path ) );
 128+
 129+ $lines = explode( "\n", wfShellExec( $command ) );
 130+ $out = array();
 131+
 132+ $divider = str_repeat( '-', 72 );
 133+ $formats = array(
 134+ 'rev' => '/^r(\d+)$/',
 135+ 'author' => '/^(.*)$/',
 136+ 'date' => '/^(.*?) \(.*\)$/',
 137+ 'lines' => '/^(\d+) lines?$/',
 138+ );
 139+ $state = "start";
 140+ foreach ( $lines as $line ) {
 141+ $line = rtrim( $line );
 142+
 143+ switch( $state ) {
 144+ case "start":
 145+ if ( $line == $divider ) {
 146+ $state = "revdata";
 147+ break;
 148+ } else {
 149+ return $out;
 150+ # throw new MWException( "Unexpected start line: $line" );
 151+ }
 152+ case "revdata":
 153+ if ( $line == "" ) {
 154+ $state = "done";
 155+ break;
 156+ }
 157+ $data = array();
 158+ $bits = explode( " | ", $line );
 159+ $i = 0;
 160+ foreach ( $formats as $key => $regex ) {
 161+ $text = $bits[$i++];
 162+ if ( preg_match( $regex, $text, $matches ) ) {
 163+ $data[$key] = $matches[1];
 164+ } else {
 165+ throw new MWException(
 166+ "Unexpected format for $key in '$text'" );
 167+ }
 168+ }
 169+ $data['msg'] = '';
 170+ $data['paths'] = array();
 171+ $state = 'changedpaths';
 172+ break;
 173+ case "changedpaths":
 174+ if ( $line == "Changed paths:" ) { // broken when svn messages are not in English
 175+ $state = "path";
 176+ } elseif ( $line == "" ) {
 177+ // No changed paths?
 178+ $state = "msg";
 179+ } else {
 180+ throw new MWException(
 181+ "Expected 'Changed paths:' or '', got '$line'" );
 182+ }
 183+ break;
 184+ case "path":
 185+ if ( $line == "" ) {
 186+ // Out of paths. Move on to the message...
 187+ $state = 'msg';
 188+ } else {
 189+ if ( preg_match( '/^ (.) (.*)$/', $line, $matches ) ) {
 190+ $data['paths'][] = array(
 191+ 'action' => $matches[1],
 192+ 'path' => $matches[2] );
 193+ }
 194+ }
 195+ break;
 196+ case "msg":
 197+ $data['msg'] .= $line;
 198+ if ( --$data['lines'] ) {
 199+ $data['msg'] .= "\n";
 200+ } else {
 201+ unset( $data['lines'] );
 202+ $out[] = $data;
 203+ $state = "start";
 204+ }
 205+ break;
 206+ case "done":
 207+ throw new MWException( "Unexpected input after end: $line" );
 208+ default:
 209+ throw new MWException( "Invalid state '$state'" );
 210+ }
 211+ }
 212+
 213+ return $out;
 214+ }
 215+
 216+ function getDirList( $path, $rev = null ) {
 217+ $command = sprintf(
 218+ "svn list --xml -r%s %s %s",
 219+ wfEscapeShellArg( $this->_rev( $rev, 'HEAD' ) ),
 220+ $this->getExtraArgs(),
 221+ wfEscapeShellArg( $this->mRepo . $path ) );
 222+ $document = new DOMDocument();
 223+
 224+ if ( !@$document->loadXML( wfShellExec( $command ) ) )
 225+ // svn list --xml returns invalid XML if the file does not exist
 226+ // FIXME: report bug upstream
 227+ return false;
 228+
 229+ $entries = $document->getElementsByTagName( 'entry' );
 230+ $result = array();
 231+ foreach ( $entries as $entry ) {
 232+ $item = array();
 233+ $item['type'] = $entry->getAttribute( 'kind' );
 234+ foreach ( $entry->childNodes as $child ) {
 235+ switch ( $child->nodeName ) {
 236+ case 'name':
 237+ $item['name'] = $child->textContent;
 238+ break;
 239+ case 'size':
 240+ $item['size'] = intval( $child->textContent );
 241+ break;
 242+ case 'commit':
 243+ $item['created_rev'] = intval( $child->getAttribute( 'revision' ) );
 244+ foreach ( $child->childNodes as $commitEntry ) {
 245+ switch ( $commitEntry->nodeName ) {
 246+ case 'author':
 247+ $item['last_author'] = $commitEntry->textContent;
 248+ break;
 249+ case 'date':
 250+ $item['time_t'] = wfTimestamp( TS_UNIX, $commitEntry->textContent );
 251+ break;
 252+ }
 253+ }
 254+ break;
 255+ }
 256+ }
 257+ $result[] = $item;
 258+ }
 259+ return $result;
 260+ }
 261+
 262+ /**
 263+ * Returns a string of extra arguments to be passed into the shell commands
 264+ */
 265+ private function getExtraArgs() {
 266+ global $wgSubversionOptions, $wgSubversionUser, $wgSubversionPassword;
 267+ $args = $wgSubversionOptions;
 268+ if ( $wgSubversionUser ) {
 269+ $args .= ' --username ' . wfEscapeShellArg( $wgSubversionUser )
 270+ . ' --password ' . wfEscapeShellArg( $wgSubversionPassword );
 271+ }
 272+ return $args;
 273+ }
 274+}
 275+
 276+/**
 277+ * Using a remote JSON proxy
 278+ */
 279+class SubversionProxy extends SubversionAdaptor {
 280+ function __construct( $repo, $proxy, $timeout = 30 ) {
 281+ parent::__construct( $repo );
 282+ $this->mProxy = $proxy;
 283+ $this->mTimeout = $timeout;
 284+ }
 285+
 286+ function getFile( $path, $rev = null ) {
 287+ throw new MWException( "NYI" );
 288+ }
 289+
 290+ function getDiff( $path, $rev1, $rev2 ) {
 291+ return $this->_proxy( array(
 292+ 'action' => 'diff',
 293+ 'path' => $path,
 294+ 'rev1' => $rev1,
 295+ 'rev2' => $rev2 ) );
 296+ }
 297+
 298+ function getLog( $path, $startRev = null, $endRev = null ) {
 299+ return $this->_proxy( array(
 300+ 'action' => 'log',
 301+ 'path' => $path,
 302+ 'start' => $startRev,
 303+ 'end' => $endRev ) );
 304+ }
 305+
 306+ function getDirList( $path, $rev = null ) {
 307+ return $this->_proxy( array(
 308+ 'action' => 'list',
 309+ 'path' => $path,
 310+ 'rev' => $rev ) );
 311+ }
 312+
 313+ protected function _proxy( $params ) {
 314+ foreach ( $params as $key => $val ) {
 315+ if ( is_null( $val ) ) {
 316+ // Don't pass nulls to remote
 317+ unset( $params[$key] );
 318+ }
 319+ }
 320+ $target = $this->mProxy . '?' . wfArrayToCgi( $params );
 321+ $blob = Http::get( $target, $this->mTimeout );
 322+ if ( $blob === false ) {
 323+ throw new MWException( "SVN proxy error" );
 324+ }
 325+ $data = unserialize( $blob );
 326+ return $data;
 327+ }
 328+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/Subversion.php
___________________________________________________________________
Added: svn:eol-style
1329 + native
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodePropChange.php
@@ -0,0 +1,24 @@
 2+<?php
 3+if ( !defined( 'MEDIAWIKI' ) ) die();
 4+
 5+class CodePropChange {
 6+ function __construct( $rev ) {
 7+ $this->rev = $rev;
 8+ }
 9+
 10+ static function newFromRow( $rev, $row ) {
 11+ return self::newFromData( $rev, get_object_vars( $row ) );
 12+ }
 13+
 14+ static function newFromData( $rev, $data ) {
 15+ $change = new CodeComment( $rev );
 16+ $change->attrib = $data['cpc_attrib'];
 17+ $change->removed = $data['cpc_removed'];
 18+ $change->added = $data['cpc_added'];
 19+ $change->user = $data['cpc_user'];
 20+ // We'd prefer the up to date user table name
 21+ $change->userText = isset( $data['user_name'] ) ? $data['user_name'] : $data['cpc_user_text'];
 22+ $change->timestamp = wfTimestamp( TS_MW, $data['cpc_timestamp'] );
 23+ return $change;
 24+ }
 25+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodePropChange.php
___________________________________________________________________
Added: svn:eol-style
126 + native
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestSuite.php
@@ -0,0 +1,38 @@
 2+<?php
 3+
 4+class CodeTestSuite {
 5+ public static function newFromRow( CodeRepository $repo, $row ) {
 6+ $suite = new CodeTestSuite();
 7+ $suite->id = intval( $row->ctsuite_id );
 8+ $suite->repo = $repo;
 9+ $suite->repoId = $repo->getId();
 10+ $suite->branchPath = $row->ctsuite_branch_path;
 11+ $suite->name = $row->ctsuite_name;
 12+ $suite->desc = $row->ctsuite_desc;
 13+ return $suite;
 14+ }
 15+
 16+ function getRun( $revId ) {
 17+ return CodeTestRun::newFromRevId( $this, $revId );
 18+ }
 19+
 20+ function setStatus( $revId, $status ) {
 21+ $run = $this->getRun( $revId );
 22+ if( $run ) {
 23+ $run->setStatus( $status );
 24+ } else {
 25+ $run = CodeTestRun::insertRun( $this, $revId, $status );
 26+ }
 27+ return $run;
 28+ }
 29+
 30+ function saveResults( $revId, $results ) {
 31+ $run = $this->getRun( $revId );
 32+ if( $run ) {
 33+ $run->saveResults( $results );
 34+ } else {
 35+ $run = CodeTestRun::insertRun( $this, $revId, "complete", $results );
 36+ }
 37+ return $run;
 38+ }
 39+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestSuite.php
___________________________________________________________________
Added: svn:eol-style
140 + native
Index: branches/wmf-deployment/extensions/CodeReview/backend/DiffHighlighter.php
@@ -0,0 +1,64 @@
 2+<?php
 3+
 4+/**
 5+ * Highlight a SVN diff for easier readibility
 6+ */
 7+class CodeDiffHighlighter {
 8+
 9+ /**
 10+ * Main entry point. Given a diff text, highlight it
 11+ * and wrap it in a div
 12+ * @param $text string Text to highlight
 13+ * @return string
 14+ */
 15+ function render( $text ) {
 16+ return '<pre class="mw-codereview-diff">' .
 17+ $this->splitLines( $text ) .
 18+ "</pre>\n";
 19+ }
 20+
 21+ /**
 22+ * Given a bunch of text, split it into individual
 23+ * lines, color them, then put it back into one big
 24+ * string
 25+ * @param $text string Text to split and highlight
 26+ * @return string
 27+ */
 28+ function splitLines( $text ) {
 29+ return implode( "\n",
 30+ array_map( array( $this, 'colorLine' ),
 31+ explode( "\n", $text ) ) );
 32+ }
 33+
 34+ /**
 35+ * Turn a diff line into a properly formatted string suitable
 36+ * for output
 37+ * @param $line string Line from a diff
 38+ * @return string
 39+ */
 40+ function colorLine( $line ) {
 41+ list( $element, $attribs ) = $this->tagForLine( $line );
 42+ return Xml::element( $element, $attribs, $line );
 43+ }
 44+
 45+ /**
 46+ * Take a line of a diff and apply the appropriate stylings
 47+ * @param $line string Line to check
 48+ * @return array
 49+ */
 50+ function tagForLine( $line ) {
 51+ static $default = array( 'span', array() );
 52+ static $tags = array(
 53+ '-' => array( 'del', array() ),
 54+ '+' => array( 'ins', array() ),
 55+ '@' => array( 'span', array( 'class' => 'meta' ) ),
 56+ ' ' => array( 'span', array() ),
 57+ );
 58+ $first = substr( $line, 0, 1 );
 59+ if ( isset( $tags[$first] ) )
 60+ return $tags[$first];
 61+ else
 62+ return $default;
 63+ }
 64+
 65+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/DiffHighlighter.php
___________________________________________________________________
Added: svn:eol-style
166 + native
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeRevision.php
@@ -0,0 +1,681 @@
 2+<?php
 3+if ( !defined( 'MEDIAWIKI' ) ) die();
 4+
 5+class CodeRevision {
 6+ public static function newFromSvn( CodeRepository $repo, $data ) {
 7+ $rev = new CodeRevision();
 8+ $rev->mRepoId = $repo->getId();
 9+ $rev->mRepo = $repo;
 10+ $rev->mId = intval( $data['rev'] );
 11+ $rev->mAuthor = $data['author'];
 12+ $rev->mTimestamp = wfTimestamp( TS_MW, strtotime( $data['date'] ) );
 13+ $rev->mMessage = rtrim( $data['msg'] );
 14+ $rev->mPaths = $data['paths'];
 15+ $rev->mStatus = 'new';
 16+
 17+ $common = null;
 18+ if ( $rev->mPaths ) {
 19+ if ( count( $rev->mPaths ) == 1 )
 20+ $common = $rev->mPaths[0]['path'];
 21+ else {
 22+ $first = array_shift( $rev->mPaths );
 23+
 24+ $common = explode( '/', $first['path'] );
 25+
 26+ foreach ( $rev->mPaths as $path ) {
 27+ $compare = explode( '/', $path['path'] );
 28+
 29+ // make sure $common is the shortest path
 30+ if ( count( $compare ) < count( $common ) )
 31+ list( $compare, $common ) = array( $common, $compare );
 32+
 33+ $tmp = array();
 34+ foreach ( $common as $k => $v )
 35+ if ( $v == $compare[$k] ) $tmp[] = $v;
 36+ else break;
 37+ $common = $tmp;
 38+ }
 39+ $common = implode( '/', $common );
 40+
 41+ array_unshift( $rev->mPaths, $first );
 42+ }
 43+ }
 44+ $rev->mCommonPath = $common;
 45+ return $rev;
 46+ }
 47+
 48+ public static function newFromRow( CodeRepository $repo, $row ) {
 49+ $rev = new CodeRevision();
 50+ $rev->mRepoId = intval( $row->cr_repo_id );
 51+ if ( $rev->mRepoId != $repo->getId() ) {
 52+ throw new MWException( "Invalid repo ID in " . __METHOD__ );
 53+ }
 54+ $rev->mRepo = $repo;
 55+ $rev->mId = intval( $row->cr_id );
 56+ $rev->mAuthor = $row->cr_author;
 57+ $rev->mTimestamp = wfTimestamp( TS_MW, $row->cr_timestamp );
 58+ $rev->mMessage = $row->cr_message;
 59+ $rev->mStatus = $row->cr_status;
 60+ $rev->mCommonPath = $row->cr_path;
 61+ return $rev;
 62+ }
 63+
 64+ public function getId() {
 65+ return intval( $this->mId );
 66+ }
 67+
 68+ public function getRepoId() {
 69+ return intval( $this->mRepoId );
 70+ }
 71+
 72+ public function getAuthor() {
 73+ return $this->mAuthor;
 74+ }
 75+
 76+ public function getWikiUser() {
 77+ return $this->mRepo->authorWikiUser( $this->getAuthor() );
 78+ }
 79+
 80+ public function getTimestamp() {
 81+ return $this->mTimestamp;
 82+ }
 83+
 84+ public function getMessage() {
 85+ return $this->mMessage;
 86+ }
 87+
 88+ public function getStatus() {
 89+ return $this->mStatus;
 90+ }
 91+
 92+ public function getCommonPath() {
 93+ return $this->mCommonPath;
 94+ }
 95+
 96+ public static function getPossibleStates() {
 97+ return array( 'new', 'fixme', 'reverted', 'resolved', 'ok', 'verified', 'deferred' );
 98+ }
 99+
 100+ public function isValidStatus( $status ) {
 101+ return in_array( $status, self::getPossibleStates(), true );
 102+ }
 103+
 104+ public function setStatus( $status, $user ) {
 105+ if ( !$this->isValidStatus( $status ) ) {
 106+ throw new MWException( "Tried to save invalid code revision status" );
 107+ }
 108+ // Get the old status from the master
 109+ $dbw = wfGetDB( DB_MASTER );
 110+ $oldStatus = $dbw->selectField( 'code_rev',
 111+ 'cr_status',
 112+ array( 'cr_repo_id' => $this->mRepoId, 'cr_id' => $this->mId ),
 113+ __METHOD__
 114+ );
 115+ if ( $oldStatus === $status ) {
 116+ return false; // nothing to do here
 117+ }
 118+ // Update status
 119+ $this->mStatus = $status;
 120+ $dbw->update( 'code_rev',
 121+ array( 'cr_status' => $status ),
 122+ array(
 123+ 'cr_repo_id' => $this->mRepoId,
 124+ 'cr_id' => $this->mId ),
 125+ __METHOD__
 126+ );
 127+ // Log this change
 128+ if ( $user && $user->getId() ) {
 129+ $dbw->insert( 'code_prop_changes',
 130+ array(
 131+ 'cpc_repo_id' => $this->getRepoId(),
 132+ 'cpc_rev_id' => $this->getId(),
 133+ 'cpc_attrib' => 'status',
 134+ 'cpc_removed' => $oldStatus,
 135+ 'cpc_added' => $status,
 136+ 'cpc_timestamp' => $dbw->timestamp(),
 137+ 'cpc_user' => $user->getId(),
 138+ 'cpc_user_text' => $user->getName()
 139+ ),
 140+ __METHOD__
 141+ );
 142+ }
 143+ return true;
 144+ }
 145+
 146+ /**
 147+ * Quickie protection against huuuuuuuuge batch inserts
 148+ */
 149+ protected function insertChunks( $db, $table, $data, $method, $options=array() ) {
 150+ $chunkSize = 100;
 151+ for( $i = 0; $i < count( $data ); $i += $chunkSize ) {
 152+ $db->insert( 'code_paths',
 153+ array_slice( $data, $i, $chunkSize ),
 154+ __METHOD__,
 155+ array( 'IGNORE' ) );
 156+ }
 157+ }
 158+
 159+ public function save() {
 160+ $dbw = wfGetDB( DB_MASTER );
 161+ $dbw->begin();
 162+
 163+ $dbw->insert( 'code_rev',
 164+ array(
 165+ 'cr_repo_id' => $this->mRepoId,
 166+ 'cr_id' => $this->mId,
 167+ 'cr_author' => $this->mAuthor,
 168+ 'cr_timestamp' => $dbw->timestamp( $this->mTimestamp ),
 169+ 'cr_message' => $this->mMessage,
 170+ 'cr_status' => $this->mStatus,
 171+ 'cr_path' => $this->mCommonPath ),
 172+ __METHOD__,
 173+ array( 'IGNORE' )
 174+ );
 175+ // Already exists? Update the row!
 176+ $newRevision = $dbw->affectedRows() > 0;
 177+ if ( !$newRevision ) {
 178+ $dbw->update( 'code_rev',
 179+ array(
 180+ 'cr_author' => $this->mAuthor,
 181+ 'cr_timestamp' => $dbw->timestamp( $this->mTimestamp ),
 182+ 'cr_message' => $this->mMessage,
 183+ 'cr_path' => $this->mCommonPath ),
 184+ array(
 185+ 'cr_repo_id' => $this->mRepoId,
 186+ 'cr_id' => $this->mId ),
 187+ __METHOD__
 188+ );
 189+ }
 190+ // Update path tracking used for output and searching
 191+ if ( $this->mPaths ) {
 192+ $data = array();
 193+ foreach ( $this->mPaths as $path ) {
 194+ $data[] = array(
 195+ 'cp_repo_id' => $this->mRepoId,
 196+ 'cp_rev_id' => $this->mId,
 197+ 'cp_path' => $path['path'],
 198+ 'cp_action' => $path['action'] );
 199+ }
 200+ $this->insertChunks( $dbw, 'code_paths', $data, __METHOD__, array( 'IGNORE' ) );
 201+ }
 202+ // Update bug references table...
 203+ $affectedBugs = array();
 204+ if ( preg_match_all( '/\bbug (\d+)\b/', $this->mMessage, $m ) ) {
 205+ $data = array();
 206+ foreach( $m[1] as $bug ) {
 207+ $data[] = array(
 208+ 'cb_repo_id' => $this->mRepoId,
 209+ 'cb_from' => $this->mId,
 210+ 'cb_bug' => $bug
 211+ );
 212+ $affectedBugs[] = intval($bug);
 213+ }
 214+ $dbw->insert( 'code_bugs', $data, __METHOD__, array( 'IGNORE' ) );
 215+ }
 216+ // Get the revisions this commit references...
 217+ $affectedRevs = array();
 218+ if ( preg_match_all( '/\br(\d{2,})\b/', $this->mMessage, $m ) ) {
 219+ foreach( $m[1] as $rev ) {
 220+ $affectedRevs[] = intval($rev);
 221+ }
 222+ }
 223+ // Also, get previous revisions that have bugs in common...
 224+ if( count($affectedBugs) ) {
 225+ $res = $dbw->select( 'code_bugs',
 226+ array( 'cb_from' ),
 227+ array(
 228+ 'cb_repo_id' => $this->mRepoId,
 229+ 'cb_bug' => $affectedBugs,
 230+ 'cb_from < '.intval($this->mId), # just in case
 231+ ),
 232+ __METHOD__,
 233+ array( 'USE INDEX' => 'cb_repo_id' )
 234+ );
 235+ foreach( $res as $row ) {
 236+ $affectedRevs[] = intval($row->cb_from);
 237+ }
 238+ }
 239+ // Filter any duplicate revisions
 240+ if( count($affectedRevs) ) {
 241+ $data = array();
 242+ $affectedRevs = array_unique($affectedRevs);
 243+ foreach( $affectedRevs as $rev ) {
 244+ $data[] = array(
 245+ 'cf_repo_id' => $this->mRepoId,
 246+ 'cf_from' => $this->mId,
 247+ 'cf_to' => $rev
 248+ );
 249+ $affectedRevs[] = intval($rev);
 250+ }
 251+ $dbw->insert( 'code_relations', $data, __METHOD__, array( 'IGNORE' ) );
 252+ }
 253+ // Email the authors of revisions that this follows up on
 254+ if( $newRevision && count($affectedRevs) > 0 ) {
 255+ // Get committer wiki user name, or repo name at least
 256+ $user = $this->mRepo->authorWikiUser( $this->mAuthor );
 257+ $committer = $user ? $user->getName() : htmlspecialchars($this->mAuthor);
 258+ // Get the authors of these revisions
 259+ $res = $dbw->select( 'code_rev',
 260+ array( 'cr_author', 'cr_id' ),
 261+ array(
 262+ 'cr_repo_id' => $this->mRepoId,
 263+ 'cr_id' => $affectedRevs,
 264+ 'cr_id < '.intval($this->mId), # just in case
 265+ // No sense in notifying if it's the same person
 266+ 'cr_author != '.$dbw->addQuotes($this->mAuthor)
 267+ ),
 268+ __METHOD__,
 269+ array( 'USE INDEX' => 'PRIMARY' )
 270+ );
 271+ // Get repo and build comment title (for url)
 272+ $title = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mId );
 273+ $url = $title->getFullUrl();
 274+ wfLoadExtensionMessages( 'CodeReview' );
 275+ foreach( $res as $row ) {
 276+ $user = $this->mRepo->authorWikiUser( $row->cr_author );
 277+ // User must exist on wiki and have a valid email addy
 278+ if( !$user || !$user->canReceiveEmail() ) continue;
 279+ // Send message in receiver's language
 280+ $lang = array( 'language' => $user->getOption( 'language' ) );
 281+ $user->sendMail(
 282+ wfMsgExt( 'codereview-email-subj2', $lang, $this->mRepo->getName(), $row->cr_id ),
 283+ wfMsgExt( 'codereview-email-body2', $lang, $committer, $row->cr_id, $url, $this->mMessage )
 284+ );
 285+ }
 286+ }
 287+ $dbw->commit();
 288+ }
 289+
 290+ public function getModifiedPaths() {
 291+ $dbr = wfGetDB( DB_SLAVE );
 292+ return $dbr->select(
 293+ 'code_paths',
 294+ array( 'cp_path', 'cp_action' ),
 295+ array( 'cp_repo_id' => $this->mRepoId, 'cp_rev_id' => $this->mId ),
 296+ __METHOD__
 297+ );
 298+ }
 299+
 300+ public function isDiffable() {
 301+ $paths = $this->getModifiedPaths();
 302+ if ( !$paths->numRows() || $paths->numRows() > 20 ) {
 303+ return false; // things need to get done this year
 304+ }
 305+ return true;
 306+ }
 307+
 308+ public function previewComment( $text, $review, $parent = null ) {
 309+ $data = $this->commentData( $text, $review, $parent );
 310+ $data['cc_id'] = null;
 311+ return CodeComment::newFromData( $this, $data );
 312+ }
 313+
 314+ public function saveComment( $text, $review, $parent = null ) {
 315+ global $wgUser;
 316+ if ( !strlen( $text ) ) {
 317+ return 0;
 318+ }
 319+ $dbw = wfGetDB( DB_MASTER );
 320+ $data = $this->commentData( $text, $review, $parent );
 321+
 322+ $dbw->begin();
 323+ $data['cc_id'] = $dbw->nextSequenceValue( 'code_comment_cc_id' );
 324+ $dbw->insert( 'code_comment', $data, __METHOD__ );
 325+ $commentId = $dbw->insertId();
 326+ $dbw->commit();
 327+
 328+ // Give email notices to committer and commenters
 329+ global $wgCodeReviewENotif, $wgEnableEmail;
 330+ if ( $wgCodeReviewENotif && $wgEnableEmail ) {
 331+ // Make list of users to send emails to
 332+ $users = $this->getCommentingUsers();
 333+ if ( $user = $this->getWikiUser() ) {
 334+ $users[$user->getId()] = $user;
 335+ }
 336+ // Get repo and build comment title (for url)
 337+ $title = SpecialPage::getTitleFor( 'Code', $this->mRepo->getName() . '/' . $this->mId );
 338+ $title->setFragment( "#c{$commentId}" );
 339+ $url = $title->getFullUrl();
 340+ foreach ( $users as $userId => $user ) {
 341+ // No sense in notifying this commenter
 342+ if ( $wgUser->getId() == $user->getId() ) {
 343+ continue;
 344+ }
 345+ // Send message in receiver's language
 346+ $lang = array( 'language' => $user->getOption( 'language' ) );
 347+ if ( $user->canReceiveEmail() ) {
 348+ $user->sendMail(
 349+ wfMsgExt( 'codereview-email-subj', $lang, $this->mRepo->getName(), $this->mId ),
 350+ wfMsgExt( 'codereview-email-body', $lang, $wgUser->getName(), $url, $this->mId, $text )
 351+ );
 352+ }
 353+ }
 354+ }
 355+
 356+ return $commentId;
 357+ }
 358+
 359+ protected function commentData( $text, $review, $parent = null ) {
 360+ global $wgUser;
 361+ $dbw = wfGetDB( DB_MASTER );
 362+ $ts = wfTimestamp( TS_MW );
 363+ $sortkey = $this->threadedSortkey( $parent, $ts );
 364+ return array(
 365+ 'cc_repo_id' => $this->mRepoId,
 366+ 'cc_rev_id' => $this->mId,
 367+ 'cc_text' => $text,
 368+ 'cc_parent' => $parent,
 369+ 'cc_user' => $wgUser->getId(),
 370+ 'cc_user_text' => $wgUser->getName(),
 371+ 'cc_timestamp' => $dbw->timestamp( $ts ),
 372+ 'cc_review' => $review,
 373+ 'cc_sortkey' => $sortkey );
 374+ }
 375+
 376+ protected function threadedSortKey( $parent, $ts ) {
 377+ if ( $parent ) {
 378+ // We construct a threaded sort key by concatenating the timestamps
 379+ // of all our parent comments
 380+ $dbw = wfGetDB( DB_MASTER );
 381+ $parentKey = $dbw->selectField( 'code_comment',
 382+ 'cc_sortkey',
 383+ array( 'cc_id' => $parent ),
 384+ __METHOD__ );
 385+ if ( $parentKey ) {
 386+ return $parentKey . ',' . $ts;
 387+ } else {
 388+ // hmmmm
 389+ throw new MWException( 'Invalid parent submission' );
 390+ }
 391+ } else {
 392+ return $ts;
 393+ }
 394+ }
 395+
 396+ public function getComments() {
 397+ $dbr = wfGetDB( DB_SLAVE );
 398+ $result = $dbr->select( 'code_comment',
 399+ array(
 400+ 'cc_id',
 401+ 'cc_text',
 402+ 'cc_parent',
 403+ 'cc_user',
 404+ 'cc_user_text',
 405+ 'cc_timestamp',
 406+ 'cc_review',
 407+ 'cc_sortkey' ),
 408+ array(
 409+ 'cc_repo_id' => $this->mRepoId,
 410+ 'cc_rev_id' => $this->mId ),
 411+ __METHOD__,
 412+ array(
 413+ 'ORDER BY' => 'cc_sortkey' )
 414+ );
 415+ $comments = array();
 416+ foreach ( $result as $row ) {
 417+ $comments[] = CodeComment::newFromRow( $this, $row );
 418+ }
 419+ $result->free();
 420+ return $comments;
 421+ }
 422+
 423+ public function getPropChanges() {
 424+ $dbr = wfGetDB( DB_SLAVE );
 425+ $result = $dbr->select( array( 'code_prop_changes', 'user' ),
 426+ array(
 427+ 'cpc_attrib',
 428+ 'cpc_removed',
 429+ 'cpc_added',
 430+ 'cpc_timestamp',
 431+ 'cpc_user',
 432+ 'cpc_user_text',
 433+ 'user_name'
 434+ ), array(
 435+ 'cpc_repo_id' => $this->mRepoId,
 436+ 'cpc_rev_id' => $this->mId,
 437+ ),
 438+ __METHOD__,
 439+ array( 'ORDER BY' => 'cpc_timestamp DESC' ),
 440+ array( 'user' => array( 'LEFT JOIN', 'cpc_user = user_id' ) )
 441+ );
 442+ $changes = array();
 443+ foreach ( $result as $row ) {
 444+ $changes[] = CodePropChange::newFromRow( $this, $row );
 445+ }
 446+ $result->free();
 447+ return $changes;
 448+ }
 449+
 450+ protected function getCommentingUsers() {
 451+ $dbr = wfGetDB( DB_SLAVE );
 452+ $res = $dbr->select( 'code_comment',
 453+ 'DISTINCT(cc_user)',
 454+ array(
 455+ 'cc_repo_id' => $this->mRepoId,
 456+ 'cc_rev_id' => $this->mId,
 457+ 'cc_user != 0' // users only
 458+ ),
 459+ __METHOD__
 460+ );
 461+ $users = array();
 462+ while ( $row = $res->fetchObject() ) {
 463+ $users[$row->cc_user] = User::newFromId( $row->cc_user );
 464+ }
 465+ return $users;
 466+ }
 467+
 468+ public function getReferences() {
 469+ $refs = array();
 470+ $dbr = wfGetDB( DB_SLAVE );
 471+ $res = $dbr->select(
 472+ array( 'code_relations', 'code_rev' ),
 473+ array( 'cr_id', 'cr_status', 'cr_timestamp', 'cr_author', 'cr_message' ),
 474+ array(
 475+ 'cf_repo_id' => $this->mRepoId,
 476+ 'cf_to' => $this->mId,
 477+ 'cr_repo_id = cf_repo_id',
 478+ 'cr_id = cf_from'
 479+ ),
 480+ __METHOD__
 481+ );
 482+ while ( $row = $res->fetchObject() ) {
 483+ $refs[] = $row;
 484+ }
 485+ return $refs;
 486+ }
 487+
 488+ public function getTags( $from = DB_SLAVE ) {
 489+ $db = wfGetDB( $from );
 490+ $result = $db->select( 'code_tags',
 491+ array( 'ct_tag' ),
 492+ array(
 493+ 'ct_repo_id' => $this->mRepoId,
 494+ 'ct_rev_id' => $this->mId ),
 495+ __METHOD__ );
 496+
 497+ $tags = array();
 498+ foreach ( $result as $row ) {
 499+ $tags[] = $row->ct_tag;
 500+ }
 501+ return $tags;
 502+ }
 503+
 504+ public function changeTags( $addTags, $removeTags, $user = NULL ) {
 505+ // Get the current tags and see what changes
 506+ $tagsNow = $this->getTags( DB_MASTER );
 507+ // Normalize our input tags
 508+ $addTags = $this->normalizeTags( $addTags );
 509+ $removeTags = $this->normalizeTags( $removeTags );
 510+ $addTags = array_diff( $addTags, $tagsNow );
 511+ $removeTags = array_intersect( $removeTags, $tagsNow );
 512+ // Do the queries
 513+ $dbw = wfGetDB( DB_MASTER );
 514+ if ( $addTags ) {
 515+ $dbw->insert( 'code_tags',
 516+ $this->tagData( $addTags ),
 517+ __METHOD__,
 518+ array( 'IGNORE' )
 519+ );
 520+ }
 521+ if ( $removeTags ) {
 522+ $dbw->delete( 'code_tags',
 523+ array(
 524+ 'ct_repo_id' => $this->mRepoId,
 525+ 'ct_rev_id' => $this->mId,
 526+ 'ct_tag' => $removeTags ),
 527+ __METHOD__
 528+ );
 529+ }
 530+ // Log this change
 531+ if ( ( $removeTags || $addTags ) && $user && $user->getId() ) {
 532+ $dbw->insert( 'code_prop_changes',
 533+ array(
 534+ 'cpc_repo_id' => $this->getRepoId(),
 535+ 'cpc_rev_id' => $this->getId(),
 536+ 'cpc_attrib' => 'tags',
 537+ 'cpc_removed' => implode( ',', $removeTags ),
 538+ 'cpc_added' => implode( ',', $addTags ),
 539+ 'cpc_timestamp' => $dbw->timestamp(),
 540+ 'cpc_user' => $user->getId(),
 541+ 'cpc_user_text' => $user->getName()
 542+ ),
 543+ __METHOD__
 544+ );
 545+ }
 546+ }
 547+
 548+ protected function normalizeTags( $tags ) {
 549+ $out = array();
 550+ foreach ( $tags as $tag ) {
 551+ $out[] = $this->normalizeTag( $tag );
 552+ }
 553+ return $out;
 554+ }
 555+
 556+ protected function tagData( $tags ) {
 557+ $data = array();
 558+ foreach ( $tags as $tag ) {
 559+ $data[] = array(
 560+ 'ct_repo_id' => $this->mRepoId,
 561+ 'ct_rev_id' => $this->mId,
 562+ 'ct_tag' => $this->normalizeTag( $tag ) );
 563+ }
 564+ return $data;
 565+ }
 566+
 567+ public function normalizeTag( $tag ) {
 568+ global $wgContLang;
 569+ $lower = $wgContLang->lc( $tag );
 570+
 571+ $title = Title::newFromText( $tag );
 572+ if ( $title && $lower === $wgContLang->lc( $title->getPrefixedText() ) ) {
 573+ return $lower;
 574+ } else {
 575+ return false;
 576+ }
 577+ }
 578+
 579+ public function isValidTag( $tag ) {
 580+ return ( $this->normalizeTag( $tag ) !== false );
 581+ }
 582+
 583+ public function getTestRuns() {
 584+ $dbr = wfGetDB( DB_SLAVE );
 585+ $result = $dbr->select(
 586+ array(
 587+ 'code_test_suite',
 588+ 'code_test_run',
 589+ ),
 590+ '*',
 591+ array(
 592+ 'ctsuite_repo_id' => $this->mRepoId,
 593+ 'ctsuite_id=ctrun_suite_id',
 594+ 'ctrun_rev_id' => $this->mId,
 595+ ),
 596+ __METHOD__ );
 597+ $runs = array();
 598+ foreach( $result as $row ) {
 599+ $suite = CodeTestSuite::newFromRow( $this->mRepo, $row );
 600+ $runs[] = new CodeTestRun( $suite, $row );
 601+ }
 602+ return $runs;
 603+ }
 604+
 605+ public function getTestResults( $success = null ) {
 606+ $dbr = wfGetDB( DB_SLAVE );
 607+ $conds = array(
 608+ 'ctr_repo_id' => $this->mRepoId,
 609+ 'ctr_rev_id' => $this->mId,
 610+ 'ctr_case_id=ctc_id',
 611+ 'ctc_suite_id=cts_id' );
 612+ if( $success === true ) {
 613+ $conds['ctr_result'] = 1;
 614+ } elseif( $success === false ) {
 615+ $conds['ctr_result'] = 0;
 616+ }
 617+ $results = $dbr->select(
 618+ array(
 619+ 'code_test_result',
 620+ 'code_test_case',
 621+ 'code_test_suite',
 622+ ),
 623+ '*',
 624+ $conds,
 625+ __METHOD__ );
 626+ $out = array();
 627+ foreach( $results as $row ) {
 628+ $out[] = new CodeTestResult( $row );
 629+ }
 630+ return $out;
 631+ }
 632+
 633+ public function getPrevious() {
 634+ // hack!
 635+ if ( $this->mId > 1 ) {
 636+ return $this->mId - 1;
 637+ } else {
 638+ return false;
 639+ }
 640+ }
 641+
 642+ public function getNext() {
 643+ $dbr = wfGetDB( DB_SLAVE );
 644+ $encId = $dbr->addQuotes( $this->mId );
 645+ $row = $dbr->selectRow( 'code_rev',
 646+ 'cr_id',
 647+ array(
 648+ 'cr_repo_id' => $this->mRepoId,
 649+ "cr_id > $encId" ),
 650+ __METHOD__,
 651+ array(
 652+ 'ORDER BY' => 'cr_repo_id, cr_id',
 653+ 'LIMIT' => 1 ) );
 654+
 655+ if ( $row ) {
 656+ return intval( $row->cr_id );
 657+ } else {
 658+ return false;
 659+ }
 660+ }
 661+
 662+ public function getNextUnresolved() {
 663+ $dbr = wfGetDB( DB_SLAVE );
 664+ $encId = $dbr->addQuotes( $this->mId );
 665+ $row = $dbr->selectRow( 'code_rev',
 666+ 'cr_id',
 667+ array(
 668+ 'cr_repo_id' => $this->mRepoId,
 669+ "cr_id > $encId",
 670+ 'cr_status' => array( 'new', 'fixme' ) ),
 671+ __METHOD__,
 672+ array(
 673+ 'ORDER BY' => 'cr_repo_id, cr_id',
 674+ 'LIMIT' => 1 )
 675+ );
 676+ if ( $row ) {
 677+ return intval( $row->cr_id );
 678+ } else {
 679+ return false;
 680+ }
 681+ }
 682+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeRevision.php
___________________________________________________________________
Added: svn:eol-style
1683 + native
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestResult.php
@@ -0,0 +1,11 @@
 2+<?php
 3+
 4+class CodeTestResult {
 5+ function __construct( CodeTestRun $run, $row ) {
 6+ $this->run = $run;
 7+ $this->id = $row->ctresult_id;
 8+ $this->caseId = $row->ctresult_case_id;
 9+ $this->caseName = $row->ctcase_name;
 10+ $this->success = (bool)$row->ctresult_success;
 11+ }
 12+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeTestResult.php
___________________________________________________________________
Added: svn:eol-style
113 + native
Index: branches/wmf-deployment/extensions/CodeReview/backend/CodeRepository.php
@@ -0,0 +1,359 @@
 2+<?php
 3+if ( !defined( 'MEDIAWIKI' ) ) die();
 4+
 5+class CodeRepository {
 6+ static $userLinks = array();
 7+
 8+ public static function newFromName( $name ) {
 9+ $dbw = wfGetDB( DB_MASTER );
 10+ $row = $dbw->selectRow(
 11+ 'code_repo',
 12+ array(
 13+ 'repo_id',
 14+ 'repo_name',
 15+ 'repo_path',
 16+ 'repo_viewvc',
 17+ 'repo_bugzilla' ),
 18+ array( 'repo_name' => $name ),
 19+ __METHOD__ );
 20+
 21+ if ( $row ) {
 22+ return self::newFromRow( $row );
 23+ } else {
 24+ return null;
 25+ }
 26+ }
 27+
 28+ public static function newFromId( $id ) {
 29+ $dbw = wfGetDB( DB_MASTER );
 30+ $row = $dbw->selectRow(
 31+ 'code_repo',
 32+ array(
 33+ 'repo_id',
 34+ 'repo_name',
 35+ 'repo_path',
 36+ 'repo_viewvc',
 37+ 'repo_bugzilla' ),
 38+ array( 'repo_id' => intval( $id ) ),
 39+ __METHOD__ );
 40+
 41+ if ( $row ) {
 42+ return self::newFromRow( $row );
 43+ } else {
 44+ return null;
 45+ }
 46+ }
 47+
 48+ static function newFromRow( $row ) {
 49+ $repo = new CodeRepository();
 50+ $repo->mId = intval( $row->repo_id );
 51+ $repo->mName = $row->repo_name;
 52+ $repo->mPath = $row->repo_path;
 53+ $repo->mViewVc = $row->repo_viewvc;
 54+ $repo->mBugzilla = $row->repo_bugzilla;
 55+ return $repo;
 56+ }
 57+
 58+ static function getRepoList() {
 59+ $dbr = wfGetDB( DB_SLAVE );
 60+ $res = $dbr->select( 'code_repo', '*', array(), __METHOD__ );
 61+ $repos = array();
 62+ foreach ( $res as $row ) {
 63+ $repos[] = self::newFromRow( $row );
 64+ }
 65+ return $repos;
 66+ }
 67+
 68+ public function getId() {
 69+ return intval( $this->mId );
 70+ }
 71+
 72+ public function getName() {
 73+ return $this->mName;
 74+ }
 75+
 76+ public function getPath() {
 77+ return $this->mPath;
 78+ }
 79+
 80+ public function getViewVcBase() {
 81+ return $this->mViewVc;
 82+ }
 83+
 84+ /**
 85+ * Return a bug URL or false.
 86+ */
 87+ public function getBugPath( $bugId ) {
 88+ if ( $this->mBugzilla ) {
 89+ return str_replace( '$1',
 90+ urlencode( $bugId ), $this->mBugzilla );
 91+ }
 92+ return false;
 93+ }
 94+
 95+ public function getLastStoredRev() {
 96+ $dbr = wfGetDB( DB_SLAVE );
 97+ $row = $dbr->selectField(
 98+ 'code_rev',
 99+ 'MAX(cr_id)',
 100+ array( 'cr_repo_id' => $this->getId() ),
 101+ __METHOD__
 102+ );
 103+ return intval( $row );
 104+ }
 105+
 106+ public function getAuthorList() {
 107+ global $wgMemc;
 108+ $key = wfMemcKey( 'codereview', 'authors', $this->getId() );
 109+ $authors = $wgMemc->get( $key );
 110+ if ( is_array( $authors ) ) {
 111+ return $authors;
 112+ }
 113+ $dbr = wfGetDB( DB_SLAVE );
 114+ $res = $dbr->select(
 115+ 'code_rev',
 116+ array( 'cr_author', 'MAX(cr_timestamp) AS time' ),
 117+ array( 'cr_repo_id' => $this->getId() ),
 118+ __METHOD__,
 119+ array( 'GROUP BY' => 'cr_author',
 120+ 'ORDER BY' => 'time DESC', 'LIMIT' => 500 )
 121+ );
 122+ $authors = array();
 123+ while ( $row = $dbr->fetchObject( $res ) ) {
 124+ $authors[] = $row->cr_author;
 125+ }
 126+ $wgMemc->set( $key, $authors, 3600 * 24 * 3 );
 127+ return $authors;
 128+ }
 129+
 130+ public function getTagList() {
 131+ global $wgMemc;
 132+ $key = wfMemcKey( 'codereview', 'tags', $this->getId() );
 133+ $tags = $wgMemc->get( $key );
 134+ if ( is_array( $tags ) ) {
 135+ return $tags;
 136+ }
 137+ $dbr = wfGetDB( DB_SLAVE );
 138+ $res = $dbr->select(
 139+ 'code_tags',
 140+ array( 'ct_tag', 'COUNT(*) AS revs' ),
 141+ array( 'ct_repo_id' => $this->getId() ),
 142+ __METHOD__,
 143+ array( 'GROUP BY' => 'ct_tag',
 144+ 'ORDER BY' => 'revs DESC', 'LIMIT' => 500 )
 145+ );
 146+ $tags = array();
 147+ while ( $row = $dbr->fetchObject( $res ) ) {
 148+ $tags[] = $row->ct_tag;
 149+ }
 150+ $wgMemc->set( $key, $tags, 3600 * 24 * 3 );
 151+ return $tags;
 152+ }
 153+
 154+ /**
 155+ * Load a particular revision out of the DB
 156+ */
 157+ public function getRevision( $id ) {
 158+ if ( !$this->isValidRev( $id ) ) {
 159+ return null;
 160+ }
 161+ $dbr = wfGetDB( DB_SLAVE );
 162+ $row = $dbr->selectRow( 'code_rev',
 163+ '*',
 164+ array(
 165+ 'cr_id' => $id,
 166+ 'cr_repo_id' => $this->getId(),
 167+ ),
 168+ __METHOD__
 169+ );
 170+ if ( !$row )
 171+ throw new MWException( 'Failed to load expected revision data' );
 172+ return CodeRevision::newFromRow( $this, $row );
 173+ }
 174+
 175+ /**
 176+ * Load test suite information
 177+ */
 178+ public function getTestSuite( $name ) {
 179+ $dbr = wfGetDB( DB_SLAVE );
 180+ $row = $dbr->selectRow( 'code_test_suite',
 181+ '*',
 182+ array(
 183+ 'ctsuite_repo_id' => $this->getId(),
 184+ 'ctsuite_name' => $name,
 185+ ),
 186+ __METHOD__
 187+ );
 188+ if( $row ) {
 189+ return CodeTestSuite::newFromRow( $this, $row );
 190+ } else {
 191+ return null;
 192+ }
 193+ }
 194+
 195+ /**
 196+ * @param int $rev Revision ID
 197+ * @param $useCache 'skipcache' to avoid caching
 198+ * 'cached' to *only* fetch if cached
 199+ */
 200+ public function getDiff( $rev, $useCache = '' ) {
 201+ global $wgMemc;
 202+ wfProfileIn( __METHOD__ );
 203+
 204+ $rev1 = $rev - 1;
 205+ $rev2 = $rev;
 206+
 207+ $revision = $this->getRevision( $rev );
 208+ if ( $revision == null || !$revision->isDiffable() ) {
 209+ wfProfileOut( __METHOD__ );
 210+ return false;
 211+ }
 212+
 213+ # Try memcached...
 214+ $key = wfMemcKey( 'svn', md5( $this->mPath ), 'diff', $rev1, $rev2 );
 215+ if ( $useCache === 'skipcache' ) {
 216+ $data = NULL;
 217+ } else {
 218+ $data = $wgMemc->get( $key );
 219+ }
 220+
 221+ # Try the database...
 222+ if ( !$data && $useCache != 'skipcache' ) {
 223+ $dbr = wfGetDB( DB_SLAVE );
 224+ $row = $dbr->selectRow( 'code_rev',
 225+ array( 'cr_diff', 'cr_flags' ),
 226+ array( 'cr_repo_id' => $this->mId, 'cr_id' => $rev, 'cr_diff IS NOT NULL' ),
 227+ __METHOD__
 228+ );
 229+ if ( $row ) {
 230+ $flags = explode( ',', $row->cr_flags );
 231+ $data = $row->cr_diff;
 232+ // If the text was fetched without an error, convert it
 233+ if ( $data !== false && in_array( 'gzip', $flags ) ) {
 234+ # Deal with optional compression of archived pages.
 235+ # This can be done periodically via maintenance/compressOld.php, and
 236+ # as pages are saved if $wgCompressRevisions is set.
 237+ $data = gzinflate( $data );
 238+ }
 239+ }
 240+ }
 241+
 242+ # Generate the diff as needed...
 243+ if ( !$data && $useCache !== 'cached' ) {
 244+ $svn = SubversionAdaptor::newFromRepo( $this->mPath );
 245+ $data = $svn->getDiff( '', $rev1, $rev2 );
 246+ // Store to cache
 247+ $wgMemc->set( $key, $data, 3600 * 24 * 3 );
 248+ // Permanent DB storage
 249+ $storedData = $data;
 250+ $flags = Revision::compressRevisionText( $storedData );
 251+ $dbw = wfGetDB( DB_MASTER );
 252+ $dbw->update( 'code_rev',
 253+ array( 'cr_diff' => $storedData, 'cr_flags' => $flags ),
 254+ array( 'cr_repo_id' => $this->mId, 'cr_id' => $rev ),
 255+ __METHOD__
 256+ );
 257+ }
 258+ wfProfileOut( __METHOD__ );
 259+ return $data;
 260+ }
 261+
 262+ /**
 263+ * Is the requested revid a valid revision to show?
 264+ * @return bool
 265+ * @param $rev int Rev id to check
 266+ */
 267+ public function isValidRev( $rev ) {
 268+ $rev = intval( $rev );
 269+ if ( $rev > 0 && $rev <= $this->getLastStoredRev() ) {
 270+ return true;
 271+ }
 272+ return false;
 273+ }
 274+
 275+ /*
 276+ * Link the $author to the wikiuser $user
 277+ * @param string $author
 278+ * @param User $user
 279+ * @return bool success
 280+ */
 281+ public function linkUser( $author, User $user ) {
 282+ // We must link to an existing user
 283+ if ( !$user->getId() ) {
 284+ return false;
 285+ }
 286+ $dbw = wfGetDB( DB_MASTER );
 287+ // Insert in the auther -> user link row.
 288+ // Skip existing rows.
 289+ $dbw->insert( 'code_authors',
 290+ array(
 291+ 'ca_repo_id' => $this->getId(),
 292+ 'ca_author' => $author,
 293+ 'ca_user_text' => $user->getName()
 294+ ),
 295+ __METHOD__,
 296+ array( 'IGNORE' )
 297+ );
 298+ // If the last query already found a row, then update it.
 299+ if ( !$dbw->affectedRows() ) {
 300+ $dbw->update(
 301+ 'code_authors',
 302+ array( 'ca_user_text' => $user->getName() ),
 303+ array(
 304+ 'ca_repo_id' => $this->getId(),
 305+ 'ca_author' => $author,
 306+ ),
 307+ __METHOD__
 308+ );
 309+ }
 310+ self::$userLinks[$author] = $user;
 311+ return ( $dbw->affectedRows() > 0 );
 312+ }
 313+
 314+ /*
 315+ * Remove local user links for $author
 316+ * @param string $author
 317+ * @return bool success
 318+ */
 319+ public function unlinkUser( $author ) {
 320+ $dbw = wfGetDB( DB_MASTER );
 321+ $dbw->delete(
 322+ 'code_authors',
 323+ array(
 324+ 'ca_repo_id' => $this->getId(),
 325+ 'ca_author' => $author,
 326+ ),
 327+ __METHOD__
 328+ );
 329+ self::$userLinks[$author] = false;
 330+ return ( $dbw->affectedRows() > 0 );
 331+ }
 332+
 333+ /*
 334+ * returns a User object if $author has a wikiuser associated,
 335+ * or false
 336+ */
 337+ public function authorWikiUser( $author ) {
 338+ if ( isset( self::$userLinks[$author] ) )
 339+ return self::$userLinks[$author];
 340+
 341+ $dbr = wfGetDB( DB_SLAVE );
 342+ $wikiUser = $dbr->selectField(
 343+ 'code_authors',
 344+ 'ca_user_text',
 345+ array(
 346+ 'ca_repo_id' => $this->getId(),
 347+ 'ca_author' => $author,
 348+ ),
 349+ __METHOD__
 350+ );
 351+ $user = null;
 352+ if ( $wikiUser )
 353+ $user = User::newFromName( $wikiUser );
 354+ if ( $user instanceof User )
 355+ $res = $user;
 356+ else
 357+ $res = false;
 358+ return self::$userLinks[$author] = $res;
 359+ }
 360+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/backend/CodeRepository.php
___________________________________________________________________
Added: svn:eol-style
1361 + native
Index: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeUpdate.php
@@ -0,0 +1,104 @@
 2+<?php
 3+
 4+class ApiCodeUpdate extends ApiBase {
 5+
 6+ public function execute() {
 7+ global $wgUser;
 8+ // Before doing anything at all, let's check permissions
 9+ if( !$wgUser->isAllowed('codereview-use') ) {
 10+ $this->dieUsage('You don\'t have permission update code','permissiondenied');
 11+ }
 12+ $params = $this->extractRequestParams();
 13+
 14+ if ( !isset( $params['repo'] ) ) {
 15+ $this->dieUsageMsg( array( 'missingparam', 'repo' ) );
 16+ }
 17+ if ( !isset( $params['rev'] ) ) {
 18+ $this->dieUsageMsg( array( 'missingparam', 'rev' ) );
 19+ }
 20+
 21+ $repo = CodeRepository::newFromName( $params['repo'] );
 22+ if ( !$repo ) {
 23+ $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
 24+ }
 25+
 26+ $svn = SubversionAdaptor::newFromRepo( $repo->getPath() );
 27+ $lastStoredRev = $repo->getLastStoredRev();
 28+
 29+ if ( $lastStoredRev >= $params['rev'] ) {
 30+ // Nothing to do, we're up to date.
 31+ // Return an empty result
 32+ $this->getResult()->addValue( null, $this->getModuleName(), array() );
 33+ return;
 34+ }
 35+
 36+ // FIXME: this could be a lot?
 37+ $log = $svn->getLog( '', $lastStoredRev + 1, $params['rev'] );
 38+ if ( !$log ) {
 39+ // FIXME: When and how often does this happen?
 40+ // Should we use dieUsage() here instead?
 41+ ApiBase::dieDebug( __METHOD__, "Something awry..." );
 42+ }
 43+
 44+ $result = array();
 45+ foreach ( $log as $data ) {
 46+ $codeRev = CodeRevision::newFromSvn( $repo, $data );
 47+ $codeRev->save();
 48+ $result[] = array(
 49+ 'id' => $codeRev->getId(),
 50+ 'author' => $codeRev->getAuthor(),
 51+ 'timestamp' => wfTimestamp( TS_ISO_8601, $codeRev->getTimestamp() ),
 52+ 'message' => $codeRev->getMessage()
 53+ );
 54+ }
 55+ // Cache the diffs if there are a only a few.
 56+ // Mainly for WMF post-commit ping hook...
 57+ if ( count( $result ) <= 2 ) {
 58+ foreach ( $result as $revData ) {
 59+ $diff = $repo->getDiff( $revData['id'] ); // trigger caching
 60+ }
 61+ }
 62+ $this->getResult()->setIndexedTagName( $result, 'rev' );
 63+ $this->getResult()->addValue( null, $this->getModuleName(), $result );
 64+ }
 65+
 66+ public function mustBePosted() {
 67+ // Discourage casual browsing :)
 68+ return true;
 69+ }
 70+
 71+ public function isWriteMode() {
 72+ return true;
 73+ }
 74+
 75+ public function getAllowedParams() {
 76+ return array(
 77+ 'repo' => null,
 78+ 'rev' => array(
 79+ ApiBase::PARAM_TYPE => 'integer',
 80+ ApiBase::PARAM_MIN => 1
 81+ )
 82+ );
 83+ }
 84+
 85+ public function getParamDescription() {
 86+ return array(
 87+ 'repo' => 'Name of repository to update',
 88+ 'rev' => 'Revision ID number to update to' );
 89+ }
 90+
 91+ public function getDescription() {
 92+ return array(
 93+ 'Update CodeReview repository data from master revision control system.' );
 94+ }
 95+
 96+ public function getExamples() {
 97+ return array(
 98+ 'api.php?action=codeupdate&repo=MediaWiki&rev=42080',
 99+ );
 100+ }
 101+
 102+ public function getVersion() {
 103+ return __CLASS__ . ': $Id: ApiCodeUpdate.php 48928 2009-03-27 18:41:20Z catrope $';
 104+ }
 105+}
Index: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeTestUpload.php
@@ -0,0 +1,119 @@
 2+<?php
 3+
 4+class ApiCodeTestUpload extends ApiBase {
 5+
 6+ public function execute() {
 7+ global $wgUser;
 8+ // Before doing anything at all, let's check permissions
 9+ if( !$wgUser->isAllowed('codereview-use') ) {
 10+ $this->dieUsage('You don\'t have permission to upload test results', 'permissiondenied');
 11+ }
 12+ $params = $this->extractRequestParams();
 13+
 14+ $this->validateParams( $params );
 15+ $this->validateHmac( $params );
 16+
 17+ $repo = CodeRepository::newFromName( $params['repo'] );
 18+ if( !$repo ) {
 19+ $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
 20+ }
 21+
 22+ $suite = $repo->getTestSuite( $params['suite'] );
 23+ if( !$suite ) {
 24+ $this->dieUsage( "Invalid test suite ``{$params['suite']}''", 'invalidsuite' );
 25+ }
 26+
 27+ // Note that we might be testing a revision that hasn't gotten slurped in yet,
 28+ // so we won't reject data for revisions we don't know about yet.
 29+ $revId = intval( $params['rev'] );
 30+
 31+ $status = $params['status'];
 32+ if( $status == 'running' || $status == 'aborted' ) {
 33+ // Set the 'tests running' flag so we can mark it...
 34+ $suite->setStatus( $revId, $status );
 35+ } elseif( $status == 'complete' ) {
 36+ // Save data and mark running test as completed.
 37+ $results = json_decode( $params['results'], true );
 38+ if( !is_array( $results ) ) {
 39+ $this->dieUsage( "Invalid test result data", 'invalidresults' );
 40+ }
 41+ $suite->saveResults( $revId, $results );
 42+ }
 43+ }
 44+
 45+ protected function validateParams( $params ) {
 46+ $required = array( 'repo', 'suite', 'rev', 'status', 'hmac' );
 47+ if( isset( $params['status'] ) && $params['status'] == 'complete' ) {
 48+ $required[] = 'results';
 49+ }
 50+ foreach( $required as $arg ) {
 51+ if ( !isset( $params[$arg] ) ) {
 52+ $this->dieUsageMsg( array( 'missingparam', $arg ) );
 53+ }
 54+ }
 55+ }
 56+
 57+ protected function validateHmac( $params ) {
 58+ global $wgCodeReviewSharedSecret;
 59+
 60+ // Generate a hash MAC to validate our credentials
 61+ $message = array(
 62+ $params['repo'],
 63+ $params['suite'],
 64+ $params['rev'],
 65+ $params['status'],
 66+ );
 67+ if( $params['status'] == "complete" ) {
 68+ $message[] = $params['results'];
 69+ }
 70+ $hmac = hash_hmac( "sha1", implode( "|", $message ), $wgCodeReviewSharedSecret );
 71+ if( $hmac != $params['hmac'] ) {
 72+ $this->dieUsageMsg( array( 'invalidhmac', $params['hmac'] ) );
 73+ }
 74+ }
 75+
 76+ public function mustBePosted() {
 77+ // Discourage casual browsing :)
 78+ return true;
 79+ }
 80+
 81+ public function isWriteMode() {
 82+ return true;
 83+ }
 84+
 85+ public function getAllowedParams() {
 86+ return array(
 87+ 'repo' => null,
 88+ 'suite' => null,
 89+ 'rev' => array(
 90+ ApiBase::PARAM_TYPE => 'integer',
 91+ ApiBase::PARAM_MIN => 1
 92+ ),
 93+ 'status' => array(
 94+ ApiBase::PARAM_TYPE => array( 'running', 'complete', 'abort' ),
 95+ ),
 96+ 'hmac' => null,
 97+ 'results' => null,
 98+ );
 99+ }
 100+
 101+ public function getParamDescription() {
 102+ return array(
 103+ 'repo' => 'Name of repository to update',
 104+ 'suite' => 'Name of test suite to record run results for',
 105+ 'rev' => 'Revision ID tests were run against',
 106+ 'status' => 'Status of test run',
 107+ 'hmac' => 'HMAC validation',
 108+ 'results' => 'JSON-encoded map of test names to success results, for status "complete"',
 109+ );
 110+ }
 111+
 112+ public function getDescription() {
 113+ return array(
 114+ 'Upload CodeReview test run results from a test runner.' );
 115+ }
 116+
 117+ public function getVersion() {
 118+ return __CLASS__ . ': $Id: ApiCodeUpdate.php 48928 2009-03-27 18:41:20Z catrope $';
 119+ }
 120+}
Property changes on: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeTestUpload.php
___________________________________________________________________
Added: svn:eol-style
1121 + native
Index: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeComments.php
@@ -0,0 +1,139 @@
 2+<?php
 3+
 4+/*
 5+ * Created on Oct 29, 2008
 6+ *
 7+ * API for MediaWiki 1.8+
 8+ *
 9+ * Copyright (C) 2008 Bryan Tong Minh <Bryan.TongMinh@Gmail.com>
 10+ *
 11+ * This program is free software; you can redistribute it and/or modify
 12+ * it under the terms of the GNU General Public License as published by
 13+ * the Free Software Foundation; either version 2 of the License, or
 14+ * (at your option) any later version.
 15+ *
 16+ * This program is distributed in the hope that it will be useful,
 17+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 18+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 19+ * GNU General Public License for more details.
 20+ *
 21+ * You should have received a copy of the GNU General Public License along
 22+ * with this program; if not, write to the Free Software Foundation, Inc.,
 23+ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 24+ * http://www.gnu.org/copyleft/gpl.html
 25+ */
 26+
 27+class ApiCodeComments extends ApiQueryBase {
 28+ public function __construct( $query, $moduleName ) {
 29+ parent::__construct( $query, $moduleName, 'cc' );
 30+ }
 31+
 32+ public function execute() {
 33+ global $wgUser;
 34+ // Before doing anything at all, let's check permissions
 35+ if( !$wgUser->isAllowed('codereview-use') ) {
 36+ $this->dieUsage('You don\'t have permission to view code comments','permissiondenied');
 37+ }
 38+ $params = $this->extractRequestParams();
 39+ if ( is_null( $params['repo'] ) )
 40+ $this->dieUsageMsg( array( 'missingparam', 'repo' ) );
 41+ $this->props = array_flip( $params['prop'] );
 42+
 43+ $listview = new CodeCommentsListView( $params['repo'] );
 44+ if ( is_null( $listview->getRepo() ) )
 45+ $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
 46+ $pager = $listview->getPager();
 47+
 48+ if ( !is_null( $params['start'] ) )
 49+ $pager->setOffset( $this->getDB()->timestamp( $params['start'] ) );
 50+ $limit = $params['limit'];
 51+ $pager->setLimit( $limit );
 52+
 53+ $pager->doQuery();
 54+
 55+ $comments = $pager->getResult();
 56+ $data = array();
 57+
 58+ $count = 0;
 59+ $lastTimestamp = 0;
 60+ while ( $row = $comments->fetchObject() ) {
 61+ if ( $count == $limit ) {
 62+ $this->setContinueEnumParameter( 'start',
 63+ wfTimestamp( TS_ISO_8601, $lastTimestamp ) );
 64+ break;
 65+ }
 66+
 67+ $data[] = $this->formatRow( $row );
 68+ $lastTimestamp = $row->cc_timestamp;
 69+ $count++;
 70+ }
 71+ $comments->free();
 72+
 73+ $result = $this->getResult();
 74+ $result->setIndexedTagName( $data, 'comment' );
 75+ $result->addValue( 'query', $this->getModuleName(), $data );
 76+ }
 77+
 78+ private function formatRow( $row ) {
 79+ $item = array();
 80+ if ( isset( $this->props['timestamp'] ) )
 81+ $item['timestamp'] = wfTimestamp( TS_ISO_8601, $row->cc_timestamp );
 82+ if ( isset( $this->props['user'] ) )
 83+ $item['user'] = $row->cc_user_text;
 84+ if ( isset( $this->props['revision'] ) )
 85+ $item['status'] = $row->cr_status;
 86+ if ( isset( $this->props['text'] ) )
 87+ ApiResult::setContent( $item, $row->cc_text );
 88+ return $item;
 89+ }
 90+
 91+ public function getAllowedParams() {
 92+ return array (
 93+ 'repo' => null,
 94+ 'limit' => array (
 95+ ApiBase :: PARAM_DFLT => 10,
 96+ ApiBase :: PARAM_TYPE => 'limit',
 97+ ApiBase :: PARAM_MIN => 1,
 98+ ApiBase :: PARAM_MAX => ApiBase :: LIMIT_BIG1,
 99+ ApiBase :: PARAM_MAX2 => ApiBase :: LIMIT_BIG2
 100+ ),
 101+ 'start' => array(
 102+ ApiBase :: PARAM_TYPE => 'timestamp'
 103+ ),
 104+ 'prop' => array (
 105+ ApiBase :: PARAM_ISMULTI => true,
 106+ ApiBase :: PARAM_DFLT => 'timestamp|user|revision',
 107+ ApiBase :: PARAM_TYPE => array (
 108+ 'timestamp',
 109+ 'user',
 110+ 'revision',
 111+ 'text',
 112+ ),
 113+ ),
 114+ );
 115+ }
 116+
 117+ public function getParamDescription() {
 118+ return array(
 119+ 'repo' => 'Name of the repository',
 120+ 'limit' => 'How many comments to return',
 121+ 'start' => 'Timestamp to start listing at',
 122+ 'prop' => 'Which properties to return',
 123+ );
 124+ }
 125+
 126+ public function getDescription() {
 127+ return 'List comments on revisions in CodeReview';
 128+ }
 129+
 130+ public function getExamples() {
 131+ return array(
 132+ 'api.php?action=query&list=codecomments&ccrepo=MediaWiki',
 133+ 'api.php?action=query&list=codecomments&ccrepo=MediaWiki&ccprop=timestamp|user|revision|text',
 134+ );
 135+ }
 136+
 137+ public function getVersion() {
 138+ return __CLASS__ . ': $Id: ApiCodeComments.php 48777 2009-03-25 01:26:54Z aaron $';
 139+ }
 140+}
Index: branches/wmf-deployment/extensions/CodeReview/api/ApiCodeDiff.php
@@ -0,0 +1,80 @@
 2+<?php
 3+
 4+class ApiCodeDiff extends ApiBase {
 5+
 6+ public function execute() {
 7+ global $wgUser;
 8+ // Before doing anything at all, let's check permissions
 9+ if( !$wgUser->isAllowed('codereview-use') ) {
 10+ $this->dieUsage('You don\'t have permission to view code diffs','permissiondenied');
 11+ }
 12+ $params = $this->extractRequestParams();
 13+
 14+ if ( !isset( $params['repo'] ) ) {
 15+ $this->dieUsageMsg( array( 'missingparam', 'repo' ) );
 16+ }
 17+ if ( !isset( $params['rev'] ) ) {
 18+ $this->dieUsageMsg( array( 'missingparam', 'rev' ) );
 19+ }
 20+
 21+ $repo = CodeRepository::newFromName( $params['repo'] );
 22+ if ( !$repo ) {
 23+ $this->dieUsage( "Invalid repo ``{$params['repo']}''", 'invalidrepo' );
 24+ }
 25+
 26+ $svn = SubversionAdaptor::newFromRepo( $repo->getPath() );
 27+ $lastStoredRev = $repo->getLastStoredRev();
 28+
 29+ if ( $params['rev'] > $lastStoredRev ) {
 30+ $this->dieUsage( "There is no revision with ID {$params['rev']}", 'nosuchrev' );
 31+ }
 32+
 33+ $diff = $repo->getDiff( $params['rev'] );
 34+
 35+ if ( $diff ) {
 36+ $hilite = new CodeDiffHighlighter();
 37+ $html = $hilite->render( $diff );
 38+ } else {
 39+ // FIXME: Are we sure we don't want to throw an error here?
 40+ $html = 'Failed to load diff.';
 41+ }
 42+
 43+ $data = array(
 44+ 'repo' => $params['repo'],
 45+ 'id' => $params['rev'],
 46+ 'diff' => $html
 47+ );
 48+ $this->getResult()->addValue( 'code', 'rev', $data );
 49+ }
 50+
 51+ public function getAllowedParams() {
 52+ return array(
 53+ 'repo' => null,
 54+ 'rev' => array(
 55+ ApiBase::PARAM_TYPE => 'integer',
 56+ ApiBase::PARAM_MIN => 1
 57+ )
 58+ );
 59+ }
 60+
 61+ public function getParamDescription() {
 62+ return array(
 63+ 'repo' => 'Name of repository to look at',
 64+ 'rev' => 'Revision ID to fetch diff of' );
 65+ }
 66+
 67+ public function getDescription() {
 68+ return array(
 69+ 'Fetch formatted diff from CodeReview\'s backing revision control system.' );
 70+ }
 71+
 72+ public function getExamples() {
 73+ return array(
 74+ 'api.php?action=codediff&repo=MediaWiki&rev=42080',
 75+ );
 76+ }
 77+
 78+ public function getVersion() {
 79+ return __CLASS__ . ': $Id: ApiCodeDiff.php 48777 2009-03-25 01:26:54Z aaron $';
 80+ }
 81+}
Index: branches/wmf-deployment/extensions/CodeReview/CodeReview.alias.php
@@ -19,7 +19,7 @@
2020 * @author Meno25
2121 */
2222 $aliases['ar'] = array(
23 - 'Code' => array( 'كود' ),
 23+ 'Code' => array( 'كود', 'مراجعة_الكود' ),
2424 'RepoAdmin' => array( 'إدارة_المستودع' ),
2525 );
2626
@@ -94,6 +94,11 @@
9595 'RepoAdmin' => array( 'Admin repo' ),
9696 );
9797
 98+/** Indonesian (Bahasa Indonesia) */
 99+$aliases['id'] = array(
 100+ 'Code' => array( 'Kode', 'Tinjauan kode', 'TinjauanKode' ),
 101+);
 102+
98103 /** Japanese (日本語) */
99104 $aliases['ja'] = array(
100105 'Code' => array( 'コード', 'コードレビュー' ),
@@ -160,6 +165,12 @@
161166 'RepoAdmin' => array( 'रेपोप्रचालक' ),
162167 );
163168
 169+/** Slovak (Slovenčina) */
 170+$aliases['sk'] = array(
 171+ 'Code' => array( 'Kód', 'KontrolaKódu' ),
 172+ 'RepoAdmin' => array( 'SprávcaÚložiska' ),
 173+);
 174+
164175 /** Swahili (Kiswahili) */
165176 $aliases['sw'] = array(
166177 'Code' => array( 'Kodi', 'Onyesha kodi' ),
Index: branches/wmf-deployment/extensions/CodeReview/codereview.sql
@@ -65,7 +65,7 @@
6666 cr_diff mediumblob NULL,
6767 -- Text flags: gzip,utf-8,external
6868 cr_flags tinyblob NOT NULL,
69 -
 69+
7070 primary key (cr_repo_id, cr_id),
7171 key (cr_repo_id, cr_timestamp),
7272 key cr_repo_author (cr_repo_id, cr_author, cr_timestamp)
@@ -126,7 +126,7 @@
127127 cf_to int not null,
128128
129129 primary key (cf_repo_id, cf_from, cf_to),
130 - key (cf_repo_id, cf_to, cf_from)
 130+ key repo_to_from (cf_repo_id, cf_to, cf_from)
131131 ) /*$wgDBTableOptions*/;
132132
133133 -- And for our commenting system...
@@ -217,3 +217,70 @@
218218 key cpc_repo_rev_time (cpc_repo_id, cpc_rev_id, cpc_timestamp),
219219 key cpc_repo_time (cpc_repo_id, cpc_timestamp)
220220 ) /*$wgDBTableOptions*/;
 221+
 222+--
 223+-- Information on available test suites
 224+DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_suite;
 225+CREATE TABLE /*$wgDBprefix*/code_test_suite (
 226+ -- Unique ID per test suite
 227+ ctsuite_id int auto_increment not null,
 228+
 229+ -- Repository ID of the code base this applies to
 230+ ctsuite_repo_id int not null,
 231+
 232+ -- Which branch path this applies to, eg '/trunk/phase3'
 233+ ctsuite_branch_path varchar(255) not null,
 234+
 235+ -- Pleasantly user-readable name, eg "ParserTests"
 236+ ctsuite_name varchar(255) not null,
 237+
 238+ -- Description...
 239+ ctsuite_desc varchar(255) not null,
 240+
 241+ primary key ctsuite_id (ctsuite_id)
 242+) /*$wgDBtableOptions*/;
 243+
 244+DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_case;
 245+CREATE TABLE /*$wgDBprefix*/code_test_case (
 246+ ctcase_id int auto_increment not null,
 247+ ctcase_suite_id int not null,
 248+ ctcase_name varchar(255) not null,
 249+
 250+ primary key ctc_id (ctcase_id),
 251+ key (ctcase_suite_id, ctcase_id)
 252+) /*$wgDBtableOptions*/;
 253+
 254+DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_run;
 255+CREATE TABLE /*$wgDBprefix*/code_test_run (
 256+ ctrun_id int auto_increment not null,
 257+
 258+ ctrun_suite_id int not null,
 259+ ctrun_rev_id int not null,
 260+
 261+ ctrun_status enum ('running', 'complete', 'abort'),
 262+
 263+ ctrun_count_total int,
 264+ ctrun_count_success int,
 265+
 266+ primary key ctrun_id (ctrun_id),
 267+ key suite_rev (ctrun_suite_id, ctrun_rev_id)
 268+) /*$wgDBtableOptions*/;
 269+
 270+
 271+DROP TABLE IF EXISTS /*$wgDBprefix*/code_test_result;
 272+CREATE TABLE /*$wgDBprefix*/code_test_result (
 273+ ctresult_id int auto_increment not null,
 274+
 275+ -- Which test run and case are we on?
 276+ ctresult_run_id int not null,
 277+ ctresult_case_id int not null,
 278+
 279+ -- Did we succeed or fail?
 280+ ctresult_success bool not null,
 281+
 282+ -- Optional HTML chunk data
 283+ ctresult_details blob,
 284+
 285+ primary key ctr_id (ctresult_id),
 286+ key run_id (ctresult_run_id, ctresult_id)
 287+) /*$wgDBtableOptions*/;
Property changes on: branches/wmf-deployment/extensions/CodeReview
___________________________________________________________________
Added: svn:mergeinfo
221288 Merged /trunk/phase3/extensions/CodeReview:r52290,52402,52404,52718,52737,52759,52776,52791,52800,52808,52812-52813,52815-52819,52822,52846,52850,52852-52853,52855-52857,52859,52879,52924,52986,53128-53129,53190,53197,53199,53203-53204,53210-53211,53247,53249,53252,53267,53369
222289 Merged /trunk/phase3/CodeReview:r52859,53272
223290 Merged /branches/REL1_15/phase3/extensions/CodeReview:r51646
224291 Merged /trunk/extensions/CodeReview:r52089-54191

Past revisions this follows-up on

RevisionCommit summaryAuthorDate
r54191Divide out UI and backend classes to subdirectories to help keep things sorte...brion06:54, 2 August 2009

Status & tagging log