Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 145 |
|
0.00% |
0 / 7 |
CRAP | |
0.00% |
0 / 1 |
SubversionShell | |
0.00% |
0 / 145 |
|
0.00% |
0 / 7 |
1640 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
canConnect | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
12 | |||
getFile | |
0.00% |
0 / 7 |
|
0.00% |
0 / 1 |
6 | |||
getDiff | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
2 | |||
getLog | |
0.00% |
0 / 72 |
|
0.00% |
0 / 1 |
380 | |||
getDirList | |
0.00% |
0 / 36 |
|
0.00% |
0 / 1 |
132 | |||
getExtraArgs | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
6 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\CodeReview\Backend; |
4 | |
5 | use DOMDocument; |
6 | use Exception; |
7 | |
8 | /** |
9 | * Using the thingy-bobber |
10 | */ |
11 | class SubversionShell extends SubversionAdaptor { |
12 | private const MIN_MEMORY = 204800; |
13 | |
14 | public function __construct( $repo ) { |
15 | parent::__construct( $repo ); |
16 | global $wgMaxShellMemory; |
17 | if ( $wgMaxShellMemory < self::MIN_MEMORY ) { |
18 | $wgMaxShellMemory = self::MIN_MEMORY; |
19 | wfDebug( __METHOD__ . " raised wgMaxShellMemory to $wgMaxShellMemory\n" ); |
20 | } |
21 | } |
22 | |
23 | public function canConnect() { |
24 | $command = sprintf( |
25 | 'svn info %s %s', |
26 | $this->getExtraArgs(), |
27 | wfEscapeShellArg( $this->mRepoPath ) |
28 | ); |
29 | |
30 | $result = wfShellExec( $command ); |
31 | if ( $result == '' ) { |
32 | return false; |
33 | } elseif ( strpos( $result, 'No repository found' ) !== false ) { |
34 | return false; |
35 | } else { |
36 | return true; |
37 | } |
38 | } |
39 | |
40 | public function getFile( $path, $rev = null ) { |
41 | if ( $rev ) { |
42 | $path .= "@$rev"; |
43 | } |
44 | $command = sprintf( |
45 | 'svn cat %s %s', |
46 | $this->getExtraArgs(), |
47 | wfEscapeShellArg( $this->mRepoPath . $path ) ); |
48 | |
49 | return wfShellExec( $command ); |
50 | } |
51 | |
52 | public function getDiff( $path, $rev1, $rev2 ) { |
53 | $command = sprintf( |
54 | 'svn diff -r%d:%d %s %s', |
55 | intval( $rev1 ), |
56 | intval( $rev2 ), |
57 | $this->getExtraArgs(), |
58 | wfEscapeShellArg( $this->mRepoPath . $path ) |
59 | ); |
60 | |
61 | return wfShellExec( $command ); |
62 | } |
63 | |
64 | public function getLog( $path, $startRev = null, $endRev = null ) { |
65 | $lang = wfIsWindows() ? '' : 'LC_ALL=en_US.utf-8 '; |
66 | $command = sprintf( |
67 | "{$lang}svn log -v -r%s:%s %s %s", |
68 | wfEscapeShellArg( $this->_rev( $startRev, 'BASE' ) ), |
69 | wfEscapeShellArg( $this->_rev( $endRev, 'HEAD' ) ), |
70 | $this->getExtraArgs(), |
71 | wfEscapeShellArg( $this->mRepoPath . $path ) ); |
72 | |
73 | $lines = explode( "\n", wfShellExec( $command ) ); |
74 | $out = []; |
75 | |
76 | $divider = str_repeat( '-', 72 ); |
77 | $formats = [ |
78 | 'rev' => '/^r(\d+)$/', |
79 | 'author' => '/^(.*)$/', |
80 | // account for '(no date)' |
81 | 'date' => '/^(?:(.*?) )?\(.*\)$/', |
82 | 'lines' => '/^(\d+) lines?$/', |
83 | ]; |
84 | $state = "start"; |
85 | foreach ( $lines as $line ) { |
86 | $line = rtrim( $line ); |
87 | |
88 | switch ( $state ) { |
89 | case 'start': |
90 | if ( $line == $divider ) { |
91 | $state = 'revdata'; |
92 | break; |
93 | } else { |
94 | return $out; |
95 | # throw new Exception( "Unexpected start line: $line" ); |
96 | } |
97 | // Fall through |
98 | case 'revdata': |
99 | if ( $line == '' ) { |
100 | $state = 'done'; |
101 | break; |
102 | } |
103 | $data = []; |
104 | $bits = explode( ' | ', $line ); |
105 | $i = 0; |
106 | foreach ( $formats as $key => $regex ) { |
107 | $text = $bits[$i++]; |
108 | $matches = []; |
109 | if ( preg_match( $regex, $text, $matches ) ) { |
110 | $data[$key] = $matches[1]; |
111 | } else { |
112 | throw new Exception( |
113 | "Unexpected format for $key in '$text'" ); |
114 | } |
115 | } |
116 | $data['msg'] = ''; |
117 | $data['paths'] = []; |
118 | $state = 'changedpaths'; |
119 | break; |
120 | case 'changedpaths': |
121 | if ( $line == 'Changed paths:' ) { |
122 | // broken when svn messages are not in English |
123 | $state = 'path'; |
124 | } elseif ( $line == '' ) { |
125 | // No changed paths? |
126 | $state = 'msg'; |
127 | } else { |
128 | throw new Exception( |
129 | "Expected 'Changed paths:' or '', got '$line'" ); |
130 | } |
131 | break; |
132 | case 'path': |
133 | if ( $line == '' ) { |
134 | // Out of paths. Move on to the message... |
135 | $state = 'msg'; |
136 | } else { |
137 | $matches = []; |
138 | if ( preg_match( '/^ (.) (.*)$/', $line, $matches ) ) { |
139 | // @phan-suppress-next-line PhanUndeclaredVariableDim |
140 | $data['paths'][] = [ |
141 | 'action' => $matches[1], |
142 | 'path' => $matches[2] |
143 | ]; |
144 | } |
145 | } |
146 | break; |
147 | case 'msg': |
148 | $data['msg'] .= $line; |
149 | // @phan-suppress-next-line PhanTypeArraySuspiciousNull, PhanTypeInvalidUnaryOperandIncOrDec |
150 | if ( --$data['lines'] ) { |
151 | $data['msg'] .= "\n"; |
152 | } else { |
153 | unset( $data['lines'] ); |
154 | $out[] = $data; |
155 | $state = 'start'; |
156 | } |
157 | break; |
158 | case 'done': |
159 | throw new Exception( "Unexpected input after end: $line" ); |
160 | default: |
161 | throw new Exception( "Invalid state '$state'" ); |
162 | } |
163 | } |
164 | |
165 | return $out; |
166 | } |
167 | |
168 | public function getDirList( $path, $rev = null ) { |
169 | $command = sprintf( |
170 | 'svn list --xml -r%s %s %s', |
171 | wfEscapeShellArg( $this->_rev( $rev, 'HEAD' ) ), |
172 | $this->getExtraArgs(), |
173 | wfEscapeShellArg( $this->mRepoPath . $path ) |
174 | ); |
175 | $document = new DOMDocument(); |
176 | |
177 | $listXml = wfShellExec( $command ); |
178 | if ( !$listXml || !$document->loadXML( $listXml ) ) { |
179 | // svn list --xml returns invalid XML if the file does not exist |
180 | // FIXME: report bug upstream |
181 | return false; |
182 | } |
183 | |
184 | $entries = $document->getElementsByTagName( 'entry' ); |
185 | $result = []; |
186 | foreach ( $entries as $entry ) { |
187 | $item = []; |
188 | $item['type'] = $entry->getAttribute( 'kind' ); |
189 | foreach ( $entry->childNodes as $child ) { |
190 | switch ( $child->nodeName ) { |
191 | case 'name': |
192 | $item['name'] = $child->textContent; |
193 | break; |
194 | case 'size': |
195 | $item['size'] = intval( $child->textContent ); |
196 | break; |
197 | case 'commit': |
198 | $item['created_rev'] = intval( $child->getAttribute( 'revision' ) ); |
199 | foreach ( $child->childNodes as $commitEntry ) { |
200 | switch ( $commitEntry->nodeName ) { |
201 | case 'author': |
202 | $item['last_author'] = $commitEntry->textContent; |
203 | break; |
204 | case 'date': |
205 | $item['time_t'] = wfTimestamp( TS_UNIX, $commitEntry->textContent ); |
206 | break; |
207 | } |
208 | } |
209 | break; |
210 | } |
211 | } |
212 | $result[] = $item; |
213 | } |
214 | return $result; |
215 | } |
216 | |
217 | /** |
218 | * Returns a string of extra arguments to be passed into the shell commands |
219 | * @return string |
220 | */ |
221 | private function getExtraArgs() { |
222 | global $wgSubversionOptions, $wgSubversionUser, $wgSubversionPassword; |
223 | $args = $wgSubversionOptions; |
224 | if ( $wgSubversionUser ) { |
225 | $args .= ' --username ' . wfEscapeShellArg( $wgSubversionUser ) |
226 | . ' --password ' . wfEscapeShellArg( $wgSubversionPassword ); |
227 | } |
228 | return $args; |
229 | } |
230 | } |