Indefero

Indefero Commit Details


Date:2009-04-25 09:24:40 (15 years 7 months ago)
Author:Loic d'Anterroches
Branch:dev, develop, feature-issue_links, feature.better-home, feature.content-md5, feature.diff-whitespace, feature.download-md5, feature.issue-links, feature.issue-of-others, feature.issue-summary, feature.search-filter, feature.webrepos, feature.wiki-default-page, master, release-1.1, release-1.2, release-1.3
Commit:7c502b1745a2f7188299dac7c754f21690c6e858
Parents: aab8720cac801e21672f9372881624c4b8bedaf3
Message:Continued the SCM backend refactor.

The new backend is near completion.
Changes:

File differences

src/IDF/Project.php
358358
359359
360360
361
361
362362
363363
364364
......
369369
370370
371371
372
372
373373
374374
375375
376376
377
378
377
378
379379
380380
381381
$conf = $this->getConf();
$scm = $conf->getVal('scm', 'git');
$scms = Pluf::f('allowed_scm');
return call_user_func(array($scms[$scm], 'getRemoteAccessUrl'),
return call_user_func(array($scms[$scm], 'getAnonymousAccessUrl'),
$this);
}
* same as the one to read. For example, you do a checkout with
* git-daemon and push with SSH.
*/
public function getWriteRemoteAccessUrl()
public function getWriteRemoteAccessUrl($user)
{
$conf = $this->getConf();
$scm = $conf->getVal('scm', 'git');
$scms = Pluf::f('allowed_scm');
return call_user_func(array($scms[$scm], 'getWriteRemoteAccessUrl'),
$this);
return call_user_func(array($scms[$scm], 'getAuthAccessUrl'),
$this, $user);
}
/**
src/IDF/Scm.php
3737
3838
3939
40
40
41
42
4143
4244
4345
......
8486
8587
8688
87
89
8890
8991
9092
......
9496
9597
9698
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
97122
98123
99124
......
104129
105130
106131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
107161
108162
109
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
110183
111184
112185
......
116189
117190
118191
119
192
193
194
195
196
120197
121198
122199
......
201278
202279
203280
204
205
206
281
282
283
207284
208285
209286
210
211
287
288
289
212290
213
214
291
215292
216293
217294
218
295
219296
220297
221298
222299
300
301
302
303
304
305
306
307
308
309
310
223311
224312
225
313
226314
227
228
229
230
315
316
317
231318
232
319
233320
234
235
236
237
238
239
240
241
242
243
244
245
246
321
247322
248323
249324
250
325
251326
252
253
327
328
329
254330
255
331
256332
257
258
259
260
261
262
263
264
333
265334
266335
267336
*
* Note on caching: You must not cache ephemeral information like the
* changelog, but you can cache the commit info (except with
* subversion where you can change commit info...).
* subversion where you can change commit info...). It is ok to do
* some caching for the lifetime of the IDF_Scm object, for example
* not to retrieve several times the list of branches, etc.
*
* All the output of the methods must be serializable. This means that
* if you are parsing XML you need to correctly cast the results as
* @param IDF_Project
* @return Object
*/
public static function get($project=null)
public static function get($project)
{
// Get scm type from project conf ; defaults to git
// We will need to cache the factory
}
/**
* Returns the URL of the git daemon.
*
* @param IDF_Project
* @return string URL
*/
public static function getAnonymousAccessUrl($project)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns the URL for SSH access
*
* @param IDF_Project
* @param Pluf_User
* @return string URL
*/
public static function getAuthAccessUrl($project, $user)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Check if the backend is available for display.
*
* @return bool Available
}
/**
* Check if a revision or commit is valid.
*
* @param string Revision or commit
* @return bool
*/
public function isValidRevision($rev)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns in which branches a commit/path is.
*
* A commit can be in several branches and some of the SCMs are
* managing branches using subfolders (like Subversion).
*
* This means that to know in which branch we are at the moment,
* one needs to have both the path and the commit.
*
* @param string Commit
* @param string Path
* @return array Branches
*/
public function inBranches($commit, $path)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Returns the list of branches.
*
* @return array For example array('trunk', '1.0branch')
* The return value must be a branch indexed array with the
* optional path to access the branch as value. For example with
* git you would get (note that some people are using / in the
* name of their git branches):
*
* <pre>
* array('master' => '',
* 'foo-branch' => '',
* 'design/feature1' => '')
* </pre>
*
* But with Subversion, as the branches are managed as subfolder
* with a special folder for trunk, you would get something like:
*
* <pre>
* array('trunk' => 'trunk',
* 'foo-branch' => 'branches/foo-branch',)
* </pre>
*
* @return array Branches
*/
public function getBranches()
{
/**
* Returns the list of tags.
*
* @return array For example array('v0.9', 'v1.0')
* The format is the same as for the branches.
*
* @see self::getBranches()
*
* @return array Tags
*/
public function getTags()
{
/**
* Given a revision and a file path, retrieve the file content.
*
* The third parameter is to only request the command that is used
* to get the file content. This is used when downloading a file
* at a given revision as it can be passed to a
* The $cmd_only parameter is to only request the command that is
* used to get the file content. This is used when downloading a
* file at a given revision as it can be passed to a
* Pluf_HTTP_Response_CommandPassThru reponse. This allows to
* stream a large response without buffering it in memory.
*
* The file definition can be a hash or a path depending on the
* SCM.
* The file definition is coming from getPathInfo().
*
* @see self::getPathInfo()
*
* @param string File definition
* @param string Revision ('')
* @param stdClass File definition
* @param bool Returns command only (false)
* @return string File content
*/
public function getFile($def, $rev='', $cmd_only=false)
public function getFile($def, $cmd_only=false)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Get information about a file or a path.
*
* @param string File or path
* @param string Revision (null)
* @return mixed False or stdClass with info
*/
public function getPathInfo($file, $rev=null)
{
throw new Pluf_Exception_NotImplemented();
}
/**
* Equivalent to exec but with caching.
* Given a revision and possible path returns additional properties.
*
* @param string Command
* @param &array Output
* @param &int Return value
* @return string Last line of the output
* @param string Revision
* @param string Path ('')
* @return mixed null or array of properties
*/
public static function exec($command, &$output=array(), &$return=0)
public function getProperties($rev, $path='')
{
$command = Pluf::f('idf_exec_cmd_prefix', '').$command;
$key = md5($command);
$cache = Pluf_Cache::factory();
if (null === ($res=$cache->get($key))) {
$ll = exec($command, $output, $return);
if ($return != 0 and Pluf::f('debug_scm', false)) {
throw new IDF_Scm_Exception(sprintf('Error when running command: "%s", return code: %d', $command, $return));
}
$cache->set($key, array($ll, $return, $output));
} else {
list($ll, $return, $output) = $res;
}
return $ll;
return null;
}
/**
* Equivalent to shell_exec but with caching.
* Generate the command to create a zip archive at a given commit.
*
* @param string Command
* @return string Output of the command
* @param string Commit
* @param string Prefix ('repository/')
* @return string Command
*/
public static function shell_exec($command)
public function getArchiveCommand($commit, $prefix='repository/')
{
$command = Pluf::f('idf_exec_cmd_prefix', '').$command;
$key = md5($command);
$cache = Pluf_Cache::factory();
if (null === ($res=$cache->get($key))) {
$res = shell_exec($command);
$cache->set($key, $res);
}
return $res;
throw new Pluf_Exception_NotImplemented();
}
/**
src/IDF/Scm/Git.php
6161
6262
6363
64
64
6565
6666
6767
......
6969
7070
7171
72
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
7392
7493
94
7595
7696
7797
......
152172
153173
154174
155
156
157
158
159
160
161
162
175
163176
164177
165178
166179
167
168
169
170
171
172
173
180
174181
175182
176183
......
187194
188195
189196
197
198
199
200
201
202
190203
191204
192205
193206
194
195207
196208
197
209
198210
199211
200212
201213
202214
203
215
204216
205217
206218
......
257269
258270
259271
260
272
261273
262274
263275
264276
265277
266278
267
279
268280
269281
270282
......
276288
277289
278290
279
280
281
282
283
284
285
286
291
287292
288
289
290
291
293
294
295
296
297
292298
293299
294300
......
345351
346352
347353
348
354
349355
350356
351357
......
377383
378384
379385
380
386
381387
382388
383389
......
436442
437443
438444
439
440
441
442
443
444
445
446
445
447446
448447
449448
......
471470
472471
473472
474
473
475474
476475
477476
478
477
479478
480479
481480
}
$res = array();
foreach ($out as $b) {
$res[] = substr($b, 2);
$res[substr($b, 2)] = '';
}
$this->cache['branches'] = $res;
return $res;
public function getMainBranch()
{
return 'master';
$possible = array('master', 'main', 'trunk', 'local');
$branches = array_keys($this->getBranches());
foreach ($possible as $p) {
if (in_array($p, $branches)) {
return $p;
}
}
return $branches[0];
}
/**
* Note: Running the `git branch --contains $commit` is
* theoritically the best way to do it, until you figure out that
* you cannot cache the result and that it takes several seconds
* to execute on a big tree.
*/
public function inBranches($commit, $path)
{
return (in_array($commit, array_keys($this->getBranches())))
? array($commit) : array();
}
/**
* Git "tree" is not the same as the tree we get here.
*
return ($users->count() > 0) ? $users[0] : null;
}
/**
* Returns the URL of the git daemon.
*
* @param IDF_Project
* @return string URL
*/
public static function getRemoteAccessUrl($project)
public static function getAnonymousAccessUrl($project)
{
return sprintf(Pluf::f('git_remote_url'), $project->shortname);
}
/**
* Returns the URL for SSH access
*
* @param IDF_Project
* @return string URL
*/
public static function getWriteRemoteAccessUrl($project)
public static function getAuthAccessUrl($project, $user)
{
return sprintf(Pluf::f('git_write_remote_url'), $project->shortname);
}
return new IDF_Scm_Git($rep, $project);
}
public function isValidRevision($commit)
{
return ('commit' == $this->testHash($commit));
}
/**
* Test a given object hash.
*
* @param string Object hash.
* @param null to be svn client compatible
* @return mixed false if not valid or 'blob', 'tree', 'commit'
*/
public function testHash($hash, $dummy=null)
public function testHash($hash)
{
$cmd = sprintf('GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file -t %s',
escapeshellarg($this->repo),
escapeshellarg($hash));
$ret = 0; $out = array();
IDF_Scm::exec($cmd, $out, $ret);
exec($cmd, $out, $ret);
if ($ret != 0) return false;
return trim($out[0]);
}
* @param string Commit ('HEAD')
* @return false Information
*/
public function getFileInfo($totest, $commit='HEAD')
public function getPathInfo($totest, $commit='HEAD')
{
$cmd_tmpl = 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' ls-tree -r -t -l %s';
$cmd = sprintf($cmd_tmpl,
escapeshellarg($this->repo),
escapeshellarg($commit));
$out = array();
IDF_Scm::exec($cmd, $out);
exec($cmd, $out);
foreach ($out as $line) {
list($perm, $type, $hash, $size, $file) = preg_split('/ |\t/', $line, 5, PREG_SPLIT_NO_EMPTY);
if ($totest == $file) {
return false;
}
/**
* Get a blob.
*
* @param string request_file_info
* @param null to be svn client compatible
* @return string Raw blob
*/
public function getBlob($request_file_info, $dummy=null)
public function getFile($def, $cmd_only=false)
{
return shell_exec(sprintf(Pluf::f('idf_exec_cmd_prefix', '').
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file blob %s',
escapeshellarg($this->repo),
escapeshellarg($request_file_info->hash)));
$cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', '').
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' cat-file blob %s',
escapeshellarg($this->repo),
escapeshellarg($def->hash));
return ($cmd_only) ? $cmd : shell_exec($cmd);
}
"'commit %H%n'",
escapeshellarg($commit));
$out = array();
IDF_Scm::exec($cmd, $out);
exec($cmd, $out);
$affected = count($out) - 2;
$added = 0;
$removed = 0;
escapeshellarg($this->repo), $n, $this->mediumtree_fmt,
escapeshellarg($commit));
$out = array();
IDF_Scm::exec($cmd, $out);
exec($cmd, $out);
return self::parseLog($out, 4);
}
return $res;
}
/**
* Generate the command to create a zip archive at a given commit.
*
* @param string Commit
* @param string Prefix ('git-repo-dump')
* @return string Command
*/
public function getArchiveCommand($commit, $prefix='git-repo-dump/')
public function getArchiveCommand($commit, $prefix='repository/')
{
return sprintf(Pluf::f('idf_exec_cmd_prefix', '').
'GIT_DIR=%s '.Pluf::f('git_path', 'git').' archive --format=zip --prefix=%s %s',
{
$file->type = 'extern';
$file->extern = '';
$info = $this->getFileInfo('.gitmodules', $commit);
$info = $this->getPathInfo('.gitmodules', $commit);
if ($info == false) {
return $file;
}
$gitmodules = $this->getBlob($info);
$gitmodules = $this->getFile($info);
if (preg_match('#\[submodule\s+\"'.$file->fullpath.'\"\]\s+path\s=\s(\S+)\s+url\s=\s(\S+)#mi', $gitmodules, $matches)) {
$file->extern = $matches[2];
}
src/IDF/Views/Source.php
6666
6767
6868
69
69
7070
7171
7272
......
119119
120120
121121
122
122123
123124
124125
......
126127
127128
128129
129
130
130
131131
132
133
134
135
132
136133
137134
138135
......
140137
141138
142139
143
140
144141
145142
146143
......
174171
175172
176173
177
174
178175
179176
180177
181178
182
179
183180
184181
185182
......
190187
191188
192189
193
190
194191
195192
196193
......
210207
211208
212209
213
210
214211
215212
216213
......
243240
244241
245242
246
243
247244
248245
249246
......
273270
274271
275272
276
273
277274
278275
279276
......
309306
310307
311308
312
309
313310
314311
315312
......
347344
348345
349346
350
347
351348
352349
353350
......
377374
378375
379376
380
377
381378
382379
383380
384381
385382
386383
387
384
388385
389386
390387
......
394391
395392
396393
397
394
398395
399396
400397
......
410407
411408
412409
413
410
414411
415412
416413
......
441438
442439
443440
444
441
445442
446443
447444
......
451448
452449
453450
454
451
455452
456453
457454
$scm = IDF_Scm::get($request->project);
$branches = $scm->getBranches();
$commit = $match[2];
if ('commit' != $scm->testHash($commit)) {
if (!$scm->isValidRevision($commit)) {
if (count($branches) == 0) {
// Redirect to the project source help
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::help',
return new Pluf_HTTP_Response_Redirect($url);
}
$branches = $scm->getBranches();
$in_branches = $scm->inBranches($commit, '');
$cache = Pluf_Cache::factory();
$key = sprintf('Project:%s::IDF_Views_Source::treeBase:%s::',
$request->project->id, $commit);
$res = new Pluf_Template_ContextVars($scm->getTree($commit));
$cache->set($key, $res);
}
$tree_in = in_array($commit, $branches);
//$tree_in = in_array($commit, $branches);
$scmConf = $request->conf->getVal('scm', 'git');
$props = null;
if ($scmConf === 'svn') {
$props = $scm->getProperties($commit);
}
$props = $scm->getProperties($commit);
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/tree.html',
array(
'page_title' => $title,
'files' => $res,
'cobject' => $cobject,
'commit' => $commit,
'tree_in' => $tree_in,
'tree_in' => $in_branches,
'branches' => $branches,
'props' => $props,
),
return new Pluf_HTTP_Response_Redirect($url, 301);
}
if ('commit' != $scm->testHash($commit, $request_file)) {
if (!$scm->isValidRevision($commit, $request_file)) {
// Redirect to the first branch
return new Pluf_HTTP_Response_Redirect($fburl);
}
$request_file_info = $scm->getFileInfo($request_file, $commit);
$request_file_info = $scm->getPathInfo($request_file, $commit);
if (!$request_file_info) {
// Redirect to the first branch
return new Pluf_HTTP_Response_Redirect($fburl);
$commit, $scm);
if (!self::isText($info)) {
$rep = new Pluf_HTTP_Response($scm->getBlob($request_file_info, $commit),
$rep = new Pluf_HTTP_Response($scm->getFile($request_file_info),
$info[0]);
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"';
return $rep;
$page_title = $bc.' - '.$title;
$cobject = $scm->getCommit($commit);
$tree_in = in_array($commit, $branches);
$in_branches = $scm->inBranches($commit, $request_file);
try {
$cache = Pluf_Cache::factory();
$key = sprintf('Project:%s::IDF_Views_Source::tree:%s::%s',
'cobject' => $cobject,
'base' => $request_file_info->file,
'prev' => $previous,
'tree_in' => $tree_in,
'tree_in' => $in_branches,
'branches' => $branches,
'props' => $props,
),
$scm = IDF_Scm::get($request->project);
$commit = $match[2];
$branches = $scm->getBranches();
if ('commit' != $scm->testHash($commit)) {
if (!$scm->isValidRevision($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$scm = IDF_Scm::get($request->project);
$commit = $match[2];
$branches = $scm->getBranches();
if ('commit' != $scm->testHash($commit)) {
if (!$scm->isValidRevision($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
if ($scmConf === 'svn') {
$props = $scm->getProperties($commit, $request_file);
}
$content = self::highLight($extra['mime'], $scm->getBlob($request_file_info, $commit));
$content = self::highLight($extra['mime'], $scm->getFile($request_file_info));
return Pluf_Shortcuts_RenderToResponse('idf/source/'.$scmConf.'/file.html',
array(
'page_title' => $page_title,
$branches = $scm->getBranches();
$commit = $match[2];
$request_file = $match[3];
if ('commit' != $scm->testHash($commit, $request_file)) {
if (!$scm->isValidRevision($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
$branches[0]));
return new Pluf_HTTP_Response_Redirect($url);
}
$request_file_info = $scm->getFileInfo($request_file, $commit);
$request_file_info = $scm->getPathInfo($request_file, $commit);
if (!$request_file_info or $request_file_info->type == 'tree') {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
}
$info = self::getRequestedFileMimeType($request_file_info,
$commit, $scm);
$rep = new Pluf_HTTP_Response($scm->getBlob($request_file_info, $commit),
$rep = new Pluf_HTTP_Response($scm->getFile($request_file_info),
$info[0]);
$rep->headers['Content-Disposition'] = 'attachment; filename="'.$info[1].'"';
return $rep;
$commit = trim($match[2]);
$scm = IDF_Scm::get($request->project);
$branches = $scm->getBranches();
if ('commit' != $scm->testHash($commit)) {
if (!$scm->isValidRevision($commit)) {
// Redirect to the first branch
$url = Pluf_HTTP_URL_urlForView('IDF_Views_Source::treeBase',
array($request->project->shortname,
return $mime;
}
return self::getMimeTypeFromContent($file_info->file,
$scm->getBlob($file_info, $commit));
$scm->getFile($file_info));
}
/**
* @param string File content
* @return array Mime type found or 'application/octet-stream', basename, extension
*/
public static function getMimeTypeFromContent($file, $filedata)
public static function getMimeTypeFromContent($file, &$filedata)
{
$info = pathinfo($file);
$res = array('application/octet-stream',
src/IDF/relations.php
4040
4141
4242
43
4344
4445
4546
$m['IDF_Key'] = array('relate_to' => array('Pluf_User'));
$m['IDF_Conf'] = array('relate_to' => array('IDF_Project'));
$m['IDF_Commit'] = array('relate_to' => array('IDF_Project', 'Pluf_User'));
$m['IDF_Scm_Cache_Git'] = array('relate_to' => array('IDF_Project'));
Pluf_Signal::connect('Pluf_Template_Compiler::construct_template_tags_modifiers',
array('IDF_Middleware', 'updateTemplateTagsModifiers'));
src/IDF/templates/idf/source/git/help.html
88
99
1010
11
11
1212
1313
1414
......
2222
2323
2424
25
25
2626
2727
2828
<h3>{trans 'Command-Line Access'}</h3>
<p><kbd>git clone {if $project.private}{$project.getWriteRemoteAccessUrl()}{else}{$project.getRemoteAccessUrl()}{/if}</kbd></p>
<p><kbd>git clone {if $project.private}{$project.getWriteRemoteAccessUrl($user)}{else}{$project.getRemoteAccessUrl()}{/if}</kbd></p>
{aurl 'url', 'IDF_Views_User::myAccount'}
<p>{blocktrans}You may need to <a href="{$url}">provide your SSH key</a>. The synchronization of your SSH key can take a couple of minutes. You can learn more about <a href="http://www.google.com/search?q=public+ssh+key+authentication">SSH key authentification</a>.{/blocktrans}</p>
git init
git add .
git commit -m "initial import"
git remote add origin {$project.getWriteRemoteAccessUrl()}
git remote add origin {$project.getWriteRemoteAccessUrl($url)}
git push origin master
</pre>
src/IDF/templates/idf/source/git/tree.html
4747
4848
4949
50
50
5151
5252
5353
5454
5555
56
57
58
56
57
58
5959
6060
6161
</tbody>
</table>
{aurl 'url', 'IDF_Views_Source::download', array($project.shortname, $commit)}
<p class="right soft"><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/package-grey.png'}" alt="{trans 'Archive'}" align="bottom" /></a> <a href="{$url}">{trans 'Download this version'}</a> {trans 'or'} <kbd>git clone {if $project.private}{$project.getWriteRemoteAccessUrl()}{else}{$project.getRemoteAccessUrl()}{/if}</kbd> <a href="{url 'IDF_Views_Source::help', array($project.shortname)}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/help.png'}" alt="{trans 'Help'}" /></a></p>
<p class="right soft"><a href="{$url}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/package-grey.png'}" alt="{trans 'Archive'}" align="bottom" /></a> <a href="{$url}">{trans 'Download this version'}</a> {trans 'or'} <kbd>git clone {if $project.private}{$project.getWriteRemoteAccessUrl($user)}{else}{$project.getRemoteAccessUrl()}{/if}</kbd> <a href="{url 'IDF_Views_Source::help', array($project.shortname)}"><img style="vertical-align: text-bottom;" src="{media '/idf/img/help.png'}" alt="{trans 'Help'}" /></a></p>
{/block}
{block context}
<p><strong>{trans 'Branches:'}</strong><br />
{foreach $branches as $branch}
{aurl 'url', 'IDF_Views_Source::treeBase', array($project.shortname, $branch)}
<span class="label{if $commit == $branch} active{/if}"><a href="{$url}" class="label">{$branch}</a></span><br />
{foreach $branches as $branch => $path}
{aurl 'url', 'IDF_Views_Source::tree', array($project.shortname, $branch)}
<span class="label{if in_array($branch, $tree_in)} active{/if}"><a href="{$url}" class="label">{$branch}</a></span><br />
{/foreach}
</p>
{/block}

Archive Download the corresponding diff file

Page rendered in 0.10531s using 13 queries.